トップ回答者
COMにおけるWORD*の使い方について

質問
-
いつもお世話になっております。
VS2015UD3で作業しています。
表題の件ですが、COM側でのデータをWORD*で欲しくて下記のように既存のCOMに追加したのですが、
「0x80020005、種類が一致しません」になります。
引数の違いなどいろいろ検索したのですが、解につながりません。
何が間違っていますでしょうか?
初歩の初歩で申し訳ございません。
お忙しいところ恐縮ですが、ご教示いただければ幸いです。
どうぞ宜しくお願いいたします。呼び出し
BOOL ABC( LPCSTR pcszName/*=NULL*/, WORD* pSize/*=NULL*/, WORD* pXyz/*=NULL*/ ) { ... BOOL bRet; try{ bRet = ptrABC->ABC( pcszName, pSize, pXyz ); }catch(_com_error& e){ printf("Error: %s\n", e.ErrorMessage()); //0x80020005、種類が一致しませんになります。pcszNameは通っています。 bRet = FALSE; } return bRet; }
BEGIN_DISPATCH_MAP(CABCDoc, CDocument)
//{{AFX_DISPATCH_MAP(CABCDoc)
...
DISP_FUNCTION(CABCDoc, "ABC", ABC, VT_BOOL, VTS_BSTR VTS_PUI2 VTS_PUI2)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
元odlファイル
[id(10)] VARIANT_BOOL ABC(BSTR szName/*=NULL*/, WORD* pSize/*=NULL*/, WORD* pXyz/*=NULL*/);
生成されたtliファイル
inline VARIANT_BOOL IAbc::ABC ( _bstr_t szName, unsigned short * pSize, unsigned short * pXyz ) { VARIANT_BOOL _result = 0; _com_dispatch_method(this, 0xa, DISPATCH_METHOD, VT_BOOL, (void*)&_result, L"\x0008\x4002\x4002", (BSTR)szName, pSize, pXyz); return _result; }
呼び出し先
BOOL CABCDoc::ABC( LPCSTR pcszName/*=NULL*/, WORD* pSize/*=NULL*/, WORD* pXyz/*=NULL*/ ) { MessageBox(NULL,"#","#",MB_OK | MB_TOPMOST); }
回答
-
どちらかと言えば、VARTYPE 側ではないでしょうか。
double:VT_BYREF | VT_R8 = 0x4000 | 0x0005 = 0x4005
unsigned short:VT_BYREF | VT_UI2 = 0x4000 | 0x0012 = 0x4012
short:VT_BYREF | VT_I2 = 0x4000 | 0x0002 = 0x40020x4002 になっているのは符号付きなので「種類が一致しません」と言われても不思議ではないかと思っています。
MFC の oledisp1.cpp の CCmdTarget::PushStackArgs では、VariantChangeType を使っていますが、これに VT_I2 | VT_BYREF のものから VT_UI2 | VT_BYREF に変換しようとすると「種類が一致しません」と返すので、そういったことかと思っています。 -
#importでno_dual_interfacesを指定してるなら外してみるか、SHORT*に変えてみてください。
https://msdn.microsoft.com/ja-jp/library/298h7faa(v=vs.140).aspx
うる覚えですが、_com_dispatch_methodはunsigned pointerと相性が悪かった(=必ずsigned pointerのシグネーチャを生成する)ような気がします。
私の同僚は、#importを元に手書きでヘッダーを書いているという話をしていました。試しにAzuleanさんのご指摘の通りに\4012で書き直したヘッダーを書いてみましたが動作しました。
ちなみに、VARIANT_BOOLをBOOLで受けているので、VARIANT_TRUE((short)-1)が返るとTRUE((int)1)にはならないので、お忘れなく。
なお、#importのCOMマーシャラーはBSTRはBSTR (ただし、既定で_bstr_tに変換)、VARIANT_BOOLはVARIANT_BOOLになるようですが、MFCのCOMマーシャラーはちょっと違うので戸惑います。
https://msdn.microsoft.com/ja-jp/library/087e0w00.aspx
VT_BOOL, VTS_BOOL, VTS_PBOOLは、BOOL(=int), BOOL, BOOL*
これは一瞬バグ?と思ってしまいますが、BOOLがバリバリの現役だった時代の名残です。
勿論ですが、IDLのCOMの型はVARTYPEの通り、VARIANT_BOOLですが、利用側(COMクライアント)でMFCのラッパーを作るとリファレンス通りBOOLになります。
VT_BSTR, VTS_BSTR, VTS_PBSTRは、BSTR、LCPTSTR (LCPSTR or LCPOLESTR = LCPWSTR), BSTR*
となっていて、当然ですがLCPTSTRを使用しないとコンパイラオプションの変更でミスマッチが発生する可能性があります。
勿論、IDLのCOMの型はVARTYPEの通り、BSTRです。
ですから、利用側(COMクライアント)でMFCラッパーを作ると、利用側のコンパイラオプションによっては、LCPSTR or LCPOLESTRのどちらかになり、実装側(COMサーバ)と異なる型になるケースもあり得ます。
よく、IDLの型をC++の型にしないといけないと思ったり、実装側と利用側のラッパーが同じ型と思ってしまう人がいるので、この辺は注意が必要です。
なお、MFCのInvokeHelperはVTSをパラメータに指定するので、tlbからMFCクラスを生成してラッパを作ればうまくいくかもしれません。
-
クライアント側とサーバー側で値が違っても動くということは中でいい感じで処理してくれるのかな。
どこかに情報がありそうですが、検索キーワードが思い浮かばないな。
値の違いですが、コンパイラバージョンについては特にないと思いますよ。少なくともCOMはABIの規格ですし、IDispatch はそれを動的に解決できるようにした仕組みなので、この部分はコンパイラのバージョンなどで変わるということはありません。
あるとすれば、文字コードの違い。
.tli 側は wchar_t* となっていますが、MFC側は、char* を使っていますよね。
この辺りの違いを自動的に変換するなどがあるのかもしれません。
ただ、それが解決につながる何かというわけではないので何とも言えませんが。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
- 回答の候補に設定 栗下 望Microsoft employee, Moderator 2016年12月6日 1:59
- 回答としてマーク SHIN109 2016年12月6日 2:51
すべての返信
-
関数名以外の部分は無変更のままコピーしたのであれば、変換エラーは WORD <-> unsigned short ではなく、LPCSTR <-> BSTR の部分です。
ABC() を実装する側は、LPCSTR ではなく、CABCDoc::ABC( BSTR pcszName, ... ) としなければなりません。
呼び出す側のほうは、_bstr_t でラップしているのでこの限りではありません((BSTR)szName で適切に処理されている)。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
-
とっちゃん 様
早速のレスありがとうございました。
前回に引き続き恐縮です。いつもお世話になります。ありがとうございます。
早速トライしてみました。LPCSTRからBSTRに変更してみました。結果は同じで「0x80020005、種類が一致しません」でした。
BSTR szNameを除去した、WORD*のみのものも作ってみたのですが、それでも結果は同じでした。
因みに、( BSTR, WORD, WORD )の別の関数があるのですが、それは通って、使用できます。
WORDとWORD*だけの差なのですが、何か問題があるのでしょうか?
どうぞ宜しくお願いいたします。 -
ちょっと、定義文を見直してみました。
_com_dispatch_method() の、L"\x0008\x4002\x4002", の部分は手を入れてないですよね?
この部分、VTS_PUI2 とかの文字列を連結した値になるはずなんですが。。。もしそうだとすると、MFC側の定義は L"\x0008\x0052\x0052" が正しい値になります。
これで試してみてうまくいく場合は、\x4002がどこで定義されているかを確認するとか、MFCとの違いがなぜかを調べるということになると思います(こんなの今まで違ってることすら気づかなかったけど)。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
-
とっちゃん 様
レスありがとうございました。
>_com_dispatch_method() の、L"\x0008\x4002\x4002", の部分は手を入れてないですよね?
はい。それでL"\x0008\x0052\x0052"に変更してビルド(リビルドは\x4002に戻るので)&トライしてみましたが、やはり同じでした。
VTS_PUI2、定義の参照先はafxdisp.hで、それによると#define VTS_PUI2 "\x52" // a 'WORD*'
でしたので、???とは思っていたのですが、自動生成信じておりました。
別関数にVTS_PR8(double*)を使っている(通る)ものがあり、それは#define VTS_PR8 "\x45" // a 'double*'
ですが、生成されたtliファイルでは、L"\x4005"です。
コンパイラバージョンとかってあるでしょうか?
どうぞ宜しくお願いいたします。
- 編集済み SHIN109 2016年12月5日 10:21
-
クライアント側とサーバー側で値が違っても動くということは中でいい感じで処理してくれるのかな。
どこかに情報がありそうですが、検索キーワードが思い浮かばないな。
値の違いですが、コンパイラバージョンについては特にないと思いますよ。少なくともCOMはABIの規格ですし、IDispatch はそれを動的に解決できるようにした仕組みなので、この部分はコンパイラのバージョンなどで変わるということはありません。
あるとすれば、文字コードの違い。
.tli 側は wchar_t* となっていますが、MFC側は、char* を使っていますよね。
この辺りの違いを自動的に変換するなどがあるのかもしれません。
ただ、それが解決につながる何かというわけではないので何とも言えませんが。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
- 回答の候補に設定 栗下 望Microsoft employee, Moderator 2016年12月6日 1:59
- 回答としてマーク SHIN109 2016年12月6日 2:51
-
どちらかと言えば、VARTYPE 側ではないでしょうか。
double:VT_BYREF | VT_R8 = 0x4000 | 0x0005 = 0x4005
unsigned short:VT_BYREF | VT_UI2 = 0x4000 | 0x0012 = 0x4012
short:VT_BYREF | VT_I2 = 0x4000 | 0x0002 = 0x40020x4002 になっているのは符号付きなので「種類が一致しません」と言われても不思議ではないかと思っています。
MFC の oledisp1.cpp の CCmdTarget::PushStackArgs では、VariantChangeType を使っていますが、これに VT_I2 | VT_BYREF のものから VT_UI2 | VT_BYREF に変換しようとすると「種類が一致しません」と返すので、そういったことかと思っています。 -
#importでno_dual_interfacesを指定してるなら外してみるか、SHORT*に変えてみてください。
https://msdn.microsoft.com/ja-jp/library/298h7faa(v=vs.140).aspx
うる覚えですが、_com_dispatch_methodはunsigned pointerと相性が悪かった(=必ずsigned pointerのシグネーチャを生成する)ような気がします。
私の同僚は、#importを元に手書きでヘッダーを書いているという話をしていました。試しにAzuleanさんのご指摘の通りに\4012で書き直したヘッダーを書いてみましたが動作しました。
ちなみに、VARIANT_BOOLをBOOLで受けているので、VARIANT_TRUE((short)-1)が返るとTRUE((int)1)にはならないので、お忘れなく。
なお、#importのCOMマーシャラーはBSTRはBSTR (ただし、既定で_bstr_tに変換)、VARIANT_BOOLはVARIANT_BOOLになるようですが、MFCのCOMマーシャラーはちょっと違うので戸惑います。
https://msdn.microsoft.com/ja-jp/library/087e0w00.aspx
VT_BOOL, VTS_BOOL, VTS_PBOOLは、BOOL(=int), BOOL, BOOL*
これは一瞬バグ?と思ってしまいますが、BOOLがバリバリの現役だった時代の名残です。
勿論ですが、IDLのCOMの型はVARTYPEの通り、VARIANT_BOOLですが、利用側(COMクライアント)でMFCのラッパーを作るとリファレンス通りBOOLになります。
VT_BSTR, VTS_BSTR, VTS_PBSTRは、BSTR、LCPTSTR (LCPSTR or LCPOLESTR = LCPWSTR), BSTR*
となっていて、当然ですがLCPTSTRを使用しないとコンパイラオプションの変更でミスマッチが発生する可能性があります。
勿論、IDLのCOMの型はVARTYPEの通り、BSTRです。
ですから、利用側(COMクライアント)でMFCラッパーを作ると、利用側のコンパイラオプションによっては、LCPSTR or LCPOLESTRのどちらかになり、実装側(COMサーバ)と異なる型になるケースもあり得ます。
よく、IDLの型をC++の型にしないといけないと思ったり、実装側と利用側のラッパーが同じ型と思ってしまう人がいるので、この辺は注意が必要です。
なお、MFCのInvokeHelperはVTSをパラメータに指定するので、tlbからMFCクラスを生成してラッパを作ればうまくいくかもしれません。
-
とっちゃん 様、Azulean 様、tmori3y2 様
皆さま、いつもお世話になっております。ありがとうございます。
レスありがとうございました。
tliファイルに表示されているコンパイラバージョンとヘルプ>>バージョン情報の内容の違いまで疑ったりしたのですが、とっちゃん様がおっしゃる通りやはり関係がないようでした。失礼いたしました。
お伺いしながら、tmori3y2様のおっしゃるshort*で通すことにいたしました。
今回取るべき値が小さいので必ずしもunsignedである必要がないこともあり、Azulean様のおっしゃる
>0x4002 になっているのは符号付きなので「種類が一致しません」と言われても不思議ではないかと思っています。
と同様なことが、調べる中での記述に私もひっかかってもいて、でもまさかちゃんと定義もあるから何か方法があるのではと思っていましたが、
tmori3y2様のおっしゃる
>_com_dispatch_methodはunsigned pointerと相性が悪かった(=必ずsigned pointerのシグネーチャを生成する)ような
とのことにて通すことにいたしました。
今回はこれが曲者ということでしょうか。
>よく、IDLの型をC++の型にしないといけないと思ったり、実装側と利用側のラッパーが同じ型と思ってしまう人がいるので、この辺は注意が必要です。
取得する際の型がWORDであったことで出来るだけ同じに値をやり取り出来ればとスタートしたのですが、やはり柔軟性は必要ですね。
またこういった知識も裏付けとして大事ですね。
しかし_com_dispatch_methodでもunsigned pointerは普通に欲しいような気がいたしますが、、、。
皆さま、ご丁寧に詳細にご教示いただき、ありがとうございました。
これにつながったそれぞれのご回答に「回答としてマーク」させていただきます。
ありがとうございました。 -
> #importでno_dual_interfacesを指定してるなら外してみるか、SHORT*に変えてみてください。
解決済みとされましたが、補足します。
#import "Abc.tlb" no_namespace no_dual_interfaces
のとき、_com_dispatch_methodで使用できる型が以下に書かれています。
https://msdn.microsoft.com/ja-jp/library/cc446916.aspx
https://msdn.microsoft.com/ja-jp/library/cc447161.aspxこの辺は、VB 6などとの相互運用のため、と思われます。
しかし、_com_dispatch_methodでunsignedが使えないかというとそうではなくて、#importがシグネーチャはVB互換のまま、キャストしないコードを生成しているだけなので、手書きで書き換えてunsignedで動くようにしても良いですが、VBなどの一部の言語との相互運用性が失われることになります。
一方、コメントに書いたように、以下のようにすると、デュアルインターフェースが有効になり、カスタムインタフェースの呼び出しができるため、unsignedも使用できます。
#import "Abc.tlb" no_namespace
inline VARIANT_BOOL IAbc::ABC ( _bstr_t szName, unsigned short * pSize, unsigned short * pXyz ) {
VARIANT_BOOL _result = 0;
HRESULT _hr = raw_ABC(szName, pSize, pXyz, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _result;
}
カスタムインターフェースは便利ですが、下手をすると言語によっては使用できない/し難いことにもなるので、何でもありという訳ではないです。(少なくともCOMと互換性のない型を使用すべきではない)
今回のようなことを悩むくらいなら、修正が可能な範囲で、IDispatchの互換のsigned型を使用する努力はしても良いかと思いますし、複数人で開発しているなら、若手への入れ替わりを考えて、最初からそのような型の使用を考慮をする努力も必要かな?と思います。
- 編集済み tmori3y2 2016年12月6日 17:44