裸寫一個程序外 COM 元件
引言
前面九月份的八篇關於COM的文章,說的都是程序內COM。那時,我們從一個含內嵌IE控制元件的視窗說起,根據COM協議手工書寫了程序內COM元件,並由此積累了一些類似ATL的框架性程式碼。
今天開始,我們把腳步邁向程序外元件。同樣是從最基礎的開始,本篇我們將根據程序外COM元件的載入規範手工編寫一個EXE,然後用標準的COM呼叫方法來使用它。之前積累的框架性程式碼不屬於第三方庫,所以這裡不會避免去使用,相反地,會把一些通用性較強的程式碼直接擴充到框架中。
本文僅限於常規EXE。NT服務程式暫時不在討論之列。
命令列規範
程序外COM不像DLL,不需要實現四個匯出函式。取而代之地,需要實現一些命令列引數:
1./RegServer
2./UnregServer
3./RegServerPerUser
4./UnRegServerPerUser
我沒有找到官方文件,只是從ATL的實現來找到了上述四個引數。從名字來看,很容易理解。前兩個相當於程序內元件的 DllRegisterServer 和 DllUnregisterServer,後兩個針對當前使用者,相當於 DllInstall(“user”)。
從ATL的實現程式碼來看,命令的前導符號不必是“/”,也可以是“-”。
另外,從測試情況來看,當COM庫載入程序外元件的時候,會帶上引數“-Embedding”,這可以用於區分使用者主動執行還是被COM
註冊和反註冊
為了快速達到執行目的,今天我們只實現/RegServer和/UnregServer,後兩個先不管了。
程序外COM和程序內COM的登錄檔結構大體一致,“粗看”發現,唯一的區別是,CLSID下的InprocServer32變成了LocalServer32。另外一個關鍵點是,我們自定義的每一個介面都需要註冊到Interface下。Interface鍵結構:
第二個,TypeLib,跟CLSID的TypeLib一樣。
第一個,ProxyStubClsid32,是要註冊該介面的代理存根物件,用於序列化/反序列化引數和返回值。序列化/反序列化在COM中的術語是列集/散集,超不喜歡這名字。我們這裡不實現自定義的代理存根,直接寫死“
1.實現了IDispatch介面,並在IDL中把介面屬性標記為dual。
2.只使用VARIANT相容的資料型別,並在IDL中把介面屬性標記為oleautomation。
另外說一點,ATL的 /RegServer,僅僅註冊 dual 的介面。這點我們這裡不學。
下面修改以前的ComModule::RegisterTypeLib,增加註冊Interface的程式碼:
bool RegisterTypeLib(HKEYhRootKey) { String strPath; strPath += _T("Software\\Classes\\TypeLib\\"); strPath += m_strLibID; strPath += _T("\\"); strPath += m_strLibVersion; if (!Registry::SetString(hRootKey, strPath, _T(""), m_strLibName)) { returnfalse; } strPath += _T("\\0\\"); #ifdef _WIN64 strPath += _T("Win64"); #else strPath += _T("Win32"); #endif if (!Registry::SetString(hRootKey, strPath, _T(""), m_strModulePath)) { returnfalse; } for (UINT i = 0; i < m_pTypeLib->GetTypeInfoCount(); ++i) { TYPEKIND type = TKIND_MAX; HRESULT hr = m_pTypeLib->GetTypeInfoType(i, &type); if (FAILED(hr)) { returnfalse; } if (type != TKIND_INTERFACE && type != TKIND_DISPATCH) { continue; } ITypeInfo *pTypeInfo = nullptr; hr = m_pTypeLib->GetTypeInfo(i, &pTypeInfo); if (FAILED(hr))
|