1. 程式人生 > >裸寫一個程序外 COM 元件

裸寫一個程序外 COM 元件

引言

前面九月份的八篇關於COM的文章,說的都是程序內COM。那時,我們從一個含內嵌IE控制元件的視窗說起,根據COM協議手工書寫了程序內COM元件,並由此積累了一些類似ATL的框架性程式碼。

今天開始,我們把腳步邁向程序外元件。同樣是從最基礎的開始,本篇我們將根據程序外COM元件的載入規範手工編寫一個EXE,然後用標準的COM呼叫方法來使用它。之前積累的框架性程式碼不屬於第三方庫,所以這裡不會避免去使用,相反地,會把一些通用性較強的程式碼直接擴充到框架中。

本文僅限於常規EXENT服務程式暫時不在討論之列。

命令列規範

程序外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鍵結構:

image

第二個,TypeLib,跟CLSIDTypeLib一樣。

第一個,ProxyStubClsid32,是要註冊該介面的代理存根物件,用於序列化/反序列化引數和返回值。序列化/反序列化在COM中的術語是列集/散集,超不喜歡這名字。我們這裡不實現自定義的代理存根,直接寫死“

{00020424-0000-0000-C000-000000000046}”,用系統的。不過使用這個代理存根有個侷限,介面必須符合下列兩種情況之一:

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))