同一ApartmentであればCOMシステムを介さず直接呼び出せますので、
好きな引数、好きな戻り値が使用できます。
しかし、Apartmentを超える場合はCOMシステムの仲介によりメソッド呼び出しを実現します。
そのため、メソッドにはいくつかの制約があります。
戻り値はHRESULTとする。COMシステムでエラーが起こった場合に、対応するHRESULT値を返すためです。
引数にデータへのポインタを含む場合は、その内容を明確にCOMシステムに示す。そうでないと、別のプロセスにデータを渡すことができません。
引数に処理(クラスや関数など)へのポインタを含めたい場合は、その呼び出しに対してCOMシステムの介在が必要になるため、決められた方法で渡す。
またCOMの制約として、インターフェース(IUnknown等)へのポインタを渡す場合は、巡回参照が起こらない設計にする必要があります。
このように制約はありますが、関数ポインタやCOMでないクラスは渡せないと考えておけば十分でしょう。
インターフェースの定義方法
まずCOMから離れて、次の関数を考えてみます。
void set_data(int num, int* data);
この関数を呼び出す場合、呼び出し側は、
数値の配列(data)
配列の要素数(num)
の2つを用意しています。
呼び出し側の疑似コードは、例えば次のようになるでしょう。
int data[] = {1,2,3,4,5}
int num = 5;
set_data(num, data);
呼び出しにシステムを仲介させようと考えると、
この関数インターフェースは明確ではありません。
例えば、set_data()の引数であるdataは、int値を含むポインタを渡しているだけかもしれません。
配列であったとしても、その要素数がnumであることはわかりません。
また、*dataに数値をセットして返す関数とも区別できません。
インターフェースを定義するには、
引数が入力であるのか、出力であるのか、ポインタが配列を表すのか、
また配列であれば要素数はいくつであるのか、を明確にする必要があります。
midlでは、これらの追加情報を与えるために、
引数の前に[]で括った属性を与えられるようになっています。
(何も与えない場合はデフォルトの属性が使われます)
先ほどの関数では、次のように与えるとその意味が明確になります。
void set_data(
[in] int num,
[in, size_is(num)] int* data);
ここでinやsize_isといった属性が出てきました。
これらの意味は次の通りです。
属性 | 内容 |
in | この引数は入力である。 |
out | この引数は出力である。 |
size_is(x) | この引数は可変長配列であり、そのサイズはxである。ここで、xは引数を使った計算式です。固定長配列の場合はsize_isを使わずに、要素数つきの配列を引数として渡します。 |
size_is(x,y) | この引数は2次元の可変長配列であり、そのサイズは[x][y]である。 |
length_is(x) | この引数は可変長配列であり、そのうち意味のあるデータのサイズはxである。 |
これをふまえて、IIchigo7というCOMのインターフェースを定義する例を示します。
この例はそのままmidlでコンパイル可能です。
//
// Copyright (c) 2018 The Ichigopack Project (http://ichigopack.net/).
//
import "unknwn.idl";
// IIchigo7 interface
[
object,
uuid(8F76DB6F-9539-46A7-BB60-049681B4144B),
pointer_default(unique)
]
interface IIchigo7 : IUnknown
{
HRESULT ReadBuf(
[in] DWORD len,
[out] DWORD* plen_read,
[out, size_is(len), length_is(*plen_read)] BYTE* pbuf);
HRESULT Read(
[out] DWORD* plen_read,
[out, size_is(,*plen_read)] BYTE** ppbuf);
HRESULT WriteData(
[in] DWORD len,
[in, size_is(len)] BYTE* pdata);
};
// Type library
[
version(1.0),
uuid(5AE2BCDB-1653-4BBB-A368-50F90E9E6ADB)
]
library Ichigo7TypeLib
{
importlib("stdole32.tlb");
interface IIchigo7;
[ uuid(17C60F8A-B0B1-438B-A1EC-95336DB669E8) ]
coclass Ichigo7
{
interface IIchigo7;
};
};
ReadBuf()とRead()はいずれもデータ読み込みメソッドです。
ReadBuf()は引数としてバッファサイズを与える例、
Read()はバッファ自体をメソッド実装側で確保する例です。
Write()はデータ書き出しメソッドの例です。
このidlでは各データの扱いと配列のサイズが明確に定義されていますので、
proxyとstubのコードを生成させ、それらをビルドすれば、
COMシステムと協調して必要なデータの送受信を行えます。
なお、COMの引数として渡すデータに対してメモリの確保や解放を行う場合は、
proxyやstubのコードと同じメモリ管理関数を使う必要があるため、
次の関数を使うことになっています。
LPVOID CoTaskMemAlloc(SIZE_T cbMem);
void CoTaskMemFree(LPVOID pvMem);
使い方はC言語のmalloc()、free()と同じです。
サンプルコード
IIchigo7メソッド実装部分は、例えば次のようになるでしょう。
なお、IIchigo7をDLLサーバとして実装する場合は、
仕組みで説明したDllSurrogateに空のパスを設定しておくと、
EXEサーバとしても動くようになります。
static const char g_ichigodata[] = "Ichigopack http://ichigopack.net/";
HRESULT STDMETHODCALLTYPE CIchigo7::ReadBuf(
DWORD len,
DWORD* plen_read,
BYTE* pbuf)
{
DWORD len_full = strlen(g_ichigodata);
if (len > len_full) {
len = len_full;
}
memcpy(pbuf, &g_ichigodata[0], len);
*plen_read = len;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CIchigo7::Read(
DWORD* plen_read,
BYTE** ppbuf)
{
*plen_read = 0;
*ppbuf = NULL;
DWORD len_full = strlen(g_ichigodata);
BYTE* pbuf = (BYTE*)CoTaskMemAlloc(len_full);
if (pbuf == NULL) return E_OUTOFMEMORY;
memcpy(pbuf, &g_ichigodata[0], len_full);
*ppbuf = pbuf;
*plen_read = len_full;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CIchigo7::WriteData(
DWORD len,
BYTE* pdata)
{
return suppLogFileAppendW( LOGFILENAME, pdata, len );
}
また、このサンプルインターフェースの呼び出しコードは、
例えば次のようになります。
proxyとstubを登録しておくと、
ICHIGO7_SERVER_TYPEをCLSCTX_LOCAL_SERVERとしても動くようになります。
//
// Copyright (c) 2018 The Ichigopack Project (http://ichigopack.net/).
//
#include <windows.h>
#include <ole2.h>
#include <iostream>
#include <string>
#include <iomanip>
#include "ichigocom7.h"
#if !defined(ICHIGO7_SERVER_TYPE)
#define ICHIGO7_SERVER_TYPE \
CLSCTX_INPROC_SERVER
#endif
int main(int argc, char** argv)
{
HRESULT hr;
hr = CoInitialize(NULL);
if ( FAILED(hr) ) {
std::cout << "CoInitialize() error: " << hr << std::endl;
return 1;
}
IUnknown* punk = NULL;
IIchigo7* pObj = NULL;
hr = CoCreateInstance(
CLSID_Ichigo7, NULL,
ICHIGO7_SERVER_TYPE,
IID_IUnknown, (LPVOID*)&punk);
if ( FAILED(hr) ) {
std::cout << "CoCreateInstance() error: "
<< std::hex << std::setfill('0')
<< std::setw(8) << hr << std::endl;
}
if ( SUCCEEDED(hr) ) {
hr = punk->QueryInterface(
IID_IIchigo7, (LPVOID*)&pObj);
punk->Release();
if ( FAILED(hr) ) {
std::cout << "QueryInterface() error: "
<< std::hex << std::setfill('0')
<< std::setw(8) << hr << std::endl;
}
}
if ( SUCCEEDED(hr) ) {
BYTE localbuf[100];
BYTE* pbuf = NULL;
char data[] = "ichigopack COM sample 7\n";
DWORD dw = 0;
hr = pObj->ReadBuf(15, &dw, localbuf);
if ( SUCCEEDED(hr) ) {
std::cout.write((char*)localbuf, dw);
std::cout << std::endl;
}
hr = pObj->ReadBuf(100, &dw, localbuf);
if ( SUCCEEDED(hr) ) {
std::cout.write((char*)localbuf, dw);
std::cout << std::endl;
}
hr = pObj->Read(&dw, &pbuf);
if ( SUCCEEDED(hr) ) {
std::cout.write((char*)pbuf, dw);
std::cout << std::endl;
CoTaskMemFree(pbuf);
}
hr = pObj->WriteData( strlen(data), (BYTE*)(&data[0]) );
pObj->Release();
}
CoUninitialize();
return 0;
}
目次
COM/ActiveXの解説ページ 目次
仕組み
インターフェースの定義
ビルド