Recently, I encountered an exchange problem, exception "
The calling thread must be STA, because many UI components require this." thrown in the main thread. This should not happen. This article describes the problem, root cause and solutions.
The architecture of my program could be described as:
MFC dialog based application, native C++ -> ATL DLL COM Server, inproc server, managed C++ -> WPF User control, C#.
The MFC application wants to create a WPF user control on its
HWND. It works well if the MFC application itself is a CLR support program. But when I use an ATL DLL COM Server compiled with managed C++ enabled, the exception is thrown.
Understand the Problem
The ALT COM Server's interface source code looks like the following:
STDMETHODIMP CWrapperObject::Luanch(LONG hWnd)
HwndSourceParameters^ sourceParams = gcnew HwndSourceParameters("WPFHWND");
sourceParams->PositionX = 0;
sourceParams->PositionY = 0;
sourceParams->Height = 1000;
sourceParams->Width = 1000;
sourceParams->ParentWindow = IntPtr(hWnd);
sourceParams->WindowStyle = WS_VISIBLE | WS_CHILD;
auto pHwndSource = gcnew HwndSource(*sourceParams);
auto ctrl1 = gcnew UserControl1();
FrameworkElement^ myPage = ctrl1;
pHwndSource->RootVisual = myPage;
The exception thrown out at line "
auto pHwndSource = gcnew hWndSource(sourceParams);",
HwndSource is a WPF class, which makes sense to require STA. But, the calling thread is the main thread of MFC application. MFC has already initialized COM as STA in
AFXOleInit(). Why do I still get this "
The calling thread must be STA, because many UI components require this." exception?
First Around Debug
To approve the exception, I checked the current thread's apartment state by the following code:
auto a = System::Threading::Thread::CurrentThread->GetApartmentState();
And checked value of an under debug mode, yes, it is MTA, back to check thread and confirmed that it was the main thread.
Then I did some Google search and got some information related to this problem, but no one provided a useful solution. This is a reason I propose this article. I will be very glad if someone else could benefit from this.
- .NET CLR will initialize thread as MTA by default.
- To control thread apartment state, we could call
SetApartmentState, but only before thread starting.
- The only way to set main thread's apartment state is use
Ok, fair enough, but not helping.
- .NET CLR uses MTA by default, so I need a method to set to STA.
SetApartmentState needs to be called before starting thread. But
SetApartmentState is a .NET Framework API, and cannot be called from native appliction. And in ATL managed C++, the thread is already running.
- We have the main thread, but native application does not have
STAThreadAttribute property. And there is no main thread function in ATL DLL COM Server, no matter managed or not.
But with the information on Google and exception description, I got the direction of solution.
Set apartment state to STA in the managed world, to a thread created in the native world.
After several tries, I got the final solution by luck, just call
SetApartmentState() before any WPF operations, it works. See the following code:
This solution looks ridiculous, Google just said, it only works when the thread is not starting. Thanks to Microsoft, .NET is open source now, we could check the source code to see why it works after thread starts (source code copied from .NET VM,
if ((!ThreadNotStarted(thread) && !ThreadIsRunning(thread)) ||
(!ThreadNotStarted(thread) && (GetThread() != thread)))
ok = FALSE;
state = thread->SetApartment(state, fireMDAOnMismatch == TRUE);
Ok, we may change the apartment state when the thread is running if only we are the thread itself....
After reading the other code related to
SetApartmentState, I finally understood why I got a "
should be STA" exception in an STA thread. The issue is caused by the following steps:
- Any operation on WPF will check thread apartment state is STA or not, which makes sense.
- But our thread is a native thread, the state in the managed world is unknown. Then
GetApartmentState will call
EnsureComStarted() under this case, see the following code (copied from .NET VM
if (state == Thread::AS_Unknown)
state = thread->GetApartment();
EnsureComStarted(), it will set the thread apartment state to MTA, if the thread apartment state is unknown. See the following code (copied from .NET VM
if (g_fComStarted == FALSE)
g_fComStarted = TRUE;
This is the root cause, a clear "side effect" which should be avoided. Microsoft's fault!
The final suggestion would be, if you encountered the same as me, you could create a interface called
SetThreadApartmentState(STA) in it. The world will be saved.
This is the first time I have written an article here. You are welcome to provide comments.