More Related Content
Similar to T93 com入門 (20)
T93 com入門
- 2. 自己紹介
• HN:暁紫電
• Twitter: @akatukisiden
• 本名:伊藤伸男
• フリーランスプログラマー
• 使用言語
わんくま同盟東京勉強会#93
–C++
–C#
–C++/CLI
- 6. COM(Component Object Model)とは
• コンポーネントを作成し、
そのコンポーネントからアプリケーションを構
築する方法を定義する仕様
• 作成されたコンポーネントは
実行可能コードでありWin32ダイナミックリンク
ライブラリ(DLL)もしくは実行可能ファイル(EXE)
として配布される
わんくま同盟東京勉強会#93
- 7. 何にCOMが使われているか
• Office
• Silverlight
• WinRT(ストアアプリ)
• 新しいネイティブAPI →基本的にCOMで提供
– Sensor & Location
– Media Foundation
– DirectX
– AnimationManager
わんくま同盟東京勉強会#93
- 8. COMの特徴
わんくま同盟東京勉強会#93
• 言語から独立
– 大半の言語で記述、使用が可能
– C++,C,Ada,Java,Modula-3,Oberon,Pascal,etc…
• バイナリ形式で出荷可能
• ネットワーク上に等価的に配置、利用が可能
– 同じプロセス(dll)、別のプロセス(exe),別のマシン上
• 呼び出し元を編集することなく更新が可能
• 実装を隠蔽し、更新を容易にするため
コンポーネントをインターフェイスを通して扱う
- 9. COMインターフェイス
• 関数の宣言のみで実装を持たない
(≒純粋抽象基底クラス)
• 多重継承可能(非仮想)
• 同じインターフェイスを実装していれば異なる
コンポーネントを同じように扱うことが出来る。
• 一度公開したら変更してはいけない
• 変更したい場合は新しいインターフェイスを追
加する。
わんくま同盟東京勉強会#93
- 10. IUnknown
• 全てのCOMインターフェイスの共通の派生元
• 全てのインターフェイスに知られてる
唯一のインターフェイスなのにUnknown(未
知)
わんくま同盟東京勉強会#93
#define interface struct
interface IUnknown
{
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
const IID& riid, void** ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
};
- 11. HRESULT __stdcall QueryInterface(const IID& riid, void **ppv)
• コンポーネントがIIDで指定したインターフェイ
スを実装しているかどうか調べ、実装してい
ればそのインターフェイスへのポインタをppv
に受け取る
• 戻り値HRESULTは32bit 桁によって意味が
異なるので実行の成否判定はマクロで行う
SUCCEEDED(),FAILED()
AddRef()/Release()
• 参照カウントのインクリメント/デクリメント
わんくま同盟東京勉強会#93
- 12. COMの登録
• COMは基本的にレジストリに登録する必要が
ある。
• コンポーネントの一意なID(CLSID)とそのコン
ポーネントを含むファイル(dll,exe)の配置場
所を一緒に格納し、実行時にそれを暗黙的に
参照することでコンポーネントがどのファイル
(dll,exe)に含まれているか、そのファイルがど
こにあるかを意識せずに開発を行うことが出
来る。
わんくま同盟東京勉強会#93
- 13. DllRegisterServer() /DllUnRegisterServer()
• COM登録/登録解除用のコマンド
regsvr32.exe “dll名”
regsvr32.exe –u “dll名”で呼び出される
• レジストリ関係のAPIを用いてレジストリに登
録/解除するコードの記述する必要がある。
• Dllのパスはdllmain関数の引数HMODULE
hModule をGetModuleFileName関数に渡す
ことで取得できる
わんくま同盟東京勉強会#93
- 14. COMのレジストリ構成
(DLLサーバーの場合)
CLSID{CLSID値}
InprocServer32
ProgID
VersionIndependentProgID
<Program>.<Component>.<Version>
CLSID
CurVer
<Program>.<Component>
CLSID
コンポーネント名
DLLフルパス
<Program>.<Component>.<Version>
<Program>.<Component>
コンポーネント名
{CLSID値}
<Program>.<Component>.<Version>
コンポーネント名
{CLSID値}
わんくま同盟東京勉強会#93
HKEY_CLASSES_ROOT
キー
値(既定値)
- 15. COMの使いかた
• CoInitialize(NULL)でシステムを初期化
• CoCreateInstance()関数で
コンポーネントのインスタンスを指定したイン
ターフェイスで取得して仕様
• 必要に応じてQueryInterfaceで別のインター
フェイスを取得
• AddRef/Releaseで参照カウントを操作
• 最後はCoUninitialize()を呼び出して終了。
わんくま同盟東京勉強会#93
- 17. HRESULT CoCreateInstance(
const CLSID& rclsid, Iunknown* pUnkOuter,
DWORD dwClsContext, const IID& riid,void**ppv );
CLSIDで指定したコンポーネント生成し、
IIDで指定したインターフェイスとして受け取る
• rclsid:作成するオブジェクトを指定
• pUnkOuter:実装に集約という手法を用いる場合に用いる、
使わない場合はNULL
• dwClsContext:コンポーネントが実行される場所(インプロセ
ス、アウトプロセス、リモートetc)
• IID:どのインターフェイスで受け取るか
• ppv:作成したコンポーネントを受け取るポインタ
わんくま同盟東京勉強会#93
- 18. サンプル
• 関数void FuncX()を持つインターフェイスIX
• 関数void FuncY()を持つインターフェイスIY
• の二つを実装したコンポーネント
SessionComponentを使い
• QueryInstanceでそれぞれのインターフェイス
を取得、関数の呼び出しを行う。
• ついでに実装していないインターフェイスIZの
取得を試み、失敗することを確認する。
わんくま同盟東京勉強会#93
- 20. インターフェイス及び各種ID
interface IX:public IUnknown
{
virtual void FuncX() =0;
わんくま同盟東京勉強会#93
};
interface IY :public IUnknown
{
virtual void FuncY() =0;
};
interface IZ :public IUnknown
{
virtual void FuncZ() =0;
};
- 21. インターフェイス及び各種ID
extern "C"
{
extern const CLSID CLSID_SessionComponent;
extern const IID IID_IX;
extern const IID IID_IY;
extern const IID IID_IZ;
}
わんくま同盟東京勉強会#93
- 22. インターフェイス及び各種ID
extern "C"
{
extern const CLSID CLSID_SessionComponent =
{ 0x3c4ec447, 0xa17, 0x4f0d, { 0xb7, 0x10, 0xc7, 0x3d, 0xde, 0xb0,
0xbd, 0xcf } };
extern const IID IID_IX =
{ 0x1772c7e7, 0x80d9, 0x49ac, { 0xbc, 0x25, 0xc6, 0x2, 0xf3, 0xe, 0xaf,
0x72 } };
extern const IID IID_IY =
{ 0x2b5cebed, 0xa19d, 0x4376, { 0xa2, 0xbb, 0x92, 0x38, 0x1e, 0x60,
0x88, 0x60 } };
extern const IID IID_IZ =
{ 0xf3b7ff4, 0xa5a6, 0x4296, { 0x98, 0x2c, 0x83, 0x18, 0x9e, 0x14,
0x5d, 0x8b } };
}
わんくま同盟東京勉強会#93
- 23. クライアント
int _tmain(int argc, _TCHAR* argv[])
{
わんくま同盟東京勉強会#93
// COMの初期化
CoInitialize(NULL);
// CLSID_SessionComponentの指すコンポーネントのインスタンスを作成
// インターフェイスIXで受け取る。
IX *pIX = NULL;
HRESULT hr = CoCreateInstance(CLSID_SessionComponent,
NULL,CLSCTX_INPROC_SERVER,IID_IX, (void**) &pIX);
//マクロで成否判定
if (SUCCEEDED(hr))
{
// IXの関数を呼び出す。
pIX->FuncX();
- 24. クライアント
わんくま同盟東京勉強会#93
pIX->FuncX();
//コンポーネントがIID_IYの差すインターフェイス(IY)
//を実装しているか調べ、実装していれば受け取る
IY* pIY = NULL;
HRESULT hr = pIX->QueryInterface(IID_IY, (void**) &pIY);
if (SUCCEEDED(hr))
{
//IY の関数を呼び出す。
pIY->FuncY();
//インターフェイスを使い終わったので解放
pIY->Release();
}
else
{
}
- 25. クライアント
わんくま同盟東京勉強会#93
else
{
}
// IZを実装しているかどうか調べる
IZ* pIZ = NULL;
HRESULT rZ = pIX->QueryInterface(IID_IZ, (void**) &pIZ);
if (SUCCEEDED(rZ))
{
// 実装していれば関数を呼び出す。
pIZ->FuncZ();
pIZ->Release();
}
else
{
//実装していなければメッセージを表示
std::wcerr << L"Not Supportted IZ" << std::endl;
}
- 26. クライアント
std::wcerr << L"Not Supportted IZ" << std::endl;
}
// IXを使い終わったので解放
pIX->Release();
わんくま同盟東京勉強会#93
}
// 終了
CoUninitialize();
return 0;
}
- 27. COMの作り方(必要なクラス・関数)
• IUnknownまたはその派生インターフェイスから派生
したコンポーネントクラス
• 特定のコンポーネントのインスタンスを作成するため
の関数CreateInstance()を持つ
IClassFactoryから派生したクラスファクトリクラス
• CLSIDを受け取り対応したクラスファクトリを生成す
る関数。DllGetClassObject()
• Dllをアンロードできる状態かどうかを返す。
DllCanUnloadNow()
わんくま同盟東京勉強会#93
- 28. クラスファクトリIClassFactory
interface IClassFactory : public IUnknown
{
public:
virtual HRESULT __stdcall CreateInstance(IUnknown *pUnkOuter,
const IID& riid, void **ppvObject) = 0;
virtual HRESULT __stdcall LockServer(BOOL fLock) = 0;
• 他のコンポーネントを作成するためのコンポーネント
• 1つのクラスファクトリが1つのコンポーネントに対応
• DllGetClassObject内でnewで作成
• (クラスファクトリファクトリは不要)
わんくま同盟東京勉強会#93
};
- 29. STDAPI DllGetClassObject(
const CLSID& clsid,
const IID& iid,void **ppv)
• CLSIDで指定したコンポーネント生成
するためのクラスファクトリを、
IIDで指定したインターフェイスとして受け取る
• CoGetClassObject()から呼び出される。
• コンポーネントを含むDLLに自分で実装する。
わんくま同盟東京勉強会#93
- 30. HRESULT __stdcall
CoGetClassObject(
const clsid& rclsid,
DWORD dwClsContext, void* pvReserved,
const IID& riid, void** ppv
);
• CLSIDを受け取り、対応するDLLをロードし
DllGetClassObject()を呼び出す。
• CoCreateInstance()から呼び出される。
• 同じコンポーネントのインスタンスを複数作成すると
きなどは直接この関数を呼び出して、
クラスファクトリを使いまわした方が効果的
わんくま同盟東京勉強会#93
- 31. STDAPI DllCanUnloadNow()
• 現在DLLをアンロードできる状態かどうかを調
べる
• DLL内のコンポーネントやクラスファクトリの
使用中はS_FALSEを返すように自分で実装
する必要がある(使っていないときはS_OK)
• CoFreeUnusedLibraries()から呼び出される。
わんくま同盟東京勉強会#93
- 33. コンポーネント
class SessionComponent:IX,IY
{
わんくま同盟東京勉強会#93
public:
SessionComponent();
~SessionComponent();
// IUnknown
virtual HRESULT __stdcall QueryInterface(
const IID& riid,void **ppvObject);
virtual ULONG __stdcall AddRef(void);
virtual ULONG __stdcall Release(void);
virtual void FuncX(); // IX
virtual void FuncY(); // IY
private:
long cRef_; // 参照カウント
};
- 35. コンポーネント(QueryInterface)
HRESULT __stdcall SessionComponent::QueryInterface(
const IID& riid, void **ppv)
わんくま同盟東京勉強会#93
{
// IIDに応じたインターフェイスを返す。
if (riid == IID_IUnknown) // IUnknown
{
// 常にIXインターフェースの規定IUnknownを返す。
*ppv = static_cast<IX*>(this);
}
else if (riid == IID_IX) // IX
{
*ppv = static_cast<IX*>(this);
}
else if (riid == IID_IY) // IY
{
*ppv = static_cast<IY*>(this);
}
else // 対応していないインターフェイス
- 36. コンポーネント(QueryInterface)
else // 対応していないインターフェイス
{
// 指定したインターフェイスに対応していない
*ppv = NULL;
return E_NOINTERFACE;
わんくま同盟東京勉強会#93
}
//新しい参照を返すので参照カウントを+1する。
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
- 37. コンポーネント(参照カウント)
ULONG __stdcall SessionComponent::AddRef(void)
{
// 参照カウントを+1
return InterlockedIncrement(&cRef_);
わんくま同盟東京勉強会#93
}
ULONG __stdcall SessionComponent::Release(void)
{
// 参照カウントを-1して0になったら自己解放
if (InterlockedDecrement(&cRef_) == 0)
{
delete this;
return 0;
}
return cRef_;
}
- 38. コンポーネント(インターフェイスの実装)
// IX
void SessionComponent::FuncX()
{
std::wcout << L"Call FuncX" << std::endl;
わんくま同盟東京勉強会#93
}
// IY
void SessionComponent::FuncY()
{
std::wcout << L"Call FuncY_" << std::endl;
}
- 39. クラスファクトリ
class CSessionComponentFactory :public IClassFactory
{
public:
CSessionComponentFactory();
~CSessionComponentFactory();
// IUnknown
virtual HRESULT __stdcall QueryInterface(
const IID& riid,void **ppvObject);
virtual ULONG __stdcall AddRef(void);
virtual ULONG __stdcall Release(void);
// IClassFactory
virtual HRESULT __stdcall CreateInstance(
IUnknown *pUnkOuter,const IID& riid,void **ppvObject);
virtual HRESULT __stdcall LockServer(BOOL fLock);
わんくま同盟東京勉強会#93
private:
long cRef_;
};
- 40. クラスファクトリ
CSessionComponentFactory::CSessionComponentFactory() :cRef_(1){}
CSessionComponentFactory::~CSessionComponentFactory(){}
ULONG __stdcall CSessionComponentFactory::AddRef(void)
{
return InterlockedIncrement(&cRef_);
わんくま同盟東京勉強会#93
}
ULONG __stdcall CSessionComponentFactory::Release(void)
{
if (InterlockedDecrement(&cRef_) == 0)
{
delete this;
return 0;
}
return cRef_;
}
- 41. クラスファクトリ(QueryInterface)
HRESULT __stdcall CSessionComponentFactory::QueryInterface(
const IID& riid,void * *ppv)
{
// IIDに応じたインターフェイスを返す。
if ((riid == IID_IUnknown) || (riid == IID_IClassFactory))
{
// IClassFactory
*ppv = static_cast<IClassFactory*>(this);
}
else
{ // 指定したインターフェイスに対応していない
*ppv = NULL;
return E_NOINTERFACE;
}
//新しい参照を返すので参照カウントを+1する。
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
わんくま同盟東京勉強会#93
}
- 42. クラスファクトリ(CreateInstance)
HRESULT STDMETHODCALLTYPE CSessionComponentFactory::CreateInstance(
IUnknown *pUnkOuter,const IID& riid,void **ppvObject)
{
if (pUnkOuter != NULL)
{return CLASS_E_NOAGGREGATION;}
else
{
SessionComponent* pC = new SessionComponent();
if (pC == NULL){return E_OUTOFMEMORY;}
else
{
//指定されたインターフェイスを取得
//内部で参照カウントが+1される
HRESULT hr = pC->QueryInterface(riid, ppvObject);
// この参照を使い終わったので参照カウントを-1する
pC->Release();
return hr;
わんくま同盟東京勉強会#93
}
}
}
- 43. クラスファクトリ(CreateInstance)
HRESULT STDMETHODCALLTYPE CSessionComponentFactory::LockServer(
BOOL fLock)
{
// Lock中はDll CanUnloadNow がfalseになる。
if (fLock)
{
InterlockedIncrement(&g_cServerLocks);
わんくま同盟東京勉強会#93
}
else
{
InterlockedDecrement(&g_cServerLocks);
}
return S_OK;
}
- 44. Defファイル
わんくま同盟東京勉強会#93
LIBRARY COMSessionDLL
EXPORTS
DllGetClassObject PRIVATE
DllRegisterServerPRIVATE
DllUnregisterServerPRIVATE
DllCanUnloadNow PRIVATE
STDAPI DllRegisterServer()
{
…省略…
}
STDAPI DllUnregisterServer()
{
…省略…
}
- 45. DllGetClassObject
STDAPI DllGetClassObject(const CLSID& clsid,const IID& iid,void **ppv)
{
if (clsid != CLSID_SessionComponent)
{ return CLASS_E_CLASSNOTAVAILABLE; }
else
{
CSessionComponentFactory* factory
= new CSessionComponentFactory();
if (factory == NULL)
{ return E_OUTOFMEMORY; }
else
{
HRESULT hr = factory->QueryInterface(iid, ppv);
factory->Release();
return hr;
わんくま同盟東京勉強会#93
}
}
- 46. DllCanUnloadNow
わんくま同盟東京勉強会#93
STDAPI DllCanUnloadNow()
{
//コンポーネントのインスタンスが存在するとき、
// ClassFactoryのロック中はアンロードできない。
if ((g_cComponents == 0) && (g_cServerLocks == 0))
{
return S_OK;
}
else
{
return S_FALSE;
}
}
- 47. まとめ
• 古い技術と思われがちだが、新しいネイティブAPIは
基本的にCOMとして提供される他、WinRT、
Silverlight等もCOMを基本とした技術でまだまだ現
役
• レジストリにCLSIDとファイルパスを登録することで、
コンテキスト、所属ファイル、配置場所などを気にせ
ずに扱うことが出来る。
• インターフェイスを通して扱うことで
実装を隠蔽し、呼び出し元を修正・再コンパイルする
ことなく変更ができる
わんくま同盟東京勉強会#93
- 48. これから勉強したいこと
• 集約と包含
• タイプライブラリ
• アウトプロセスサーバー(EXEサーバー)
• リモートサーバー
• IDispatch
• COM相互運用
わんくま同盟東京勉強会#93