C++でCOMサーバ
テキストエディタでCOMサーバを作って色々苦労したので、メモなどを残してみます。多分、間違っているところもあると思いますが、何かの参考になれば。
まず、必要なインタフェース分だけGUIDを作成します。
$ guidgen
IDLの作成(hello.idl)
[object, uuid(5137F758-8590-439d-AE71-98D8BDB88961)] interface IHello : IUnknown { import "unknwn.idl"; HRESULT Say (); } [object, uuid(126B0F62-1920-4222-92DA-23015FC076ED)] interface IHelloClass : IUnknown { import "unknwn.idl"; HRESULT Create ([out, retval] IHello **ppHello); }
IHelloを使ってHello,Worldを表示させます。IHelloClassはIHelloを実装したオブジェクトを生成するクラスオブジェクトのインタフェースです。
idlをコンパイルします。
$ midl hello.idl
すると、以下のファイルが作成されます。
- hello.h
- hello_i.c
- hello_p.c
- dlldata.c
今回使うのは、hello.hとhello_i.cです。
次に、IHelloとIHelloClassを実装します。(hello.cpp)
#include "hello.h" #include <stdio.h> const CLSID& CLSID_HelloClass = IID_IHelloClass; class Hello : public IHello { LONG m_cRef; protected: virtual ~Hello () {} public: Hello () : m_cRef(0) {} STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { if (riid == IID_IHello) *ppv = static_cast<IHello*>(this); else if (riid == IID_IUnknown) *ppv = static_cast<IHello*>(this); else { *ppv = 0; return E_NOINTERFACE; } reinterpret_cast<IUnknown*>(*ppv)->AddRef (); return S_OK; } STDMETHODIMP_(ULONG) AddRef () { return InterlockedIncrement (&m_cRef); } STDMETHODIMP_(ULONG) Release () { LONG res = InterlockedDecrement (&m_cRef); if (res == 0) delete this; return res; } STDMETHODIMP Say () { printf ("Hello, World\n"); return S_OK; } }; class HelloClass : public IHelloClass { public: virtual ~HelloClass () {} STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { if (riid == IID_IHelloClass) *ppv = static_cast<IHelloClass*>(this); else if (riid == IID_IUnknown) *ppv = static_cast<IHelloClass*>(this); else { *ppv = 0; return E_NOINTERFACE; } reinterpret_cast<IUnknown*>(*ppv)->AddRef (); return S_OK; } STDMETHODIMP_(ULONG) AddRef () { return 2; } STDMETHODIMP_(ULONG) Release () { return 1; } STDMETHODIMP Create (IHello **ppHello) { if ((*ppHello = new Hello) == 0) return E_OUTOFMEMORY; (*ppHello)->AddRef (); return S_OK; } }; STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv) { static HelloClass g_helloClass; if (rclsid == CLSID_HelloClass) return g_helloClass.QueryInterface (riid, ppv); *ppv = 0; return CLASS_E_CLASSNOTAVAILABLE; }
また、公開する関数のためにdefファイルを作成します。(hello.def)
LIBRARY HELLO EXPORTS DllRegisterServer private DllUnregisterServer private DllGetClassObject private
これで材料は揃いましたのでコンパイルします。
$ cl /LD hello.cpp hello_i.c hello_dll.cpp hello.def /link advapi32.lib
これでCOMサーバの完成です。早速、レジストリに登録してみましょう。
$ regsvr32 hello.dll
折角、サーバが出来たのでこれを呼び出すクライアントを用意してテストしてみます。
#include "hello.h" #include <stdio.h> #include <objbase.h> int main (int argc, char* argv[]) { CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); IHelloClass *phc = 0; HRESULT hr = CoGetClassObject (IID_IHelloClass, CLSCTX_ALL, 0, IID_IHelloClass, (void**)&phc); if (SUCCEEDED (hr)) { IHello *pHello = 0; hr = phc->Create (&pHello); if (SUCCEEDED (hr)) { hr = pHello->Say (); pHello->Release (); } phc->Release (); } else { printf("CoGetClassObject failed.\n"); } CoUninitialize (); return 0; }
これをhello_test.cppとして保存し、コンパイル。
$ cl hello_test.cpp hello_i.c /link ole32.lib
実行すると、
$ hello_test.exe Hello, World
と、なりました。
現状の怪しいところ。クライアント作成するのにhello.hとhello_i.cを使いましたが、hello.dllを配布するときに、これらも一緒に渡すわけがないので、このコンパイル方法は間違いなはず。.tlbを作成とかかしら?
Essential COMを3章の途中まで読んだ知識だと、まだこれだけ。(^^;