COM线程模型 - STA接口 - Part II -(跨线程传递对象,消息循环)

From : http://blog.csdn.net/zj510/article/details/38829069

 

前面一篇文章讲述了STA客户调用STA对象和MTA客户调用STA对象,其实并不难理解。

现在就来讲一下如何把一个COM对象传递到另外一个线程。

先来看看STA套间里面创建STA对象,并且传递到另外一个线程的情况。

STA客户创建STA对象,然后传递到另外一个线程

我们先改一下代码,这段代码很简单,只是修改上个文章里面的客户代码。其实就是主线程创建一个COM对象,然后传递到线程里面,起5个线程。

  1. // TestCom.cpp : Defines the entry point for the console application. 
  2. // 
  3.  
  4. #include "stdafx.h" 
  5.  
  6. #include <atlbase.h> 
  7. #include <thread> 
  8. #include <vector> 
  9.  
  10. #include "../MyCom/MyCom_i.h" 
  11. #include "../MyCom/MyCom_i.c" 
  12.  
  13.  
  14. void Test(CComPtr<ICircle>& spCircle) 
  15.     WCHAR temp[100] = { 0 }; 
  16.     swprintf_s(temp, L"STA calling thread (used passed in com object): %d\n", ::GetCurrentThreadId()); 
  17.     OutputDebugStringW(temp); 
  18.  
  19.     CoInitialize(NULL); 
  20.  
  21.     spCircle->Draw(CComBSTR(L"yellow")); 
  22.  
  23.     CoUninitialize(); 
  24.  
  25. int _tmain(int argc, _TCHAR* argv[]) 
  26.     CoInitialize(NULL); 
  27.     WCHAR temp[100] = { 0 }; 
  28.     swprintf_s(temp, L"Main thread: %d\n", ::GetCurrentThreadId()); 
  29.     OutputDebugStringW(temp); 
  30.  
  31.     { 
  32.         CComPtr<ICircle> spCircle; 
  33.         spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC); 
  34.  
  35.         spCircle->Draw(CComBSTR(L"red")); 
  36.  
  37.         std::vector<std::thread> vThreads; 
  38.         for (int i = 0; i < 5; i++) 
  39.         { 
  40.             vThreads.push_back(std::thread(Test, spCircle)); 
  41.         } 
  42.  
  43.         for (auto& t: vThreads) 
  44.         { 
  45.             t.join(); 
  46.         } 
  47.     } 
  48.  
  49.  
  50.     CoUninitialize(); 
  51.  
  52.     return 0; 
// TestCom.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <atlbase.h>
#include <thread>
#include <vector>

#include "../MyCom/MyCom_i.h"
#include "../MyCom/MyCom_i.c"


void Test(CComPtr<ICircle>& spCircle)
{
	WCHAR temp[100] = { 0 };
	swprintf_s(temp, L"STA calling thread (used passed in com object): %d\n", ::GetCurrentThreadId());
	OutputDebugStringW(temp);

	CoInitialize(NULL);

	spCircle->Draw(CComBSTR(L"yellow"));

	CoUninitialize();
}

int _tmain(int argc, _TCHAR* argv[])
{
	CoInitialize(NULL);
	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++)
		{
			vThreads.push_back(std::thread(Test, spCircle));
		}

		for (auto& t: vThreads)
		{
			t.join();
		}
	}


	CoUninitialize();

	return 0;
}

运行了一下,得到:


好像每一次COM调用都是在客户线程里面。其实我也不知道为什么会这样,但是我觉得这是不对的,这样做的后果是不可预测的。而且根据STA的定义,STA对象应该是串行化的执行同一个对象的方法。为此,我又特地在COM函数里面sleep一下,如:

  1. STDMETHODIMP CCircle::Draw(BSTR color) 
  2.     // TODO: Add your implementation code here 
  3.     WCHAR temp[100] = { 0 }; 
  4.     swprintf_s(temp, L"ICircle::Draw, color: %s, tid: %d\n", color, ::GetCurrentThreadId()); 
  5.     OutputDebugStringW(temp); 
  6.  
  7.     std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
  8.  
  9.     OutputDebugStringW(L"ICircle::Draw, end\n"); 
  10.     return S_OK; 
