簡単な 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.cGUID を定義する C ファイル
Car_p.cプロキシとスタブのコードを実装する C ファイル
Car.tlbタイプライブラリ

いろいろ生成されましたが、全てを使うわけではないので無理に調べなくて構いません。
利用できるところだけ利用します。

ここでは、MIDL コンパイラが IDL による定義から C 言語での宣言に書き落としてくれた、と考えておけば良いです。

ステップ2.クラスと関数の宣言と実装と構成ファイルの確認

今回は次の内容を別々のファイルで宣言し実装します。

ヘッダファイル名記述する内容
CarDll.hCOM コンポーネント DLL のロックカウントを操作する LockModule 関数と UnlockModule 関数の宣言
CarDll.cppCOM コンポーネント DLL の DllMain、登録機能、クラスオブジェクトの生成、モジュールの寿命管理
CarFactory.hCarFactory クラスファクトリの宣言
CarFactory.cppCarFactory クラスファクトリの実装
CarImpl.hCar クラスの宣言
CarImpl.cppCar クラスの実装

盛りだくさんですが、それぞれの実装を確認しましょう。

ステップ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 の誤りを修正しました。ご指摘くださった読者の方に感謝いたします。

« 前の記事 次の記事 &raquo
クラスファクトリ COM クライアントの実装 [1/2]

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説