在WinForms线程上使用CoInitializeEx

5

我正在为一款DSLR相机开发SDK,该相机需要以下操作指南:

开发Windows应用程序时,请注意对每个线程进行COM初始化以便从除主线程以外的线程访问相机。要创建用户线程并从该线程访问相机,请确保在线程开始时执行CoInitializeEx(NULL, COINIT_APARTMENTTHREADED),并在结束时执行CoUnInitialize()。下面是示例代码。这也适用于从其他线程控制EdsVolumeRef或EdsDirectoryItemRef对象,而不仅仅是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();
}

我的应用程序是一个C# WinForms应用程序,通常情况下,我使用托管的线程类和Control.Invoke函数来避免跨线程问题。
由于我没有C#的SDK示例源代码,我的问题是,在标记有[STAThread]属性的应用程序中使用CoInitializeEx是否有用或必要?
我还没有遇到需要让我的应用程序为线程创建新公寓的场景,因此了解线程模型的更多信息将很有帮助。
更新:在阅读了一些关于公寓和COM的文章之后,它开始变得有点明朗起来。现在我想知道.NET托管线程类的默认值是什么,我们能否以托管方式为每个线程指定公寓模型而不需要P/Invoke?

是的,正如Hans在下面的回答中所说,您需要在启动线程之前调用Thread.SetApartmentState。 - Eric Brown
1个回答

6
每个线程都需要进行COM初始化,这是一项硬性要求。CLR会自动帮你完成此操作,无需手动干预。.NET线程在启动运行之前都会调用CoInitializeEx()。
CLR需要知道传递给CoInitializeEx()的参数,即单线程单元(STA)和多线程单元(MTA)之间的选择。对于Winforms程序的启动线程,由Program.cs中Main()方法上的[STAThread]属性来确定。必须是STA,这是显示UI的线程的硬性要求。对于任何您自己启动的线程,它由Thread.SetApartmentState()调用来确定,默认为MTA。对于任何线程池线程,例如BackgroundWorker或Task或QUWI使用的线程,始终是MTA且不能更改。这样的线程永远不能正确支持STA,如果正确使用,则会自动产生后果。
这也是你的代码片段错误的原因,启动STA线程但不使用消息循环是非法的。您可能会偶然得逞,但有时不会,代码将死锁或以其他方式失败,例如未引发预期事件。由于供应商已经认可了错误的操作,因此这可能并不重要。但是,如果您注意到死锁,则会知道应该在哪里查找。
长话短说,您不应该自己调用CoInitializeEx(),因为已经完成了这项操作。

如果我创建了一个托管的 Thread 并调用 ThreadInstance.SetApartmentState(ApartmentState.STA),那么这是否会处理消息循环?或者你的意思是除了等待死锁并向供应商寻求支持外,我没有太多可以做的事情? - Raheel Khan
2
不,你必须自己处理它。Application.Run()。这就是STA的全部意义,你只需要承诺会做对,COM就相信你的话。供应商的代码片段说,如果你不调用它,它将意外地工作,没有太多理由认为它不会。嗯,只要你不太偏离示例代码。一个功能STA线程的代码示例在这里。 - Hans Passant
2
如果线程唯一要做的事情就是调用第三方API EdsSendCommand,并且这似乎是一个正常的阻塞调用,那么在该调用期间没有机会进行消息泵,假设该API快速返回,则可能没有问题。另一方面,我曾经看到过一些奇怪的问题,例如当用户尝试打开Word文档时,Windows shell挂起,显然是由于不进行泵送的不同进程中的非泵送COM线程引起的。对于一个真正短暂的线程来说,这可能没问题。(但那么为什么要将其作为单独的线程呢?) - Daniel Earwicker
@DanielEarwicker:大多数调用使用EdsSendCommand确实会非常短暂,而少数将是长时间运行的线程,显示相机的实时视图。感谢您在崩溃方面的经验提醒。如果我遇到任何问题,我会回到这个问题。 - Raheel Khan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接