12.编写COM进程外组件

来源:互联网 发布:java 合并多个excel 编辑:程序博客网 时间:2024/05/14 11:26

相比进程内组件,进程外组件的编写较为麻烦,在前面已经讲的IDL和进程外组件原理基础上,本节以一个简单实例讲解进程外组件的编写步骤和注意事项。


1.IDL生成代理/存根

假如我们需要实现一个猫猫翻译的接口,传入人类语言,然后猫猫翻译出指定的喵星语

则编写IDL如下

import "Unknwn.Idl";#define MAX_STR_LEN 256[    object,     uuid(3EA29209-B0B2-49c2-BF47-A68DF3CA1A2C),    pointer_default(unique)] interface ICat : IUnknown{   HRESULT TanslateWord([in, string] WCHAR *pWord, [out] WCHAR pResult[MAX_STR_LEN]);HRESULT TanslateWord2([in, string] BSTR pWord, [out, string, retval] BSTR* pResult);};

这里采用两种字符串,WCHAR都是调用方分配内存,BSTR在组件中分配内存在客户端释放,这个后续再说明。


这里采用VS2008,直接建立一个工程,添加IDL文件,

工程配置如下

编译选项添加REGISTER_PROXY_DLL,以支持自注册


链接选项引入需要的lib


注意链接选项中IDL的相关设置,一般采用默认如下


然后编译,会自动生成相关文件,手动添加这些生成的文件到工程中,如下:


cat_h.h中包含了接口定义,cat_i.c包含了接口IID,cat_p.c为列集数据,dlldata.c为标准com导出函数实现,我们主要关注前两个文件

可以看到生成的接口定义如下

    MIDL_INTERFACE("3EA29209-B0B2-49c2-BF47-A68DF3CA1A2C")    ICat : public IUnknown    {    public:        virtual HRESULT STDMETHODCALLTYPE TanslateWord(             /* [string][in] */ WCHAR *pWord,            /* [out] */ WCHAR pResult[ 256 ]) = 0;                virtual HRESULT STDMETHODCALLTYPE TanslateWord2(             /* [string][in] */ BSTR pWord,            /* [retval][string][out] */ BSTR *pResult) = 0;            };
符合我们的要求

此时编译工程,就可以得到接口的代理/存根 dll,然后运行regsvr32.exe注册即可。

查看注册表,可以看到接口指向了代理程序的位置



注意,本文主要是为了讲清楚进程外组件的构成,在实际ATL工程中,上述过程已经自动完成,只需要参考本文编写IDL和进程外组件实现代码即可。


2.组件程序实现代码

进程外组件是exe,有了上面生成的接口定义,和进程内组件一样,直接集成实现即可,如下

CCat::CCat(){m_Ref = 0;g_CatNumber++;}CCat::~CCat(){g_CatNumber --;if (CCatFactory::CanUnloadNow()) {::PostThreadMessage(g_dwMainThreadID, WM_QUIT, 0, 0) ;}}HRESULT __stdcall CCat::QueryInterface( const IID& iid, void **ppv ){if ( iid == IID_IUnknown ){*ppv = (ICat *) this ;((ICat *)(*ppv))->AddRef() ;} else if ( iid == IID_Cat ) {*ppv = (ICat *) this ;((ICat *)(*ppv))->AddRef() ;}else{*ppv = NULL;return E_NOINTERFACE ;}return S_OK;}ULONG__stdcall CCat::AddRef(){m_Ref ++;return  (ULONG) m_Ref;}ULONG__stdcall CCat::Release(){m_Ref --;if (m_Ref == 0 ) {delete this;return 0;}return  (ULONG) m_Ref;}HRESULT __stdcall CCat::TanslateWord( WCHAR *pWord, WCHAR pResult[ 256 ] ){if (NULL==pWord){return E_FAIL;}std::map<std::wstring, std::wstring> mapDict;mapDict.insert(std::make_pair(L"hungry", L"miao"));mapDict.insert(std::make_pair(L"thirty", L"miao~~miao"));mapDict.insert(std::make_pair(L"sleep", L"miao~~~miao~~~miao"));std::map<std::wstring, std::wstring>::iterator iTer = mapDict.find(pWord);if (iTer!=mapDict.end()){ZeroMemory(pResult, 256*sizeof(WCHAR));_tcscpy_s(pResult, 256, iTer->second.c_str());return S_OK;}else{return E_FAIL;}}HRESULT __stdcall CCat::TanslateWord2( BSTR pWord, BSTR *pResult ){CString strWord = pWord;WCHAR szRes[256] = {0};OutputDebugString(strWord.GetBuffer(0));HRESULT hr = TanslateWord(strWord.GetBuffer(0), szRes);if (SUCCEEDED(hr)){*pResult = ::SysAllocString(szRes);}return hr;}
标准的IUnknown实现都和之前一样,没什么差别,翻译的过程采用查表的方式,查询成功返回S_OK,失败返回E_FAIL

注意这里的BSTR方式传递的参数,需要使用SysAllocString分配内存,来保证跨进程传递。同理如果是其他指针数据,需要组件分配的内存给客户使用的话,要使用CoTaskMem...系列函数来保证跨进程分配释放内存


再就是组件外程序为了保证接口释放前,组件生命周期一直在,采用的消息队列循环,这里组件对象释放时,需要检查当前整个dll能否被释放,总的开关是控制在组件的工厂对象手中,这个稍后讲解。


