簡単な COM コンポーネントの実装方法 ~ 作ってわかる COM の基礎
以上で予備知識の説明はおわりです。
いよいよ COM コンポーネントを実装してみましょう。
ステップ1. インターフェイス定義
IDL (インターフェイス定義言語) という言語でインターフェイスを定義します。
import "unknwn.idl"; // ICar Interface [ object, uuid(3635fe8e-dad2-4d23-aebe-2febf9938231), pointer_default(unique) ] interface ICar: IUnknown { HRESULT Run(void); HRESULT Stop(void); } //Typelibrary [ uuid(c0be60a1-fb53-43ef-b0a6-81a435af5aa6), version(1.0) ] library CarLibrary { importlib("stdole32.tlb"); [ uuid(0069c1ec-3242-4e30-b7c2-0f5cc3a19453) ] coclass Car { [default] interface ICar; }; };
UUID というのは、ここで定義するインターフェイス、タイプライブラリ、coclass を一意に識別するための ID です。 SDK ツールの uuidgen というコマンドで作成できます。
> uuidgen 0069c1ec-3242-4e30-b7c2-0f5cc3a19453
さて上で作った IDL を Car.idl として保存します。
これを MIDL コンパイラで処理します。次のようになれば OK です。
> midl Car.idl Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0555 Copyright (c) Microsoft Corporation. All rights reserved. Processing .\Car.idl Car.idl Processing C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\unknwn.idl unknwn.idl Processing C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\wtypes.idl wtypes.idl Processing C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\basetsd.h basetsd.h Processing C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\guiddef.h guiddef.h Processing C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\oaidl.idl oaidl.idl Processing C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\objidl.idl objidl.idl Processing C:\Program Files\Microsoft SDKs\Windows\v7.0\Include\oaidl.acf oaidl.acf >
この結果、次のようなファイルが生成されているはずです。
dlldata.c | プロキシとスタブのコードを含む DLL を実装する C ファイル |
Car.h | インターフェイス宣言を含む C/C++ ヘッダファイル |
Car_i.c | GUID を定義する C ファイル |
Car_p.c | プロキシとスタブのコードを実装する C ファイル |
Car.tlb | タイプライブラリ |
いろいろ生成されましたが、全てを使うわけではないので無理に調べなくて構いません。
利用できるところだけ利用します。
ここでは、MIDL コンパイラが IDL による定義から C 言語での宣言に書き落としてくれた、と考えておけば良いです。
ステップ2.クラスと関数の宣言と実装と構成ファイルの確認
今回は次の内容を別々のファイルで宣言し実装します。
ヘッダファイル名 | 記述する内容 |
---|---|
CarDll.h | COM コンポーネント DLL のロックカウントを操作する LockModule 関数と UnlockModule 関数の宣言 |
CarDll.cpp | COM コンポーネント DLL の DllMain、登録機能、クラスオブジェクトの生成、モジュールの寿命管理 |
CarFactory.h | CarFactory クラスファクトリの宣言 |
CarFactory.cpp | CarFactory クラスファクトリの実装 |
CarImpl.h | Car クラスの宣言 |
CarImpl.cpp | Car クラスの実装 |
盛りだくさんですが、それぞれの実装を確認しましょう。
ステップ3.ヘッダファイルの作成
CarDll.h、CarFactory.h、CarImpl.h はそれぞれ次のようになります。
まず CarDll.h です。
#include <windows.h> void LockModule(); void UnlockModule();
次に CarFactory.h です。クラスファクトリを宣言します。
#include <windows.h> class CarFactory : public IClassFactory { public: // IUnknown Intarface STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IClassFactory STDMETHODIMP CreateInstance( IUnknown* pUnkOuter, REFIID riid, LPVOID *ppv); STDMETHODIMP LockServer(BOOL bLock); // Constructor/Destructor CarFactory(); ~CarFactory(); private: // Reference Counter LONG m_cRef; };
ここで STDMETHODIMP というのが出てきます。STDMETHODIMP は Standard Method Implementation の意味で、 すなわち「標準的なメソッドの実装」ということです。COM のメソッドはメソッドの成否を HRESULT を返します。 STDMETHODIMP を指定しているのは、すなわち HRESULT を返す、という意味になります。
ただし、AddRef と Release については STDMETHODIMP_(ULONG) となっていますが、これは ULONG を返すということです。 こちらのほうが例外と考えてよいです。自前で定義するメソッドは HRESULT を返す STDMETHODIMP として定義すべきです。
最後に CarImpl.h です。Car クラスを宣言します。
#pragma once #include <windows.h> #include "Car.h" class Car : public ICar { public: //IUnknown Intarface STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // ICar STDMETHODIMP Run(); STDMETHODIMP Stop(); // Constructor/Destructor Car(); ~Car(); private: // Reference Counter LONG m_cRef; };
ステップ4.それぞれ実装する
まずは CarDll.cpp です。
#include "CarDll.h" #include "CarImpl.h" #include "CarFactory.h" #include <olectl.h> static LONG g_cLocks = 0; static HINSTANCE g_hModule = NULL; const char *g_RegTable[][3] = { {"CLSID\\{0069c1ec-3242-4e30-b7c2-0f5cc3a19453}", 0, "KeicodeTest"}, {"CLSID\\{0069c1ec-3242-4e30-b7c2-0f5cc3a19453}\\InprocServer32", 0, (const char*)-1}, {"CLSID\\{0069c1ec-3242-4e30-b7c2-0f5cc3a19453}\\InprocServer32", "ThreadingModel" , "Apartment"}, {"CLSID\\{0069c1ec-3242-4e30-b7c2-0f5cc3a19453}\\ProgID", 0, "KeicodeTest.Car.1"}, {"KeicodeTest.Car.1", 0, "KeicodeTest"}, {"KeicodeTest.Car.1\\CLSID", 0, "{0069c1ec-3242-4e30-b7c2-0f5cc3a19453}"} }; BOOL APIENTRY DllMain( HINSTANCE hModule, DWORD dwReason, void* lpReserved) { if(DLL_PROCESS_ATTACH == dwReason) { g_hModule = hModule; } return TRUE; } STDAPI DllUnregisterServer(void) { HRESULT hr = S_OK; int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable); for(int i=nEntries-1;i>=0;i--) { const char *pszKeyName = g_RegTable[i][0]; long err = RegDeleteKeyA(HKEY_CLASSES_ROOT, pszKeyName); if(ERROR_SUCCESS!=err) { hr = S_FALSE; } } return hr; } STDAPI DllRegisterServer(void) { HRESULT hr = S_OK; char szFileName[MAX_PATH]; GetModuleFileNameA(g_hModule, szFileName, MAX_PATH); int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable); for( int i=0; SUCCEEDED(hr) && i<nEntries; i++ ) { const char *pszKeyName = g_RegTable[i][0]; const char *pszValueName = g_RegTable[i][1]; const char *pszValue = g_RegTable[i][2]; if(pszValue == (const char*)-1) { pszValue = szFileName; } HKEY hkey; long err = RegCreateKeyA( HKEY_CLASSES_ROOT, pszKeyName, &hkey); if( ERROR_SUCCESS == err ) { err = RegSetValueExA( hkey, pszValueName, 0, REG_SZ, (const BYTE*)pszValue, (strlen(pszValue) + 1)); RegCloseKey(hkey); } if( ERROR_SUCCESS != err ) { DllUnregisterServer(); hr = SELFREG_E_CLASS; } } return hr; } STDAPI DllGetClassObject( REFCLSID clsid, REFIID iid, LPVOID *ppv) { if( CLSID_Car != clsid ) { return CLASS_E_CLASSNOTAVAILABLE; } CarFactory* pFactory = new CarFactory; if( !pFactory ) { return E_OUTOFMEMORY; } HRESULT hr = pFactory->QueryInterface( iid, ppv ); pFactory->Release(); return hr; } // // Life Management // void LockModule() { InterlockedIncrement( &g_cLocks ); } void UnlockModule() { InterlockedDecrement( &g_cLocks ); } STDAPI DllCanUnloadNow() { return 0 == g_cLocks ? S_OK : S_FALSE; }
次に CarFactory.cpp です。クラスファクトリを実装します。
#include "CarFactory.h" #include "CarImpl.h" #include "CarDll.h" CarFactory::CarFactory() : m_cRef(1) { } CarFactory::~CarFactory() { } STDMETHODIMP CarFactory::QueryInterface( REFIID riid, LPVOID *ppv) { *ppv = NULL; if( (IID_IUnknown == riid) || (IID_IClassFactory == riid) ) { *ppv = static_cast<IClassFactory*>(this); } else { return E_NOINTERFACE; } AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CarFactory::AddRef() { LockModule(); return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CarFactory::Release() { LONG res = InterlockedDecrement(&m_cRef); if(0 == res) { UnlockModule(); delete this; } return res; } STDMETHODIMP CarFactory::CreateInstance( LPUNKNOWN pUnkOuter, REFIID riid, LPVOID *ppv ){ if(NULL != pUnkOuter) { return CLASS_E_NOAGGREGATION; } Car* pCar = new Car; if(NULL == pCar) { return E_OUTOFMEMORY; } HRESULT hr = pCar->QueryInterface( riid, ppv ); pCar->Release(); return hr; } STDMETHODIMP CarFactory::LockServer( BOOL bLock ) { if( bLock ) { LockModule(); } else { UnlockModule(); } return S_OK; }
最後に CarImpl.cpp です。Car クラス特有のメソッド、 Run、Stop は標準出力に文字をプリントするだけです。
#include "CarDll.h" #include "CarImpl.h" #include <stdio.h> Car::Car() : m_cRef(1) { } Car::~Car() { } STDMETHODIMP Car::QueryInterface( REFIID riid, LPVOID *ppv ) { *ppv = NULL; if( ( IID_IUnknown == riid ) || ( IID_ICar == riid ) ) { *ppv = static_cast<ICar*>(this); } else { return E_NOINTERFACE; } AddRef(); return S_OK; } STDMETHODIMP_(ULONG) Car::AddRef(void) { LockModule(); return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) Car::Release(void) { LONG res = InterlockedDecrement(&m_cRef); if(0 == res) { UnlockModule(); delete this; } return res; } STDMETHODIMP Car::Run(VOID) { printf("Run!\n"); return S_OK; } STDMETHODIMP Car::Stop() { printf("Stop!\n"); return S_OK; }
ステップ5.DEF ファイルの作成
最後にエクスポート関数を定義する DEF ファイルを作成します。ファイル名は Car.def にします。
LIBRARY Car EXPORTS DllCanUnloadNow DllRegisterServer DllUnregisterServer DllGetClassObject
ステップ6.ビルド
以上で実装は完了です。
makefile は次のとおりです。
TARGETNAME=car DEFFILE=Car OUTDIR=.\chk LINK32=link.exe ALL : "$(OUTDIR)\$(TARGETNAME).dll" CPPFLAGS=\ /nologo\ /MT\ /W4\ /Fo"$(OUTDIR)\\"\ /Fd"$(OUTDIR)\\"\ /c\ /Zi\ /D_WIN32_WINNT=0x0600 LINK32_FLAGS=\ ole32.lib\ advapi32.lib\ /nologo\ /subsystem:windows\ /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\ /out:"$(OUTDIR)\$(TARGETNAME).dll"\ /DEF:$(DEFFILE).def\ /DLL\ /DEBUG\ /RELEASE LINK32_OBJS= \ "$(OUTDIR)\Car_i.obj"\ "$(OUTDIR)\CarImpl.obj"\ "$(OUTDIR)\CarDll.obj"\ "$(OUTDIR)\CarFactory.obj" "$(OUTDIR)\$(TARGETNAME).dll" : "$(OUTDIR)" $(LINK32_OBJS) $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS) "$(OUTDIR)" : if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" .c{$(OUTDIR)}.obj: $(CPP) $(CPPFLAGS) $< .cpp{$(OUTDIR)}.obj: $(CPP) $(CPPFLAGS) $<
以上を nmake すれば、car.dll が作成されるはずです。
ステップ7.COM を登録する
以上で car.dll ができたら、regsvr32 コマンドで COM を登録します。
> regsvr32 car.dll
上のように成功のメッセージが表示されれば OK です。
次回の記事ではこの COM を使うクライアントを作成します。
変更履歴
8/16/2010 - makefile の誤りを修正しました。ご指摘くださった読者の方に感謝いたします。
« 前の記事 | 次の記事 » | |
クラスファクトリ | COM クライアントの実装 [1/2] |