C++で作るCOMサーバ
2005年12月20日
.NET を用いればregasm.exeでCOMサーバを簡単に登録することが出来るのだが、やはり.NETは重い。加えて、.NET Framework がインストールされていないコンピュータの割合はそれほど低くないと思われる。
他方で、簡単な構造のCOMサーバなら、C++を用いてWin32プログラムとして製作すればよい。この場合、動作は軽いし汎用性もある。ただし、複雑なプログラムを組むのはやはり骨が折れるのだが…。
今回C++を用いたCOMサーバを製作することになったので、その方法をここにまとめておくことにした。
COMサーバの基本となるインターフェース部分を一つのcppファイルにした。以下の通りである。このファイルはいっさい変更することなく用いることが出来る。C++プロジェクトにインクルードすればよい。
comserver.cppの内容
最低限必要な4つの関数DllRegisterServer, DllUnregisterServer, DllGetClassObject, DllCanUnloadNowはここに含まれている。ソース半ばにあるComClass クラスからグローバル関数COM_GetIDsOfNamesとCOM_Invokeを呼び出す形になっているので、この2つのグローバル関数を独自に製作することになる。次に、ヘッダファイルを見てみる。
comserver.hの内容
{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}は製作するCOMサーバを認識するGUIDである。GUIDGen.exeで製作した値をペーストして用いる。 "YourComName" には、製作するCOMサーバの名称を指定する。これは、例えばvbscriptでは、『Set xx=CreateObject("YourComName")』のように用いる物である。すでに登録されている・或いは一般に用いられている他の名称と重ならないように命名しなければならない。また、各COMオブジェクトで保存されるデータを格納するためのCOMDATAクラスの定義をここに書いておく。
続けて、独自のコードを次のように書いてプロジェクトにインクルードする。
yourserver.cppの内容
最後に、yourserver.defを次の様に書いて、プロジェクトにインクルードする。
comserver.cpp, yourserver.cpp, yourserver.defの3つのファイルがプロジェクトに含まれていて、comserver.hが依存ファイルになっていることを確認する。 ビルドして『regsvr32.exe yourserver.dll』で登録すれば、使えるようになる。
他方で、簡単な構造のCOMサーバなら、C++を用いてWin32プログラムとして製作すればよい。この場合、動作は軽いし汎用性もある。ただし、複雑なプログラムを組むのはやはり骨が折れるのだが…。
今回C++を用いたCOMサーバを製作することになったので、その方法をここにまとめておくことにした。
COMサーバの基本となるインターフェース部分を一つのcppファイルにした。以下の通りである。このファイルはいっさい変更することなく用いることが出来る。C++プロジェクトにインクルードすればよい。
comserver.cppの内容
#include <windows.h> #include <shlobj.h> #include "comserver.h" //Standard COM server //See "comserver.h" for usage // CServer class holds global object count class CServer { public: CServer() : m_hInstance(NULL), m_dwRef(0) {} HINSTANCE m_hInstance; DWORD m_dwRef; }; CServer m_Server; // Template class that provides basic IUnknown implementation template <class T, const IID* piid> class CInterface : public T { public: CInterface() : m_dwRef(0) { m_Server.m_dwRef++; } virtual ~CInterface() { m_Server.m_dwRef--; } STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) { if ((riid == IID_IUnknown) || (riid == *piid)) { *ppvObject = (T*)static_cast<T*>(this); m_dwRef++; return S_OK; } return E_NOINTERFACE; } STDMETHOD_(ULONG,AddRef)() { return ++m_dwRef; } STDMETHOD_(ULONG,Release)() { if (!(--m_dwRef)) { delete this; return 0; } return m_dwRef; } DWORD m_dwRef; }; // The COM class class ComClass : public CInterface<IDispatch,&IID_IDispatch> { // IDispatch interface implementation public: // These methods not implemented STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) { return E_NOTIMPL; } STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**) { return E_NOTIMPL; } //Method definition STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR* rgszNames, UINT cNames, LCID, DISPID* rgDispId) { return COM_GetIDsOfNames(rgszNames, cNames, rgDispId, data); } //Method Invoke STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT *puArgErr) { return COM_Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr, data); } ComClass(){ data=new COMDATA; } ~ComClass(){ delete data; } COMDATA* data; }; // Class factory to create COM objects class CClassFactory : public CInterface<IClassFactory,&IID_IClassFactory> { public: // IClassFactory interface implementation STDMETHOD(CreateInstance)(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) { if (pUnkOuter) return CLASS_E_NOAGGREGATION; ComClass* pObject = new ComClass; HRESULT hr = pObject->QueryInterface(riid,ppvObject); if (FAILED(hr)) delete pObject; return hr; } STDMETHOD(LockServer)(BOOL fLock) { return CoLockObjectExternal(this,fLock,TRUE); } }; // DllMain extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) { if (dwReason == DLL_PROCESS_ATTACH) { m_Server.m_hInstance = hInstance; DisableThreadLibraryCalls(hInstance); } return TRUE; } // Required COM in-proc server exports STDAPI DllUnregisterServer(void) { HRESULT hr = S_OK; HKEY key = NULL; if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,CLSIDKEY,0,NULL,REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,NULL,&key,NULL)) { char szModulePath[_MAX_PATH]; GetModuleFileName(m_Server.m_hInstance,szModulePath,_MAX_PATH); if(!RegSetValueEx(key,NULL,0,REG_SZ,(const unsigned char*)szModulePath, strlen(szModulePath)+1)) { if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,PRODKEY,0,NULL,REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,NULL,&key,NULL)) { if (RegDeleteKey(HKEY_CLASSES_ROOT,PRODIDKEY)) hr = E_FAIL; if (RegDeleteKey(HKEY_CLASSES_ROOT,PRODKEY)) hr = E_FAIL; } RegCloseKey(key); } if (RegDeleteKey(HKEY_CLASSES_ROOT,CLASSKEY)) hr = E_FAIL; if (RegDeleteKey(HKEY_CLASSES_ROOT,CLSIDKEY)) hr = E_FAIL; } RegCloseKey(key); return hr; } STDAPI DllRegisterServer(void) { HRESULT hr = E_FAIL; HKEY key = NULL; if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,CLASSKEY,0,NULL,REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,NULL,&key,NULL)) { char szModulePath[_MAX_PATH]; GetModuleFileName(m_Server.m_hInstance,szModulePath,_MAX_PATH); if(!RegSetValueEx(key,NULL,0,REG_SZ,(const unsigned char*)szModulePath, strlen(szModulePath)+1)) { RegCloseKey(key); if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,PRODIDKEY,0,NULL,REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,NULL,&key,NULL)) { if (!RegSetValueEx(key,NULL,0,REG_SZ,(const unsigned char*)CLSIDVAL, strlen(CLSIDVAL)+1)) hr = S_OK; } } } RegCloseKey(key); return hr; } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; if (rclsid == CLSID_COM) { CClassFactory* pFactory = new CClassFactory; if (FAILED(hr = pFactory->QueryInterface(riid,ppv))) delete pFactory; hr = S_OK; } return hr; } STDAPI DllCanUnloadNow() { return (m_Server.m_dwRef) ? S_FALSE : S_OK; }
最低限必要な4つの関数DllRegisterServer, DllUnregisterServer, DllGetClassObject, DllCanUnloadNowはここに含まれている。ソース半ばにあるComClass クラスからグローバル関数COM_GetIDsOfNamesとCOM_Invokeを呼び出す形になっているので、この2つのグローバル関数を独自に製作することになる。次に、ヘッダファイルを見てみる。
comserver.hの内容
// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} static const GUID CLSID_COM = { 0xXXXXXXXX, 0xXXXX, 0xXXXX, { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX} }; static const LPCSTR CLSIDVAL = "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"; static const LPCSTR CLSIDKEY = "CLSID\\{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"; static const LPCSTR CLASSKEY = "CLSID\\{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\\InProcServer32"; static const LPCSTR PRODKEY = "YourComName"; static const LPCSTR PRODIDKEY = "YourComName\\CLSID"; class COMDATA { //This class is used for storing the data of each COM object. }; DWORD COM_GetIDsOfNames(LPOLESTR* rgszNames, UINT cNames, DISPID* rgDispId,COMDATA *data); DWORD COM_Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT *puArgErr,COMDATA *data);
{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}は製作するCOMサーバを認識するGUIDである。GUIDGen.exeで製作した値をペーストして用いる。 "YourComName" には、製作するCOMサーバの名称を指定する。これは、例えばvbscriptでは、『Set xx=CreateObject("YourComName")』のように用いる物である。すでに登録されている・或いは一般に用いられている他の名称と重ならないように命名しなければならない。また、各COMオブジェクトで保存されるデータを格納するためのCOMDATAクラスの定義をここに書いておく。
続けて、独自のコードを次のように書いてプロジェクトにインクルードする。
yourserver.cppの内容
#include <windows.h> #include "comserver.h" //Method definition DWORD COM_GetIDsOfNames(LPOLESTR* rgszNames, UINT cNames, DISPID* rgDispId,COMDATA *data) { for (UINT i = 0; i < cNames; i++) { rgDispId[i] == DISPID_UNKNOWN; if (!lstrcmpiW(rgszNames[i],L"Method1")) rgDispId[i]=1; else if (!lstrcmpiW(rgszNames[i],L"Method2")) rgDispId[i]=2; else if (!lstrcmpiW(rgszNames[i],L"Method3")) rgDispId[i]=3; else return DISP_E_MEMBERNOTFOUND; } return S_OK; } //Method Invoke DWORD COM_Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT *puArgErr,COMDATA *data) { int cArgs = pDispParams->cArgs; VARIANTARG* rgvarg = pDispParams->rgvarg; switch (dispIdMember) { case 1: //Method1 //rgvarg[i].vt: // VT_UI1 byte, use rgvarg[i].bVal // VT_I2 short, use rgvarg[i].iVal // VT_I4 long, use rgvarg[i].lVal // VT_BSTR unicode, use rgvarg[i].bstrVal // etc // when (rgvarg[i].vt & VT_BYREF) //rgvarg[i].pvarVal->vt: // VT_I2 short, use rgvarg[i].pvarVal->iVal // VT_I4 long, use rgvarg[i].pvarVal->lVal // etc return S_OK; //if everything is all right. return DISP_E_BADPARAMCOUNT; //if wrong parameter(s) case 2: //Method2 case 3: //Method3 default:return (DWORD)DISPID_UNKNOWN; } }
最後に、yourserver.defを次の様に書いて、プロジェクトにインクルードする。
LIBRARY "yourserver.dll" EXPORTS DllRegisterServer PRIVATE DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllUnregisterServer PRIVATE
comserver.cpp, yourserver.cpp, yourserver.defの3つのファイルがプロジェクトに含まれていて、comserver.hが依存ファイルになっていることを確認する。 ビルドして『regsvr32.exe yourserver.dll』で登録すれば、使えるようになる。