The issue reported that SimpleObject COM instances were always created in MTA (Multi-Threaded Apartment) state, despite attempts to use [STAThread] attribute and derive from StandardOleMarshalObject. The root cause was that COM was not explicitly initialized in STA mode for the main thread that runs the COM server's message loop.
In an out-of-process COM server:
- The apartment state of created COM objects is inherited from the creating thread
- Simply adding
[STAThread]to Main() is insufficient without explicit COM initialization - The
SimpleObjectClassFactory.CreateInstance()callback creates objects on the main server thread - Without explicit
CoInitializeEx(COINIT_APARTMENTTHREADED), the thread defaults to MTA or remains uninitialized
[STAThread]
private static void Main(string[] args)- Marks the main application thread as STA
- Required for Windows message pump and UI operations
int hResult = NativeMethods.CoInitializeEx(
IntPtr.Zero,
NativeMethods.COINIT_APARTMENTTHREADED);
if (hResult != NativeMethods.S_OK && hResult != NativeMethods.S_FALSE)
{
throw new ApplicationException(
"CoInitializeEx failed w/err 0x" + hResult.ToString("X"));
}- Explicitly initializes COM on the main thread as STA
- Called at the beginning of
PreMessageLoop()before class factory registration - Handles both success cases (S_OK and S_FALSE)
NativeMethods.CoUninitialize();- Added at the end of
PostMessageLoop() - Properly uninitializes COM when the server shuts down
- Ensures clean resource cleanup
public class SimpleObject : StandardOleMarshalObject, ISimpleObject- Ensures standard COM marshaling for cross-apartment calls
- Important for proper proxy/stub generation
- Allows STA objects to be safely accessed from different apartment contexts
Added necessary constants:
COINIT_APARTMENTTHREADED = 0x2- STA initialization flagCOINIT_MULTITHREADED = 0x0- MTA initialization flagS_OK = 0- Success return valueS_FALSE = 1- COM already initializedRPC_E_CHANGED_MODE = 0x80010106- Different concurrency model error
- Application starts with
Main()marked as[STAThread] ExecutableComServer.Run()is calledPreMessageLoop()executes:- Calls
CoInitializeEx(COINIT_APARTMENTTHREADED)- Thread is now STA - Registers class factories with
CoRegisterClassObject() - Calls
CoResumeClassObjects()to allow activation
- Calls
RunMessageLoop()starts Windows message pump- When COM client calls
CoCreateInstance():- COM runtime invokes
SimpleObjectClassFactory.CreateInstance() - Factory creates
new SimpleObject()on the STA thread - Object inherits STA apartment state
- Client receives properly marshaled interface pointer
- COM runtime invokes
- Last COM object is released
- Lock count drops to zero
WM_QUITmessage posted to main thread- Message loop exits
PostMessageLoop()executes:- Revokes class factory registrations
- Cleans up resources
- Calls
CoUninitialize()to uninitialize COM
- Correct Apartment State: COM objects now correctly run in STA mode
- Cross-Apartment Marshaling:
StandardOleMarshalObjectensures proper marshaling - Thread Safety: STA serializes access to objects, preventing concurrent access issues
- UI Compatibility: STA mode allows safe use of UI components and STA-aware resources
- Client Compatibility: Works correctly with clients expecting STA behavior
- PowerShell Test: Run the included
CSExeCOMClient.ps1in STA mode - WinDbg: Attach debugger and inspect thread apartment state
- Process Monitor: Monitor COM activation and thread creation
- Custom Client: Create a test client that queries apartment state
- Main server thread should show STA apartment state
- Created COM objects should inherit STA state
- Cross-apartment calls should properly marshal
- No threading issues when accessing objects
- No security vulnerabilities introduced
- CodeQL analysis passed with 0 alerts
- Proper error handling for COM initialization failures
- Clean resource management with CoUninitialize
This change modifies the apartment threading model of the COM server. Considerations:
- Compatible: Clients that work with both STA and MTA servers
- Compatible: Clients explicitly expecting STA behavior
- May Break: Clients that explicitly depend on MTA behavior (rare)
- Best Practice: Document the apartment model in your COM server documentation
Most COM clients are apartment-agnostic or expect STA, making this change compatible with the vast majority of use cases.