STDMETHODIMP CCircle::Draw(BSTR color)
{
	// TODO: Add your implementation code here
	WCHAR temp[100] = { 0 };
	swprintf_s(temp, L"ICircle::Draw, color: %s, tid: %d\n", color, ::GetCurrentThreadId());
	OutputDebugStringW(temp);

	std::this_thread::sleep_for(std::chrono::milliseconds(100));

	OutputDebugStringW(L"ICircle::Draw, end\n");
	return S_OK;
}

运行结果是:

好像是并发的,根本就不是串行的,这个根本不符合STA的定义啊。为什么?

其实我们这么调,根本就是错的。如果回顾一下前面的文章,里面有几条规则

1. STA对象只能存在于一个线程中;

2. 如果要跨线程传递COM对象的话,一定要做Marshal

3. 一个STA对象只接受来自创建它的STA线程的调用。

等等。

上面的做法在主线程创建了一个STA对象,然后直接把它传递了其他线程,这根本不符合STA接口的规则,不知道会发生什么事,而且调用也不是线性的。

那么正确的做法应该是怎么样的呢?

Marshal和Unmarshal

通常中文翻译成列集和散集,我还是习惯叫做Marshal和unmarshal。

好,我们现在不直接com指针给线程了,我们先marshal,然后在线程函数里面unmarsha,如:

  1. // TestCom.cpp : Defines the entry point for the console application. 
  2. // 
  3.  
  4. #include "stdafx.h" 
  5.  
  6. #include <atlbase.h> 
  7. #include <thread> 
  8. #include <vector> 
  9.  
  10. #include "../MyCom/MyCom_i.h" 
  11. #include "../MyCom/MyCom_i.c" 
  12.  
  13.  
  14. void Test(LPSTREAM pStream) 
  15.     WCHAR temp[100] = { 0 }; 
  16.     swprintf_s(temp, L"STA calling thread (used passed in com object): %d\n", ::GetCurrentThreadId()); 
  17.     OutputDebugStringW(temp); 
  18.  
  19.     CoInitialize(NULL); 
  20.  
  21.     CComPtr<ICircle> spCircle; 
  22.     HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_ICircle, (LPVOID*)&spCircle);  // unmarshal to get a com object 
  23.     if (SUCCEEDED(hr)) 
  24.     { 
  25.         spCircle->Draw(CComBSTR(L"yellow")); 
  26.     } 
  27.      
  28.  
  29.     CoUninitialize(); 
  30.  
  31. int _tmain(int argc, _TCHAR* argv[]) 
  32.     CoInitialize(NULL); 
  33.     WCHAR temp[100] = { 0 }; 
  34.     swprintf_s(temp, L"Main thread: %d\n", ::GetCurrentThreadId()); 
  35.     OutputDebugStringW(temp); 
  36.  
  37.     { 
  38.         CComPtr<ICircle> spCircle; 
  39.         spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC); 
  40.  
  41.         spCircle->Draw(CComBSTR(L"red")); 
  42.  
  43.         std::vector<std::thread> vThreads; 
  44.         for (int i = 0; i < 5; i++) 
  45.         { 
  46.             LPSTREAM pStream = nullptr; 
  47.             CoMarshalInterThreadInterfaceInStream(IID_ICircle, spCircle, &pStream);  // marshal 
  48.  
  49.             vThreads.push_back(std::thread(Test, pStream));  // pass a stream instead of com object 
  50.         } 
  51.  
  52.         for (auto& t: vThreads) 
  53.         { 
  54.             t.join(); 
  55.         } 
  56.     } 
  57.  
  58.  
  59.     CoUninitialize(); 
  60.  
  61.     return 0; 
// TestCom.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <atlbase.h>
#include <thread>
#include <vector>

#include "../MyCom/MyCom_i.h"
#include "../MyCom/MyCom_i.c"


void Test(LPSTREAM pStream)
{
	WCHAR temp[100] = { 0 };
	swprintf_s(temp, L"STA calling thread (used passed in com object): %d\n", ::GetCurrentThreadId());
	OutputDebugStringW(temp);

	CoInitialize(NULL);

	CComPtr<ICircle> spCircle;
	HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_ICircle, (LPVOID*)&spCircle);  // unmarshal to get a com object
	if (SUCCEEDED(hr))
	{
		spCircle->Draw(CComBSTR(L"yellow"));
	}
	

	CoUninitialize();
}

int _tmain(int argc, _TCHAR* argv[])
{
	CoInitialize(NULL);
	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, pStream));  // pass a stream instead of com object
		}

		for (auto& t: vThreads)
		{
			t.join();
		}
	}


	CoUninitialize();

	return 0;
}


就这么运行下,完蛋了,除了主线程的那个Draw调用成功,其他辅助线程里面的调用,Draw函数都出不来了。这又是为什么?

现在就该消息循环出场了。之前有讲过,如果COM对象不需要传递到其他线程的话,那么其实不需要消息循环,但是如果需要传递到其他线程的话,就一定要创建一个消息循环。

消息循环

这里讲的消息循环,其实就是Windows的消息循环。当其他线程调用COM对象的函数的时候(通过列集,散列),其实COM系统就是往创建COM对象的线程发送windows消息。因有关消息循环可以参考这篇文章里面的描述,相对到位。http://www.codeproject.com/Articles/9190/Understanding-The-COM-Single-Threaded-Apartment-Pa

我这里截取了一部分,但是我觉得大家有兴趣应该仔细阅读。

反正,STA对象的线性调用其实就是通过Windows的消息循环来实现的。

ok,现在我们来尝试给我们的主调STA客户加上消息循环。代码也很简单,直接贴:

  1. // TestCom.cpp : Defines the entry point for the console application. 
  2. // 
  3.  
  4. #include "stdafx.h" 
  5.  
  6. #include <atlbase.h> 
  7. #include <thread> 
  8. #include <vector> 
  9. #include <windows.h> 
  10.  
  11. #include "../MyCom/MyCom_i.h" 
  12. #include "../MyCom/MyCom_i.c" 
  13.  
  14. LRESULT CALLBACK WndProc_Notify(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) 
  15.  
  16.     return DefWindowProc(hWnd, wMsg, wParam, lParam); 
  17.  
  18. void CreateWnd(void
  19.  
  20.     WNDCLASS wc = { 0 }; 
  21.     wc.style = 0; 
  22.     wc.lpfnWndProc = WndProc_Notify; 
  23.     wc.cbClsExtra = 0; 
  24.     wc.cbWndExtra = 0; 
  25.     //    wc.hInstance = g_hInstance; 
  26.     wc.hIcon = NULL; 
  27.     wc.hCursor = LoadCursor(NULL, IDC_ARROW); 
  28.     wc.hbrBackground = (HBRUSH)GetSysColorBrush(COLOR_WINDOW); 
  29.     wc.lpszMenuName = NULL; 
  30.     wc.lpszClassName = TEXT("NOTIFY_MSG_LOOP"); 
  31.  
  32.     RegisterClass(&wc); 
  33.  
  34.     HWND g_hNotifyMsgLoop = CreateWindowExW(0, 
  35.         wc.lpszClassName, 
  36.         wc.lpszClassName, 
  37.         WS_OVERLAPPEDWINDOW, 
  38.         0, 
  39.         0, 
  40.         200, 
  41.         200, 
  42.         NULL, 
  43.         NULL, 
  44.         NULL, 
  45.         0); 
  46.  
  47.     //  ShowWindow(g_hNotifyMsgLoop, SW_HIDE); 
  48.  
  49.  
  50. void Test(LPSTREAM pStream) 
  51.     CreateWnd(); 
  52.  
  53.     WCHAR temp[100] = { 0 }; 
  54.     swprintf_s(temp, L"STA calling thread (used passed in com object): %d\n", ::GetCurrentThreadId()); 
  55.     OutputDebugStringW(temp); 
  56.  
  57.     CoInitialize(NULL); 
  58.  
  59.     CComPtr<ICircle> spCircle; 
  60.     HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_ICircle, (LPVOID*)&spCircle);  // unmarshal to get a com object 
  61.     if (SUCCEEDED(hr)) 
  62.     { 
  63.         spCircle->Draw(CComBSTR(L"yellow")); 
  64.     } 
  65.      
  66.  
  67.     CoUninitialize(); 
  68.  
  69. int _tmain(int argc, _TCHAR* argv[]) 
  70.     CoInitialize(NULL); 
  71.     WCHAR temp[100] = { 0 }; 
  72.     swprintf_s(temp, L"Main thread: %d\n", ::GetCurrentThreadId()); 
  73.     OutputDebugStringW(temp); 
  74.  
  75.     { 
  76.         CComPtr<ICircle> spCircle; 
  77.         spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC); 
  78.  
  79.         spCircle->Draw(CComBSTR(L"red")); 
  80.  
  81.         std::vector<std::thread> vThreads; 
  82.         for (int i = 0; i < 5; i++) 
  83.         { 
  84.             LPSTREAM pStream = nullptr; 
  85.             CoMarshalInterThreadInterfaceInStream(IID_ICircle, spCircle, &pStream);  // marshal 
  86.  
  87.             vThreads.push_back(std::thread(Test, pStream));  // pass a stream instead of com object 
  88.         } 
  89.  
  90.         MSG msg; 
  91.         while (GetMessage(&msg, NULL, 0, 0)) 
  92.         { 
  93.             TranslateMessage(&msg); 
  94.             DispatchMessage(&msg); 
  95.         } 
  96.  
  97.         for (auto& t: vThreads) 
  98.         { 
  99.             t.join(); 
  100.         } 
  101.     } 
  102.  
  103.  
  104.     CoUninitialize(); 
  105.  
  106.     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"

LRESULT CALLBACK WndProc_Notify(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{

	return DefWindowProc(hWnd, wMsg, wParam, lParam);
}

void CreateWnd(void)
{

	WNDCLASS wc = { 0 };
	wc.style = 0;
	wc.lpfnWndProc = WndProc_Notify;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	//    wc.hInstance = g_hInstance;
	wc.hIcon = NULL;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetSysColorBrush(COLOR_WINDOW);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = TEXT("NOTIFY_MSG_LOOP");

	RegisterClass(&wc);

	HWND g_hNotifyMsgLoop = CreateWindowExW(0,
		wc.lpszClassName,
		wc.lpszClassName,
		WS_OVERLAPPEDWINDOW,
		0,
		0,
		200,
		200,
		NULL,
		NULL,
		NULL,
		0);

	//	ShowWindow(g_hNotifyMsgLoop, SW_HIDE);
}


void Test(LPSTREAM pStream)
{
	CreateWnd();

	WCHAR temp[100] = { 0 };
	swprintf_s(temp, L"STA calling thread (used passed in com object): %d\n", ::GetCurrentThreadId());
	OutputDebugStringW(temp);

	CoInitialize(NULL);

	CComPtr<ICircle> spCircle;
	HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_ICircle, (LPVOID*)&spCircle);  // unmarshal to get a com object
	if (SUCCEEDED(hr))
	{
		spCircle->Draw(CComBSTR(L"yellow"));
	}
	

	CoUninitialize();
}

int _tmain(int argc, _TCHAR* argv[])
{
	CoInitialize(NULL);
	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, pStream));  // 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;
}

就这样,给创建STA对象的线程增加了一个消息循环。现在运行一下,发现:

这次,我们看到每个Draw函数都是串行运行的,draw函数里面的sleep()前后的2个log没有被打乱。而且运行线程都是1376,也就是创建STA对象的线程。这才符合STA要求。

现在我们知道了,如果想要把一个STA对象往另外一个线程传递,就需要:

1. 列集/散列, (marshal/unmarshal)

2. 创建STA对象的线程一定要有个windows消息循环。

至于第二点,看一下msdn上的描述把。反正大概的意思就是,COM系统发现有其他线程(套间)调用COM对象的方法,COM系统就会往创建COM对象的线程发送消息,但是如果那个线程没有消息循环来接收,那就永远都处理不了了。

处理消息循环的时候还有很多细节问题,这个可以查看其他资料。反正真正需要用到消息循环的时候再研究一下。

这里还有另外一种情况,就是MTA客户调用STA对象,然后又想把STA对象传递到其他线程,这又是什么情况呢?下次再研究了...

测试代码:http://download.csdn.net/detail/zj510/7818731

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值