日本語で書かれたCOMのドキュメントは数少ない。決して趣味でやろうなどと考えてはいけない。(私は趣味だが)本文中の記述例およびサンプルは、すべてVB6またはVC6で書いてある。
記述例およびサンプルに含まれるファイルの全部、または一部を使用したことによる損害等について、一切の責任を負いません。
一般に、あるプロセスにおけるアドレス値は、別のプロセスでは意味を持たない。これは、プロセス毎にシステムから異なるアドレス空間を与えられるからだ。ところが、COMではインプロセスはもちろんのこと、アウトプロセス、更にはリモートまで、プロセスやマシンの境界を越えてのメソッド呼び出しが可能だ。これを実現するための仕組みがマーシャリングである。
VARIANT
(.NETではない)Visual BasicではおなじみのVariant型の実体はこのVARIANT構造体である。格納されているデータの型情報とデータの実体部分の共用体をフィールドに持つ構造体で、COMでは非常に重要だ。メソッドの引数を省略可能にするときもこのVARIANTを使う。
一般的なlong、short、doubleなどのほか、後述するSAFEARRAYやBSTRも格納することができる、Visual Basicにおいては非常に柔軟なデータ型となる。ただし、単純なデータ型に比し多少コストはかかる。
[VC]SAFEARRAY
Visual Basicの配列の実体はこのSAFEARRAYで、次元や要素数とデータを指すポインタのフィールドを持つ構造体である。SAFEARRAYもCOMでは非常に重要となる。
SAFEARRAYの簡単なサンプルを作成した。COMのと言うわけではなく単にSAFEARRAYのサンプルとなっている。
SAFEARRAYの簡単なサンプル(ソースファイルのみ)
BSTR
簡単に言うとサイズ情報をもった文字列型である。BSTRを扱うためのCComBSTRというクラスもあり、操作はそれほど難しくない。
[VC,ATL]VARIANTによる引数の省略
COMではIDLでVARIANTの引数に[optional]を記述することで、引数の省略をすることが可能になる。その場合、省略された引数のvtフィールドははVT_ERRORになる。(注:MSDNのドキュメントには[optional]はVARIANTかVARIANT*でないと、意味をなさないとある。私が試した限りでは、longパラメータなどに[optional]キーワードをつけても省略可能になるが、このメソッドの中で省略したlongパラメータの値を見ると0になっていた。つまり、メソッドは値0で呼び出されるようだ。)
<idlファイルの例>
[id(12), helpstring("メソッド Connect")] HRESULT Connect([in,optional] VARIANT RemoteHost,[in,optional] VARIANT RemotePort);
<メソッドの記述例>
STDMETHODIMP CSockCtl::Connect(VARIANT RemoteHost, VARIANT RemotePort) { if(RemoteHost.vt == VT_ERROR) // 引数が省略された? // 何らかの処理;
<クライアント(Visual Basic)の記述例>
Dim bytData(16383) As Byte askServer.SendData bytData
ATL側はこんな感じで書く。
<idlファイルの記述例>
[id(6), helpstring("メソッド SendData")] HRESULT SendData([in] VARIANT data);
<メソッドの記述例>
STDMETHODIMP CSockCtl::SendData(VARIANT data) { SAFEARRAY *psa; char* buf; if(data.vt & VT_BYREF) psa = *(data.pparray); else psa = data.parray; SafeArrayAccessData(psa,(void**)&buf); ATLTRACE(_T("cElements = %d\n"),psa->rgsabound->cElements); // bufを使って何らかの処理 SafeArrayUnaccessData(psa);
<クライアント(Visual Basic)の記述例>
Dim bytData() As Byte askServer.GetData bytData Debug.Print "bytData(0) = " & bytData(0) Debug.Print "bytData(1) = " & bytData(1) Debug.Print "Ubound = " & Ubound(bytData)
ATL側ではこんな感じで書く。
<idlファイルの記述例>
[id(4), helpstring("メソッド GetData")] HRESULT GetData([in,out] VARIANT* data,[in,optional] VARIANT type,[in,optional] VARIANT maxLen); data);
<メソッドの記述例>
STDMETHODIMP CSockCtl::GetData(VARIANT* data, VARIANT type, VARIANT maxLen) { char* buf; SAFEARRAY *psa; SAFEARRAYBOUND rgb[1]; ATLTRACE(_T("vt = %u\n"),data->vt); // ちなみにvtの値はVT_UI1 | VT_ARRAY | VT_BYREFです if(data->vt & VT_BYREF) psa = *(data->pparray); else psa = data->parray; ATLTRACE(_T("psa1 = %p\n"),psa); // この時点ではpsaは0 // 0〜9のBYTE配列SAFEARRAY作成 rgb[0].cElements = 10; rgb[0].lLbound = 0; psa = SafeArrayCreate(VT_UI1,1,rgb); ATLTRACE(_T("psa2 = %p\n"),psa); // データへのポインタ取得 SafeArrayAccessData(psa,(void**)&buf); buf[0] = 91; buf[1] = 92; SafeArrayUnaccessData(psa); // VARIANTにSAFEARRAYを格納 if(data->vt & VT_BYREF) *(data->pparray) = psa; else data->parray = psa;
SAFEARRAYの使用するメモリ領域の解放は、クライアント側でVARIANTを解放するときにやってくれる(はず)。
[VC,ATL]コントロールを実行時不可視にする
コントロールを実行時不可視にするには、「ATLオブジェクトの新規作成」で「その他」の「その他ステータス」で「実行時には不可視」をチェックする。が、もう作っちゃってからそんなこと気づいても遅い。それで後から実行時不可視にできないかと思い調べていると、どうもレジストリに登録するときの値の設定だけが影響するようだ。
となると.rgsファイルが臭そうである。デフォルトのプロジェクトを作成し、1つは「実行時には不可視」をチェック、もう1つはチェックしないで.rgsを比較してみた。すると、不可視のほうは'1' = s '132497'、不可視でないほうは'1' = s '131473'で変化している行を見つけた。16進値にすると132497=0x20591、131473=0x20191で1ビットだけ変化している。私は.rgsファイルを開いて以下のような修正を加えてみた。
'1' = s '132497'この方法でうまくいった。この件に関しては他に情報がないので、これで正しいかどうかは分からないが、今のところ問題は発生していない。
<idlファイルの記述例>
[propget, id(10), helpstring("プロパティ RemoteHost")] HRESULT RemoteHost([out, retval] BSTR *pVal); [propput, id(10), helpstring("プロパティ RemoteHost")] HRESULT RemoteHost([in] BSTR newVal);
<メソッドの記述例>
CComBSTR m_RemoteHost; STDMETHODIMP CSockCtl::get_RemoteHost(BSTR *pVal) { // TODO: この位置にインプリメント用のコードを追加してください *pVal = m_RemoteHost.Copy(); return S_OK; } STDMETHODIMP CSockCtl::put_RemoteHost(BSTR newVal) { // TODO: この位置にインプリメント用のコードを追加してください m_RemoteHost = newVal; return S_OK; }
このためCのランタイムを使用する場合は、このマクロを削除しなければならない。(リリースビルドではデフォルトで定義されている)私はAnpsockを作るにあたって、CreateThread関数を使っていることもあり、Cのライブラリは使わないようにしている。
にもかかわらず、あるときリンクでエラーが出てしまった。しかし、いくらコード全体を見直してもCのライブラリは使用していない。仕方がないので、プロパティ、メソッドを全てコメントアウトし、1つずつ生かして地道に調べた。すると、なんとConnectメソッドのところで適当に使っていた_bstr_tがこのエラーの原因であった。
こりゃー正直言って分かんないよね。_bstr_tを削除してやっとリンクが通った。
<件のエラー>
LIBCMT.lib(crt0.obj) : error LNK2001: 外部シンボル "_main" は未解決です
このマクロを使用するためにはそれより先にUSES_CONVERSIONマクロを記述しなければならない。変換のためのリソースを確保するためのもののようなのでループの内側などで記述してはならない。
<呼ぶ側>
USES_CONVERSION; // OLE2Tを使うためのマクロ if(createSocket(OLE2T(m_RemoteHost.m_str),m_nRemotePort) != 0)
<呼ばれる側>
int CSockCtl::createSocket(LPTSTR remoteHost,long nRemotePort) { ATLTRACE(_T("remoteHost = %s nRemotePort = %d\n"),remoteHost,nRemotePort);
<idlファイルの記述例>
[propget, id(4), helpstring("プロパティ LastWriteTime")] HRESULT LastWriteTime([out, retval] DATE *pVal);
<メソッドの記述例>
STDMETHODIMP CFileItem::get_LastWriteTime(DATE *pVal) { // TODO: この位置にインプリメント用のコードを追加してください SystemTimeToVariantTime(&m_SystemTime,pVal); return S_OK; }
Copyright(C) 2001-2003 Allergy Design Office All rights reserved.
[Home]