Click here to Skip to main content
13,794,563 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

10.2K views
3 bookmarked
Posted 22 Apr 2016
Licenced CPOL

Encounter Exception "The Calling Thread Must be STA, because many UI components require this." in ATL COM Server with CLR Enabled

, 22 Apr 2016
Rate this:
Please Sign up or sign in to vote.
Talking about C++/CLI, ATL COM Server, Apartment, .NET VM

Introduction

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.

Problem Description

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;

 return S_OK;
}

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.

Google Search

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.

Results:

  1. .NET CLR will initialize thread as MTA by default.
  2. To control thread apartment state, we could call SetApartmentState, but only before thread starting.
  3. The only way to set main thread's apartment state is use STAThreadAttribute.

Ok, fair enough, but not helping.

  1. .NET CLR uses MTA by default, so I need a method to set to STA.
  2. 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.
  3. 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.

Solution

After several tries, I got the final solution by luck, just call SetApartmentState() before any WPF operations, it works. See the following code:

System::Threading::Thread::CurrentThread->SetApartmentState(System::Threading::ApartmentState::STA);

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, SetApartmentState function).

//
// We can only change the apartment if the thread is unstarted or
// running, and if it's running we have to be in the thread's
// context.
if ((!ThreadNotStarted(thread) && !ThreadIsRunning(thread)) ||
    (!ThreadNotStarted(thread) && (GetThread() != thread)))
    ok = FALSE;
else
{
    EX_TRY
    {
        state = thread->SetApartment(state, fireMDAOnMismatch == TRUE);
    }
    EX_CATCH
    {
        pThis->LeaveObjMonitor();
        EX_RETHROW;
    }
    EX_END_CATCH_UNREACHABLE;
}

Ok, we may change the apartment state when the thread is running if only we are the thread itself....

Root Cause

After reading the other code related to GetApartmentState and 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 GetApartmentState)
    if (state == Thread::AS_Unknown)
    {
        // If the CLR hasn't started COM yet, start it up and attempt the call again.
        // We do this in order to minimize the number of situations under which we return
        // ApartmentState.Unknown to our callers.
        if (!g_fComStarted)
        {
            EnsureComStarted();
            state = thread->GetApartment();
        }
    }
    
  • In 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 EnSureComStarted())
    if (g_fComStarted == FALSE)
    {
        FinalizerThread::GetFinalizerThread()->SetRequiresCoInitialize();
    
        // Attempt to set the thread's apartment model (to MTA by default). May not
        // succeed (if someone beat us to the punch). That doesn't matter (since
        // COM+ objects are now apartment agile), we only care that a CoInitializeEx
        // has been performed on this thread by us.
        if (fCoInitCurrentThread)
           GetThread()->SetApartment(Thread::AS_InMTA, FALSE);
    
        // set the finalizer event
        FinalizerThread::EnableFinalization();
    
        g_fComStarted = TRUE;
    }
    

This is the root cause, a clear "side effect" which should be avoided. Microsoft's fault!

Final Solution

The final suggestion would be, if you encountered the same as me, you could create a interface called Initialize, call SetThreadApartmentState(STA) in it. The world will be saved.

History

This is the first time I have written an article here. You are welcome to provide comments.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Simon M Li
China China
No Biography provided

You may also be interested in...

Comments and Discussions

 
QuestionGreat writeup - wish you wrote more about your solution Pin
bryand76-Nov-16 12:22
memberbryand76-Nov-16 12:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.181207.3 | Last Updated 22 Apr 2016
Article Copyright 2016 by Simon M Li
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid