COM线程模型 - Part VI - COM服务端(STA组件)创建线程
2014-11-07 12:15
465 查看
From: http://blog.csdn.net/zj510/article/details/39290061
当我们在COM组件内部创建线程的时候,又是怎么样的一种情况呢?
这个地方跟组件的具体类型有关了,先来看看STA组件的情况。
STA组件内部创建线程
先看一段代码,CMyCarEx是一个STA组件,我们在Run()函数里面起了1000个线程。每个线程就会把m_nMiles加1,1000个线程运行完毕后通过连接点将英里数返回给调用者。
[cpp]
view plaincopyprint?
// MyCarEx.cpp : Implementation of CMyCarEx
#include "stdafx.h"
#include "MyCarEx.h"
#include <thread>
#include <vector>
// CMyCarEx
void CMyCarEx::WorkThread()
{
printf("CMyCarEx::WorkThread, begin, tid: %d\n", ::GetCurrentThreadId());
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++m_nMiles;
printf("CMyCarEx::WorkThread, end, tid: %d, miles: %d\n", ::GetCurrentThreadId(), m_nMiles);
}
STDMETHODIMP CMyCarEx::Run()
{
// TODO: Add your implementation code here
std::vector<std::thread> vThreads;
for (int i = 0; i < 1000; ++i)
{
std::thread t(&CMyCarEx::WorkThread,
this);
vThreads.push_back(std::move(t));
}
for (auto& t: vThreads)
{
t.join();
}
this->Fire_NeedMoreGas(m_nMiles);
return S_OK;
}
当客户端调用Run函数的时候,可以得到如下结果:
英里数只有999,正确结果应该是1000.如果再运行一次,可能是998了。问题就是1000个线程并发运行,导致结果出错。不是说STA COM对象是保证串行化的吗?为什么会错呢?
实际上,这个问题跟是这样的:
无论客户端是单线程环境还是多线程环境,STA对象的运行线程一定处于STA套间。(如果客户端是单线程环境,那么STA对象就在客户创建的线程里面运行,如果是多线程环境,那么就在default STA套间里面运行)。
Run()函数在一个STA套间线程里面运行,从上面的代码可以看到WorkThread线程本身并没有初始化COM,那么它就是一个普通线程,我们尝试在普通线程里面调用一个STA对象,上面的例子线程函数是成员函数,所以直接来操作数据成员了。这里就涉及到1个问题:
普通线程访问STA对象.这就会导致不可预测的后果。MSDN上写的很清楚
http://msdn.microsoft.com/en-us/library/windows/desktop/ms680112(v=vs.85).aspx
对于STA对象,每一个调用它的线程都必须初始化COM。那么我们需要把上面的线程初始化COM,这样就存在STA对象跨套间的问题了,一定需要marshal。
跨套间Marshal
我们需要把上面的代码改一下。首先为了避免线程函数直接访问COM对象数据的问题,我们把线程函数弄成普通函数。
然后再把COM对象marshal一下传递给线程,并且引入消息循环。代码如下:
[cpp]
view plaincopyprint?
// MyCarEx.cpp : Implementation of CMyCarEx
#include "stdafx.h"
#include "MyCarEx.h"
#include <thread>
#include <vector>
// CMyCarEx
void WorkThread(LPSTREAM pStream)
{
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
CComPtr<IMyCarEx> spCar;
HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_IMyCarEx, (LPVOID*)&spCar);
// unmarshal to get a com object
spCar->AddMile(1);
::CoUninitialize();
}
STDMETHODIMP CMyCarEx::Run()
{
// TODO: Add your implementation code here
std::vector<std::thread> vThreads;
for (int i = 0; i < 1000; ++i)
{
LPSTREAM pStream = nullptr;
IMyCarEx* car = nullptr;
QueryInterface(IID_IMyCarEx, (void**)&car);
CoMarshalInterThreadInterfaceInStream(IID_IMyCarEx, car, &pStream);
// marshal
car->Release();
std::thread t(WorkThread, pStream);
vThreads.push_back(std::move(t));
}
MSG msg;
int index = 0;
while (GetMessage(&msg, NULL, 0, 0))
{
index++;
printf("msg index: %d\n", index);
TranslateMessage(&msg);
DispatchMessage(&msg);
if (index >= 1000)
{
break;
}
}
for (auto& t: vThreads)
{
t.join();
}
this->Fire_NeedMoreGas(m_nMiles);
return S_OK;
}
STDMETHODIMP CMyCarEx::AddMile(LONG mile)
{
// TODO: Add your implementation code here
m_nMiles += mile;
return S_OK;
}
运行一下就可以得到如下结果:
这次就得到了正确结果。
OK, 其实在COM组件内部创建线程和客户端创建线程也是一样的。只要涉及到跨套间的问题,就一定需要marshal。要不然就会得到错误的结果。
当然,在COM组件内部创建线程的时候,有个可能性就是,客户端创建了MTA环境,那么如果组件内部线程不指定套间的话,这个线程默认就属于MTA套间。
完整客户端代码:
[cpp]
view plaincopyprint?
// ConsoleApplication4.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <thread>
#include <atlbase.h>
#include <atlcom.h>
#include <algorithm>
#include "../MyCom/MyCom_i.h"
#include "../MyCom/MyCom_i.c"
class CSink :
public CComObjectRoot,
public _IMyCarExEvents
{
BEGIN_COM_MAP(CSink)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(_IMyCarExEvents)
END_COM_MAP()
public:
virtual ~CSink(){
}
STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) {
return E_NOTIMPL; }
STDMETHODIMP GetTypeInfo(UINT iTInfo,
LCID lcid, ITypeInfo **ppTInfo) {
return E_NOTIMPL; }
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames,
UINT cNames, LCID lcid, DISPID *rgDispId) {
return E_NOTIMPL; }
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid,
LCID lcid,
WORD wFlags, DISPPARAMS *pDispParams,
VARIANT *pVarResult, EXCEPINFO *pExcepInfo,
UINT *puArgErr)
{
printf("sink, id: %d, parm: %d", dispIdMember, pDispParams->rgvarg[0].lVal);
return S_OK;
}
};
CComModule commodule;
int _tmain(int argc, _TCHAR* argv[])
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
{
CComPtr<IMyCarEx> spCar;
HRESULT hr = spCar.CoCreateInstance(CLSID_MyCarEx, NULL, CLSCTX_INPROC_SERVER);
CComObject<CSink>* sink = nullptr;
CComObject<CSink>::CreateInstance(&sink);
DWORD cookie = 0;
AtlAdvise(spCar, sink, __uuidof(_IMyCarExEvents), &cookie);
spCar->Run();
spCar.Release();
}
CoUninitialize();
return 0;
}
当我们在COM组件内部创建线程的时候,又是怎么样的一种情况呢?
这个地方跟组件的具体类型有关了,先来看看STA组件的情况。
STA组件内部创建线程
先看一段代码,CMyCarEx是一个STA组件,我们在Run()函数里面起了1000个线程。每个线程就会把m_nMiles加1,1000个线程运行完毕后通过连接点将英里数返回给调用者。
[cpp]
view plaincopyprint?
// MyCarEx.cpp : Implementation of CMyCarEx
#include "stdafx.h"
#include "MyCarEx.h"
#include <thread>
#include <vector>
// CMyCarEx
void CMyCarEx::WorkThread()
{
printf("CMyCarEx::WorkThread, begin, tid: %d\n", ::GetCurrentThreadId());
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++m_nMiles;
printf("CMyCarEx::WorkThread, end, tid: %d, miles: %d\n", ::GetCurrentThreadId(), m_nMiles);
}
STDMETHODIMP CMyCarEx::Run()
{
// TODO: Add your implementation code here
std::vector<std::thread> vThreads;
for (int i = 0; i < 1000; ++i)
{
std::thread t(&CMyCarEx::WorkThread,
this);
vThreads.push_back(std::move(t));
}
for (auto& t: vThreads)
{
t.join();
}
this->Fire_NeedMoreGas(m_nMiles);
return S_OK;
}
// MyCarEx.cpp : Implementation of CMyCarEx #include "stdafx.h" #include "MyCarEx.h" #include <thread> #include <vector> // CMyCarEx void CMyCarEx::WorkThread() { printf("CMyCarEx::WorkThread, begin, tid: %d\n", ::GetCurrentThreadId()); std::this_thread::sleep_for(std::chrono::milliseconds(10)); ++m_nMiles; printf("CMyCarEx::WorkThread, end, tid: %d, miles: %d\n", ::GetCurrentThreadId(), m_nMiles); } STDMETHODIMP CMyCarEx::Run() { // TODO: Add your implementation code here std::vector<std::thread> vThreads; for (int i = 0; i < 1000; ++i) { std::thread t(&CMyCarEx::WorkThread, this); vThreads.push_back(std::move(t)); } for (auto& t: vThreads) { t.join(); } this->Fire_NeedMoreGas(m_nMiles); return S_OK; }
当客户端调用Run函数的时候,可以得到如下结果:
英里数只有999,正确结果应该是1000.如果再运行一次,可能是998了。问题就是1000个线程并发运行,导致结果出错。不是说STA COM对象是保证串行化的吗?为什么会错呢?
实际上,这个问题跟是这样的:
无论客户端是单线程环境还是多线程环境,STA对象的运行线程一定处于STA套间。(如果客户端是单线程环境,那么STA对象就在客户创建的线程里面运行,如果是多线程环境,那么就在default STA套间里面运行)。
Run()函数在一个STA套间线程里面运行,从上面的代码可以看到WorkThread线程本身并没有初始化COM,那么它就是一个普通线程,我们尝试在普通线程里面调用一个STA对象,上面的例子线程函数是成员函数,所以直接来操作数据成员了。这里就涉及到1个问题:
普通线程访问STA对象.这就会导致不可预测的后果。MSDN上写的很清楚
http://msdn.microsoft.com/en-us/library/windows/desktop/ms680112(v=vs.85).aspx
对于STA对象,每一个调用它的线程都必须初始化COM。那么我们需要把上面的线程初始化COM,这样就存在STA对象跨套间的问题了,一定需要marshal。
跨套间Marshal
我们需要把上面的代码改一下。首先为了避免线程函数直接访问COM对象数据的问题,我们把线程函数弄成普通函数。
然后再把COM对象marshal一下传递给线程,并且引入消息循环。代码如下:
[cpp]
view plaincopyprint?
// MyCarEx.cpp : Implementation of CMyCarEx
#include "stdafx.h"
#include "MyCarEx.h"
#include <thread>
#include <vector>
// CMyCarEx
void WorkThread(LPSTREAM pStream)
{
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
CComPtr<IMyCarEx> spCar;
HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_IMyCarEx, (LPVOID*)&spCar);
// unmarshal to get a com object
spCar->AddMile(1);
::CoUninitialize();
}
STDMETHODIMP CMyCarEx::Run()
{
// TODO: Add your implementation code here
std::vector<std::thread> vThreads;
for (int i = 0; i < 1000; ++i)
{
LPSTREAM pStream = nullptr;
IMyCarEx* car = nullptr;
QueryInterface(IID_IMyCarEx, (void**)&car);
CoMarshalInterThreadInterfaceInStream(IID_IMyCarEx, car, &pStream);
// marshal
car->Release();
std::thread t(WorkThread, pStream);
vThreads.push_back(std::move(t));
}
MSG msg;
int index = 0;
while (GetMessage(&msg, NULL, 0, 0))
{
index++;
printf("msg index: %d\n", index);
TranslateMessage(&msg);
DispatchMessage(&msg);
if (index >= 1000)
{
break;
}
}
for (auto& t: vThreads)
{
t.join();
}
this->Fire_NeedMoreGas(m_nMiles);
return S_OK;
}
STDMETHODIMP CMyCarEx::AddMile(LONG mile)
{
// TODO: Add your implementation code here
m_nMiles += mile;
return S_OK;
}
// MyCarEx.cpp : Implementation of CMyCarEx #include "stdafx.h" #include "MyCarEx.h" #include <thread> #include <vector> // CMyCarEx void WorkThread(LPSTREAM pStream) { ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); std::this_thread::sleep_for(std::chrono::milliseconds(10)); CComPtr<IMyCarEx> spCar; HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_IMyCarEx, (LPVOID*)&spCar); // unmarshal to get a com object spCar->AddMile(1); ::CoUninitialize(); } STDMETHODIMP CMyCarEx::Run() { // TODO: Add your implementation code here std::vector<std::thread> vThreads; for (int i = 0; i < 1000; ++i) { LPSTREAM pStream = nullptr; IMyCarEx* car = nullptr; QueryInterface(IID_IMyCarEx, (void**)&car); CoMarshalInterThreadInterfaceInStream(IID_IMyCarEx, car, &pStream); // marshal car->Release(); std::thread t(WorkThread, pStream); vThreads.push_back(std::move(t)); } MSG msg; int index = 0; while (GetMessage(&msg, NULL, 0, 0)) { index++; printf("msg index: %d\n", index); TranslateMessage(&msg); DispatchMessage(&msg); if (index >= 1000) { break; } } for (auto& t: vThreads) { t.join(); } this->Fire_NeedMoreGas(m_nMiles); return S_OK; } STDMETHODIMP CMyCarEx::AddMile(LONG mile) { // TODO: Add your implementation code here m_nMiles += mile; return S_OK; }
运行一下就可以得到如下结果:
这次就得到了正确结果。
OK, 其实在COM组件内部创建线程和客户端创建线程也是一样的。只要涉及到跨套间的问题,就一定需要marshal。要不然就会得到错误的结果。
当然,在COM组件内部创建线程的时候,有个可能性就是,客户端创建了MTA环境,那么如果组件内部线程不指定套间的话,这个线程默认就属于MTA套间。
完整客户端代码:
[cpp]
view plaincopyprint?
// ConsoleApplication4.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <thread>
#include <atlbase.h>
#include <atlcom.h>
#include <algorithm>
#include "../MyCom/MyCom_i.h"
#include "../MyCom/MyCom_i.c"
class CSink :
public CComObjectRoot,
public _IMyCarExEvents
{
BEGIN_COM_MAP(CSink)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(_IMyCarExEvents)
END_COM_MAP()
public:
virtual ~CSink(){
}
STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) {
return E_NOTIMPL; }
STDMETHODIMP GetTypeInfo(UINT iTInfo,
LCID lcid, ITypeInfo **ppTInfo) {
return E_NOTIMPL; }
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames,
UINT cNames, LCID lcid, DISPID *rgDispId) {
return E_NOTIMPL; }
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid,
LCID lcid,
WORD wFlags, DISPPARAMS *pDispParams,
VARIANT *pVarResult, EXCEPINFO *pExcepInfo,
UINT *puArgErr)
{
printf("sink, id: %d, parm: %d", dispIdMember, pDispParams->rgvarg[0].lVal);
return S_OK;
}
};
CComModule commodule;
int _tmain(int argc, _TCHAR* argv[])
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
{
CComPtr<IMyCarEx> spCar;
HRESULT hr = spCar.CoCreateInstance(CLSID_MyCarEx, NULL, CLSCTX_INPROC_SERVER);
CComObject<CSink>* sink = nullptr;
CComObject<CSink>::CreateInstance(&sink);
DWORD cookie = 0;
AtlAdvise(spCar, sink, __uuidof(_IMyCarExEvents), &cookie);
spCar->Run();
spCar.Release();
}
CoUninitialize();
return 0;
}
// ConsoleApplication4.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <thread> #include <atlbase.h> #include <atlcom.h> #include <algorithm> #include "../MyCom/MyCom_i.h" #include "../MyCom/MyCom_i.c" class CSink : public CComObjectRoot, public _IMyCarExEvents { BEGIN_COM_MAP(CSink) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(_IMyCarExEvents) END_COM_MAP() public: virtual ~CSink(){ } STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; } STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; } STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { return E_NOTIMPL; } STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { printf("sink, id: %d, parm: %d", dispIdMember, pDispParams->rgvarg[0].lVal); return S_OK; } }; CComModule commodule; int _tmain(int argc, _TCHAR* argv[]) { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); { CComPtr<IMyCarEx> spCar; HRESULT hr = spCar.CoCreateInstance(CLSID_MyCarEx, NULL, CLSCTX_INPROC_SERVER); CComObject<CSink>* sink = nullptr; CComObject<CSink>::CreateInstance(&sink); DWORD cookie = 0; AtlAdvise(spCar, sink, __uuidof(_IMyCarExEvents), &cookie); spCar->Run(); spCar.Release(); } CoUninitialize(); return 0; }
相关文章推荐
- COM线程模型 - COM服务端(STA组件)创建线程
- COM线程模型 - MTA接口 (传递MTA COM对象给STA套间线程)
- COM线程模型 - STA接口 (MTA客户,跨线程传递COM对象)
- COM线程模型 - MTA接口 (STA套间调用MTA对象)
- COM线程模型 - STA接口 (跨线程传递对象,消息循环)
- COM线程模型 - STA - Part I
- COM线程模型 - STA接口 - Part III (MTA客户,跨线程传递COM对象)
- COM线程模型 - STA接口 - Part II -(跨线程传递对象,消息循环)
- COM线程模型 - MTA接口 - Part III -(STA套间调用MTA对象)
- COM线程模型 - MTA接口 - Part II - (传递MTA COM对象给STA套间线程)
- COM线程模型 - STA接口
- 再论COM的线程模型
- 泛说"COM线程模型"
- 接触VC之四:COM组件模型基础
- COM线程模型-套间
- COM笔记-组件的创建和类厂
- 泛说"COM线程模型"
- COM线程模型
- 理解套间(涉及进程、线程、COM线程模型)(转载)
- COM笔记-组件的创建和类厂