From: http://blog.csdn.net/zj510/article/details/38852619
前面一篇文章讲述了,STA客户环境下,跨线程传递COM对象的问题。那么在MTA环境下,是不是可以跨线程传递COM对象呢?
MTA客户跨线程传递COM对象(STA)
我们先改一下代码,变成了下面的样子:
- // TestCom.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- #include <atlbase.h>
- #include <thread>
- #include <vector>
- #include <windows.h>
- #include "../MyCom/MyCom_i.h"
- #include "../MyCom/MyCom_i.c"
- void Test(CComPtr<ICircle>& spCircle)
- {
- WCHAR temp[100] = { 0 };
- swprintf_s(temp, L"calling thread: %d\n", ::GetCurrentThreadId());
- OutputDebugStringW(temp);
- HRESULT hr = S_OK;
- // hr = CoInitialize(NULL);
- spCircle->Draw(CComBSTR(L"yellow"));
- // CoUninitialize();
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- CoInitializeEx(NULL, COINIT_MULTITHREADED);
- WCHAR temp[100] = { 0 };
- swprintf_s(temp, L"Main thread: %d\n", ::GetCurrentThreadId());
- OutputDebugStringW(temp);
- {
- CComPtr<ICircle> spCircle;
- spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC);
- spCircle->Draw(CComBSTR(L"red"));
- std::vector<std::thread> vThreads;
- for (int i = 0; i < 5; i++)
- {
- // LPSTREAM pStream = nullptr;
- // CoMarshalInterThreadInterfaceInStream(IID_ICircle, spCircle, &pStream); // marshal
- vThreads.push_back(std::thread(Test, spCircle)); // pass a stream instead of com object
- }
- /* MSG msg;
- while (GetMessage(&msg, NULL, 0, 0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }*/
- for (auto& t: vThreads)
- {
- t.join();
- }
- }
- CoUninitialize();
- return 0;
- }
// TestCom.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <atlbase.h>
#include <thread>
#include <vector>
#include <windows.h>
#include "../MyCom/MyCom_i.h"
#include "../MyCom/MyCom_i.c"
void Test(CComPtr<ICircle>& spCircle)
{
WCHAR temp[100] = { 0 };
swprintf_s(temp, L"calling thread: %d\n", ::GetCurrentThreadId());
OutputDebugStringW(temp);
HRESULT hr = S_OK;
// hr = CoInitialize(NULL);
spCircle->Draw(CComBSTR(L"yellow"));
// CoUninitialize();
}
int _tmain(int argc, _TCHAR* argv[])
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
WCHAR temp[100] = { 0 };
swprintf_s(temp, L"Main thread: %d\n", ::GetCurrentThreadId());
OutputDebugStringW(temp);
{
CComPtr<ICircle> spCircle;
spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC);
spCircle->Draw(CComBSTR(L"red"));
std::vector<std::thread> vThreads;
for (int i = 0; i < 5; i++)
{
// LPSTREAM pStream = nullptr;
// CoMarshalInterThreadInterfaceInStream(IID_ICircle, spCircle, &pStream); // marshal
vThreads.push_back(std::thread(Test, spCircle)); // pass a stream instead of com object
}
/* MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}*/
for (auto& t: vThreads)
{
t.join();
}
}
CoUninitialize();
return 0;
}
这个代码很短,主要就是:
1. 在主线程里面创建一个MTA环境,并且创建一个STA对象;
2. 开几个线程,并且把STA对象直接传给线程;
3. 线程里面并不创建套间,就是个普通线程;
4. 并不需要消息循环。
运行一下,发现:
1. MTA线程是2340(也就是创建STA对象的线程)
2. 辅助线程调用COM对象方法,这些方法都是在同一个线程里面运行的。既然是在同一个线程里面运行,那么肯定就是串行的了。
为什么会这样呢?之前STA客户调用的时候,还需要marshal和消息循环,为什么现在不需要了呢?
其实,再回顾一下这个图就行了。
MTA调用STA对象的时候,STA本来就在一个default STA里面运行的。MTA客户获得的不过是一个代理而已。
当MTA客户在调用STA对象的时候,本来就涉及到2个套间:MTA和default STA。
MTA套间里的线程直接调用STA对象,不需要做marshal,MTA里面的线程调用的是STA对象的代理,系统会把这些调用转发给default STA,default STA默认就有个消息循环了,这个是由COM系统创建的,不需要程序员干涉。
那如果我们在MTA客户里面创建一个STA对象,然后把这个对象传递给另外一个线程,而那个线程初始化成STA,又会发生什么呢?
MTA客户创建的STA对象传递给STA线程
如果我们直接把MTA创建的STA对象指针传递给STA线程,调用对象方法的时候,会得到一个错误:
RPC_E_WRONG_THREAD The application called an interface that was marshalled for a different thread.
查阅了一些资料,发现这篇文章:http://support.microsoft.com/kb/206076
里面有一段话:
If you create a COM object in one STA thread, you cannot pass an interface pointer to another STA thread and call out on that pointer. Since calls to STA objects are supposed to be serialized, COM enforces this by only allowing one thread to call into the STA object (the thread where it was created). If the interface pointer you pass to the second STA thread is a pointer to a proxy, you will get an error HRESULT of 0x8001010E or RPC_E_WRONG_THREAD (the application called an interface that was marshaled for a different thread). If the interface pointer is a direct pointer to the object, COM will not enforce serialization, you will not get the above error, and the interface method call will be made. However, this is still illegal behavior on the part of the client.
确实,跟我们看到的一样。
那么这个地方就碰到一个问题,MTA创建的STA对象存在于一个default STA中,而我们的STA线程如果直接访问COM对象的话,又涉及跨套间的问题了。所有就得mashal,在MTA线程里面,把STA对象(代理)mashal一下,再传给STA线程,会发现调用正常了。而且STA的运行线程就是default STA里面的那个线程。这样就可以实现串行调用了。
总结:
在MTA客户里面调用STA对象,程序员不需要做什么。对象传递到不同线程也没关系,还是可以正常运行(如果程序员显式把其他线程标记成STA,那么就得mashal,如果不标记,默认这些线程也属于MTA)。好像比STA客户调用STA对象还简单。
确实是这样,但是坏处也很明显,每次调用都是通过代理。而STA客户直接在套间里面调用的时候,是无需代理的,是直接调用,效率显然要高。
OK, 总之对于STA COM 对象,所有方法的调用总是串行的,不会并发,不需要考虑同步问题。
1. STA客户调用STA对象,是通过消息循环来保证串行的。就是通过往创建对象的套间发送消息,套间线程来接收消息。
2. MTA客户调用STA对象, COM系统会处理marshal,并且在default STA里面创建默认消息循环。
测试代码: http://download.csdn.net/detail/zj510/7823971