Using CoInitializeEx on WinForms threads

2019-02-14 04:02发布

问题:

I am working an SDK for a DSLR camera which has the following instructions:

Notes on Developing Windows Applications When creating applications that run under Windows, a COM initialization is required for each thread in order to access a camera from a thread other than the main thread. To create a user thread and access the camera from that thread, be sure to execute CoInitializeEx( NULL, COINIT_APARTMENTTHREADED ) at the start of the thread and CoUnInitialize() at the end. Sample code is shown below. This is the same when controlling EdsVolumeRef or EdsDirectoryItemRef objects from another thread, not just with EdsCameraRef.

void TakePicture(EdsCameraRef camera)
{
    // Executed by another thread
    HANDLE hThread = (HANDLE)_beginthread(threadProc, 0, camera);
    // Block until finished
    ::WaitForSingleObject( hThread, INFINITE );
}

void threadProc(void* lParam)
{
    EdsCameraRef camera = (EdsCameraRef)lParam;
    CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
    EdsSendCommand(camera, kEdsCameraCommand_TakePicture, 0);
    CoUninitialize();
    _endthread();
}

My application is a C# WinForms app and normally, I use the managed thread class and Control.Invoke functions to avoid cross-thread issues.

Since I do not have sample source code in C# for consuming the SDK, my question is, is it useful and/or necessary to use CoInitializeEx in an app marked with the [STAThread] attribute?

I have not come across a scenario were I would need to have my app create a new apartment for threads so some insight would be helpful to understand threading models better.

UPDATE: After reading some more about apartments and COM, it is beginning to make some sense. Now I'm wondering what the .NET managed thread class defaults to and can we specify an apartment model in a managed way to each thread without P/Invoke?

回答1:

a COM initialization is required for each thread

Yes, rock-hard requirement. So much so that the CLR does this automatically without you having to help. Every .NET thread has CoInitializeEx() called before it starts running.

The CLR needs to know what argument to pass to CoInitializeEx(), selecting between STA and MTA. For the startup thread of your Winforms program it is determined by the [STAThread] attribute on your Main() method in Program.cs. It must be STA, a hard requirement for threads that display UI. For any thread that you start yourself it is determined by your call to Thread.SetApartmentState(), default is MTA. For any thread-pool thread, like the one used by BackgroundWorker or Task or QUWI, it is always MTA and cannot be changed. An automatic consequence of such a thread never being able properly support STA if it is used correctly.

Which is also what your code snippet is doing wrong, it is illegal to start an STA thread and not pump a message loop. You tend to get away with it by accident. Sometimes you do not and code will deadlock or fail in other ways, like not raising expected events. Since the vendor sanctioned it doing wrong, it probably does not matter here. But if you ever notice deadlock then you'd know where to look.

Long story short, you must not call CoInitializeEx() yourself, it was already done.