Gotcha with STAThreadAttribute and Managed C++

Managed thread objects have an ApartmentState property that can be set to STA or MTA.  But setting this property on the main thread doesn't work reliably because the CLR might set the apartment state to MTA (by calling CoInitializeEx(NULL, COINIT_MULTITHREADED)) before your first line of code executes.  And once a thread's apartment state has been set, it can't be changed (without calling CoUninitialize first).

Therefore, to reliably set the main thread's apartment state, you must place the STAThreadAttribute or MTAThreadAttribute on your entry point.  (MTA is the CLR's default, but using MTAThreadAttribute can be necessary in VB.NET since the compiler emits STAThreadAttribute on Main by default for backwards compatibility with VB6.)  This is important to understand even if you don't use Interop directly.  For example, using Windows Forms requires that your entry point is marked with STAThreadAttribute, and the code emitted when creating a new Visual C++ Windows Forms Application project in VS.NET 7.1 doesn't handle this requirement correctly.

Using these attributes is easy in any language except Managed C++.  You'd probably expect the following code to print "0x1":

  #include <objbase.h>
#include <stdio.h>
#using <mscorlib.dll>
using namespace System;

  [STAThread]

  int main()

  {

    printf("0x%x\n", CoInitialize(NULL));

    return 0;

  }

That's because the STAThreadAttribute should force the CLR to initialize the thread to STA, causing the explicit call to CoInitialize to return S_FALSE.  But if you run it, you'll see that it prints either "0x0" (S_OK, indicating that the apartment state was not previously set) or "0x80010106" (RPC_E_CHANGED_MODE, indicating that the apartment state was already set to MTA).  This means that the attribute didn't work.

The reason for this can be discovered by opening the assembly in ILDASM.  The main function isn't the assembly's true entry point.  The C++ compiler emits an entry point called _mainCRTStartup that initializes the C runtime library before calling your main method.  Furthermore, the v7.0 and v7.1 compilers do not propagate attributes on your main method to the true entry point.  This should be addressed in a future version of the product, but in the meantime you can do the following:

  #include <objbase.h>

  #include <stdio.h>

  #using <mscorlib.dll>

  using namespace System;

  extern "C" int mainCRTStartup();

  // Called by mainCRTStartup

  int main() {}

  // The true entry point

  [STAThread]

  int myMain()

  {

    // Initialize the CRT

    mainCRTStartup();

    printf("0x%x\n", CoInitialize(NULL));

    return 0;

  }

You also need to use the linker option /ENTRY:myMain to make myMain your entry point.  In Visual Studio .NET, this is the Entry Point setting under Configuration Properties -> Linker -> Advanced in the project's Property dialog.  This new code prints "0x1" every time.

This example used mainCRTStartup with main, but you could also use wMainCRTStartup with wmain.  Or for a Windows application (/SUBSYSTEM:WINDOWS), you can use WinMainCRTStartup with WinMain, or wWinMainCRTStartup with wWinMain.