3.组件程序注册代码

和进程内dll组件自注册机制不同,进程外组件需要自己识别进程命令行参数来注册

如下

//校验输入命令行WCHAR szTokens[] = L"-/" ;WCHAR* szToken = _tcstok(lpCmdLine, szTokens) ; while (szToken != NULL){if (_wcsicmp(szToken, L"RegServer") == 0){char szModule[1024];DWORD dwResult = ::GetModuleFileNameA((HMODULE)hInstance, szModule, 1024);if (dwResult == 0)retVal = SELFREG_E_CLASS;retVal = RegisterServer(CLSID_Cat,szModule, "Cat.Object","Cat Component",NULL);// We are done, so exit.bExit = TRUE ;}else if (_wcsicmp(szToken, L"UnregServer") == 0){retVal = UnregisterServer(CLSID_Cat,"Cat.Object",NULL);// We are done, so exit.bExit = TRUE ;}else if (_wcsicmp(szToken, L"Embedding") == 0){bExit = FALSE;break ;}szToken = _tcstok(NULL, szTokens) ;}

进程参数为RegServer和UnregServer时,代表注册和反注册,参数为Embedding时表示是客户调用组件。注册时需要手动输入命令行,客户调用组件时会自动加上Embedding参数。

对应的注册表如下



4.组件类厂实现

标准实现否和进程内组件一样,实现IUnkown接口,CreateInstance中创建返回指定接口指针。

不同的是:


a.类厂注册

进程外组件调用CreateInstance中CoGetClassObject中,会连接请求工厂对象,此时组件中需要将当前工厂对象指针注册到COM中,COM会将指针列集传送给客户,客户散集后创建对应的类厂对象代理,建立类厂代理/存根连接后,才进行下一步创建接口操作

进程外组件退出时需要反注册。

对应代码如下:

//// Register factory//BOOL CCatFactory::RegisterFactory(){// Create the class factory for dictionary component.theFactory = new CCatFactory();theFactory->AddRef();IUnknown *pUnkForFactory = (IUnknown  *)theFactory;// Register the class factory.HRESULT hr = ::CoRegisterClassObject(              CLSID_Cat,              pUnkForFactory,              CLSCTX_LOCAL_SERVER,              REGCLS_MULTIPLEUSE,              // REGCLS_MULTI_SEPARATE, //@Multi              &dwRegister) ;if (FAILED(hr)){theFactory->Release() ;return FALSE ;}return TRUE ;}//// Unregister factories//void CCatFactory::UnregisterFactory(){if (dwRegister != 0) {::CoRevokeClassObject(dwRegister) ;}// Release the class factory.if (theFactory != NULL)theFactory->Release();}


实际注册和反注册代码如下:

//以Embedding参数调起进程是才是远程调用请求到,此时注册类厂//然会进行消息循环,直到组件对象销毁发WM_QUIT消息退出if (!bExit){OutputDebugString(L"Run in CatCom.exe\n");// 注册类厂CCatFactory::RegisterFactory();// Wait for shutdown.MSG msg ;while (::GetMessage(&msg, 0, 0, 0)){::DispatchMessage(&msg);}// 反注册类厂CCatFactory::UnregisterFactory() ;}


b.类厂控制生命周期

进程外组件的生命周期和组件对象及类厂锁相关,只有同时满足才能退出进程外组件进程,LockServer在这里起到很大作用。实现如下

BOOL CCatFactory::CanUnloadNow(){if (g_LockNumber>0 || g_CatNumber >0)return FALSE;else return TRUE;}


5.进程外组件调用

COM中进程内和进程外组件调用是透明的,如下:

//创建对应的接口实例hr = CoCreateInstance(easycomCLSID, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **)&pUnknown);if (hr != S_OK){wcout << L"Fail to Create Object" << endl;return -2;}//查询接口hr = pUnknown->QueryInterface(IID_Cat, (void **)&pCat);if (hr != S_OK){wcout << L"Fail to Create ICat" << endl;return -2;}//数组方式传递字符串LPWSTR pszWord[] = {L"hungry", L"thirty", L"sleep", L"play"};for (int i=0; i<ARRAYSIZE(pszWord); i++){WCHAR szResult[256] = {0};wstring str = pszWord[i];if (SUCCEEDED(pCat->TanslateWord(pszWord[i], szResult))){str += L"==>>";str += szResult;}else{str += L"==>>";str += L"Failed!!! What are you say?";}wcout << str << endl;}//BSTR方式传递字符串for (int i=0; i<ARRAYSIZE(pszWord); i++){BSTR pInput = ::SysAllocString(pszWord[i]);BSTR pResult = NULL;wstring str = pszWord[i];if (SUCCEEDED(pCat->TanslateWord2(pInput, &pResult))){str += L"==>>";str += pResult;::SysFreeString(pResult);}else{str += L"==>>";str += L"Failed!!! What are you say?";}if (pInput){::SysFreeString(pInput);}wcout << str << endl;}

唯一不同的是注意CoCreateInstance时传入CLSCTX_LOCAL_SERVER,指明当前连接的是进程外组件。

还需要注意对于BSTR是组件分配内存,客户调用SysFreeString释放内存,注意对应。


完整演示代码下载链接,其中CatCom为进程外组件,CatProxy为代理/存根,TestCom为客户程序。

原创,转载请注明来自http://blog.csdn.net/wenzhou1219
原创粉丝点击