IUnknown

COMは、Microsoftが提唱した技術で、ある言語でできたライブラリなどのコンポーネントを別の言語から利用できるようにしたコンポーネントの仕様である・・・。

Component Object Model - Wikipedia

COMは、コンポーネント本体とそれを別の言語から利用できるようにしたインターフェースで構成されていて、
IUnknownインターフェースはそれらの基本となるもので、すべてのCOMコンポーネットに存在する・・・。

IUnknown interface (COM)
COM プログラミング入門 - Web/DB プログラミング徹底解説

IUnknownインターフェースだけを持つCOMのDLLライブラリを作ってみる・・・。

まずは、

f:id:BG1:20151104112324p:plain

[ツール]-[GUIDの生成]を選択し、

f:id:BG1:20151104112536p:plain

COMのインターフェース情報について定義するIDLファイルに必要なGUIDを生成する・・・。
[New GUID]を押すだけでぽんぽんできる・・・。[Copy]でクリップボードにコピーされるので、貼りつける時に便利・・・。
インターフェース、タイプライブラリ、coclassの分が必要で今回とりあえず3つはメモしておく・・・。

次に、今回はIUnknown_というインターフェースを持つクラスライブラリを作るので、IUnknown_.idlというファイルを用意して、

こんなふうに書く・・・。
uuidの後の、"("と")"の間に先程生成したGUIDをそれぞれ別々にあてはめる・・・。

IUnknownインターフェースを継承した、IUnknown_インターフェース・・・。
それを持つcoclassであるCUnknownクラス・・・。
それを持つUnknownLibタイプライブラリ・・・。
(難しいけど、こういうものだと思うしかない・・・。)

f:id:BG1:20151104115117p:plain

Visual Studio Toolsのコマンドプロンプトで、IUnknown_.idlの場所へ行ってmidl IUnknown_.idlとコマンドを打つと、

C:\Project\Cloud\github.com\Sample\com\IUnknown\IUnknown\src>midl IUnknown_.idl
Microsoft (R) 32b/64b MIDL Compiler Version 6.00.0366
Copyright (c) Microsoft Corporation 1991-2002. All rights reserved.
Processing .\IUnknown_.idl
IUnknown_.idl
Processing C:\Program Files (x86)\Microsoft Visual Studio 8\VC\PlatformSDK\include\unknwn.idl
unknwn.idl
Processing C:\Program Files (x86)\Microsoft Visual Studio 8\VC\PlatformSDK\include\wtypes.idl
wtypes.idl
Processing C:\Program Files (x86)\Microsoft Visual Studio 8\VC\PlatformSDK\include\basetsd.h
basetsd.h
Processing C:\Program Files (x86)\Microsoft Visual Studio 8\VC\PlatformSDK\include\guiddef.h
guiddef.h
Processing C:\Program Files (x86)\Microsoft Visual Studio 8\VC\PlatformSDK\include\oaidl.idl
oaidl.idl
Processing C:\Program Files (x86)\Microsoft Visual Studio 8\VC\PlatformSDK\include\objidl.idl
objidl.idl
Processing C:\Program Files (x86)\Microsoft Visual Studio 8\VC\PlatformSDK\include\oaidl.acf
oaidl.acf

C:\Project\Cloud\github.com\Sample\com\IUnknown\IUnknown\src>

MIDLコンパイラにより、ファイルが生成される・・・。

ここでようやくIUnknownプロジェクトを作成する・・・。
先程のファイルたちを、

f:id:BG1:20151104120327p:plain

プロジェクトのところにおいて、既存のファイルでプロジェクトに追加・・・。

f:id:BG1:20151104120806p:plain

全部追加してもいいけど、IUnknown_.idlだけ追加しておく・・・。
(実は、idlを追加しておけば、ビルド時にVisualStudioが勝手にMIDLコンパイルしてくれる・・・。)

CUnknownクラスの宣言をUnknown.hに書く・・・。

STDMETHODIMPは標準的なCOMメソッドであることを表し、STDMETHODIMPマクロを付けているメソッドは、戻り値がHRESULT型になる・・・。
STDMETHODIMP_(ULONG)は戻り値がULONGになる・・・。

QueryInterface、AddRef、Releaseは、IUnknownにあるメソッドで、今回はそれらをオーバーライドしてる・・・。

定義は、Unknown.cppに、

m_lRefは、参照カウンタというもので、ここではコンストラクタで1にしてる・・・。
COMは参照カウンタでオブジェクトのリソースを管理してる・・・。
簡単な COM コンポーネントの実装方法 ~ 作ってわかる COM の基礎 - Web/DB プログラミング徹底解説
6.参照カウンタと寿命 - COM研究室
QueryInterfaceはriidで指定されたインターフェースがあるかを探し、あればS_OKを返す・・・。

IUnknown::QueryInterface method (COM)

本来、

if ((IID_IUnknown == riid) || (IID_IClassFactory == riid)){

ちゃんとした正式なCOMインターフェースにはIClassFactoryインターフェースが必要だが、今回は本当にIUnknownだけで作るので細工してる・・・。
ppvに見つかったインターフェースのアドレスを渡してる・・・。
"QueryInterface!"を出力してここが呼ばれたことがわかるようにしている・・・。

AddRefは、

IUnknown::AddRef method (COM)

インターフェースの参照開始を意味し、参照カウンタを増やす・・・。

Releaseは、

IUnknown::Release method (COM)

インターフェースの参照終了ということで、参照カウンタを減らす・・・。
参照カウンタが0になると、このオブジェクトは解放される・・・。

Methodは、IUnknownにはない、IUnknown_オリジナルのメソッド・・・。
"Method!"を出力するだけだが、実際にCOMライブラリを作る時はこのようなメソッドを作っていろいろな処理ができるようにする・・・。

次に、このクラスをDLL化するためにDllMain.cppを追加する・・・。

COMDLLはタイプライブラリのGUIDをレジストリに登録をする必要があるので、

DllRegisterServerで登録、DllUnregisterServerで削除・・・。

また、DllGetClassObjectの中でCUnknownの持つインターフェース(IUnknown_など)を取得するようになっているのだが、IClassFactoryインターフェースで取得を前提としてるので、ここも細工してる・・・。

そして、これらのDLL関数を外部参照できるようにエクスポートする・・・。
そのためにIUnknown.defというファイルを追加する・・・。

これで、構成の種類を"ダイナミックライブラリ(.dll)"にして、DLL側は終わり・・・。
あとは、Mainプロジェクトを追加して、Main.cppに、

と書く・・・。

本来、CoCreateInstanceを呼び出すが、IClassFactory前提なので、
COMメモ10 インスタンス生成まとめ | akatukisiden's blog
CoGetClassObjectでIUnknown_を呼び出して、Methodを呼んでみる・・・。

f:id:BG1:20151104133642p:plain

Mainで[追加のインクルードディレクトリ]に"../IUnknown"を追加・・・。

f:id:BG1:20151104134628p:plain

一斉にリビルドするたび、MIDLコンパイラが走り、ファイルが更新されるので、
IUnknown__h.hで↑が出たり、IUnknown__h.h自体がみつからないといったことが起こるが、一気にではなく、IUnknownを"ビルド"してからMainを"ビルド"する・・・。

実行すると、

QueryInterface!
AddRef!
AddRef!
Release!
QueryInterface!
AddRef!
Release!
Method!
Release!
続行するには何かキーを押してください . . .

何度か呼ばれてしまうのは、IClassFactoryが無いために小細工したからというのもある・・・。
正しい作り方ではない・・・。
まあ、しかしIUnknownだけのIUnknown_でもいちおうMethodを呼ぶことはできた・・・。

Sample/IUnknown_.idl at master · bg1bgst333/Sample · GitHub
Sample/Unknown.h at master · bg1bgst333/Sample · GitHub
Sample/Unknown.cpp at master · bg1bgst333/Sample · GitHub
Sample/DllMain.cpp at master · bg1bgst333/Sample · GitHub
Sample/IUnknown.def at master · bg1bgst333/Sample · GitHub
Sample/Main.cpp at master · bg1bgst333/Sample · GitHub