質問者
COMオブジェクトメソッドの配列引数にC#からアクセスしたい

全般的な情報交換
-
いつもお世話になっております。
標記のとおり、配列の引数を持つCOMオブジェクトメソッドがあり、それにC#からアクセスしたいのですが、うまくいきません。
下記URLを参考につくりました。
http://support.microsoft.com/kb/218454/ja
[COM側 *.idlの記述]
[id(90)] void SetPoints(VARIANT* points);
[COM側 *.cppの記述]
void XXXXCtrl::SetPoints(VARIANT* points) { if (points->vt & VT_ARRAY) { } }
[C#側の記述]int[] pnt = new int[4]; XXXXCtrl1.SetPoints(pnt);
これでビルドは成功しますが実行すると次のエラーが発生します。
'System.Runtime.InteropServices.COMException'のハンドルされていない例外がmscorlib.dllで発生しました。
追加情報:種類が一致しません。(HRESULTからの例外:0x80020005(DISP_E_TYPEMISMATCH))
どうすればうまくいくでしょうか? 調べても良く分からず・・・どなたかご教授いただけないでしょうか?
- 種類を変更済み 山本春海 2011年8月1日 8:46 自己解決されているようなのでステータスを変更しました。
すべての返信
-
もう C++ のコード書いて試す気が起きないので、直接の回答ではありません。
以下、参考になれば幸いです。
>下記URLを参考につくりました。
これはレガシ ASP で COM を扱う場合の話です。
そもそもの原理を知っていれば参考になる要素もありますが、「C# (.NET) で COM の配列を扱う」というケースにどんぴしゃな内容ではありません。
>[COM側 *.idlの記述]
IDL に in/out の記述がないのは何故でしょう?
「配列」に格納される値の型の記述がないのは何故でしょうか?
仮に、C# から COM に int の配列を引き渡したいなら [int] SAFEARRAY(int) points とかになりませんか?
C++ や VB6 などアンマネージで固められたシステムならそれでも動作するかもしれませんが、.NET と絡める場合には、こういった(人間には一見)ささいと思われる情報が意外と重要です。
>どうすればうまくいくでしょうか? 調べても良く分からず・・・どなたかご教授いただけないでしょうか?
知るべきことは比較的広範囲です。「配列」を IDL でどう表現するのか、それを C++ でどのように書き下すのか、←を C# (実際には .NET のランタイムの COM 相互運用機能。とりわけ「マーシャラ」)がどう扱うのか、について体系的に調べてみてください。
「配列のマーシャリング」で検索すると、
などみつかります。
そのヘンをベースに、わからないところを調べてみてください。
- 編集済み 渋木宏明Moderator 2011年7月26日 14:34 推敲
-
>IDL に in/out の記述がないのは何故でしょう?
>COMというのはActiveXなんですがMFC ActiveX ControlWizardで作成してできたidlにはin/outの記述がないんです。>「配列」に格納される値の型の記述がないのは何故でしょうか?
>仮に、C# から COM に int の配列を引き渡したいなら [int] SAFEARRAY(int) points とかになりませんか?
>お察しのとおりこのあたりがよく分かっていませんが、参考にしたWebページのサンプルがVARIANTを使っていたのでとりあえずそれで試しています。[id(90)] void SetPoints(SAFEARRAY(int) points);
みたいにすべきなんでしょうか?
>知るべきことは比較的広範囲です。「配列」を IDL でどう表現するのか、それを C++ でどのように書き下すのか、←を C# (実際には .NET のランタイムの COM 相互運用機能。とりわけ「マーシャラ」)がどう扱うのか、について体系的に調べてみてください。>
やはりC#側にMarshalAs~という宣言が必要なんですね。 ただそれをどこに書けばよいのかがよく分かっていません。
-
>COMというのはActiveXなんですがMFC ActiveX ControlWizardで作成してできたidlにはin/outの記述がないんです。
IDL は Wizard 任せなわけですね。
Wizard が自動生成した IDL でも、戻り値が必要なところには retval くらい書いてありますよね?
in, out くらいなら、自分で追記しても問題ないんじゃないかな?
場合によっては性能に影響するので、Wizard が使えなくなるなどの弊害がなければ書いた方ががいいんじゃないかと思います。
>みたいにすべきなんでしょうか?
自分で IDL を書いているのであれば。
>やはりC#側にMarshalAs~という宣言が必要なんですね。 ただそれをどこに書けばよいのかがよく分かっていません。
「ActiveX コントロールをフォームに貼っただけ」だと、もう書くタイミングがありません。
また、元の IDL を自分で書いていないとすると、IDE が自動生成する「ActiveX コントロールが公開するカスタムインターフェースのマネージ定義」の品質を高める線も望みが薄いです。
じぶんなら
・ActiveX コントロールを、もっと融通の利く ATL ベースで書き直す
・ActiveX コントロールのカスタムインターフェースのマネージ定義を自分で書くとか試すかな?
-
> どのように書こうがC#のMarshalAs宣言ができない以上、無意味なんですよね?
「MarshalAs の設定を書き換えられない」と「配列を使ったメソッドをC#から利用することはできないということ」はイコールではありません。
一般的には、配列を使ったメソッドは配列を引数にとりますので、そのようなメソッドであればタイプライブラリ等から自動的に生成されたラッパーで十分動作すると思います。(自動生成に制約があるかどうかは確認していません) C#(.NET) と COM の間では、やりとりを行うデータにマーシャリングを必要としているため、int なら int の、文字列ならなら文字列の、配列なら配列の渡し方というものがあります。通常は、IDL やタイプライブラリによって表現されるインターフェース上にこれらの型が入っているので、一般的な COM/ActiveX では何の加工もなく自動的に利用できるものがほとんどではないかと思います。
今回のように、「宣言上は VARIANT で何でも渡せる」「実装上は配列であることを期待している」という宣言に対して、自動インポートされる対象となる宣言は、VARIANT で何でも渡せるというものです。このため、自動処理では何もしてくれないことになります。(参照されているサンプルがなぜ VARIANT なのかは、既にほかの方が書いているように、相手側が C# ではなく、VARIANT を要求する VBScript だからです)
回避策としては、既に書かれているように
- 自動生成に頼らないで、自分でインターフェースを書く
- IDLで型を明記して VARIANT ではない状態にする
という他に、現状の VARIANT のまま、利用する側で SAFEARRAY を生成するという手もあります。
http://social.msdn.microsoft.com/Forums/en-US/clr/thread/6641abfc-3a9c-4976-a523-43890b2b79a2/
-
>回避策としては、既に書かれているように
>自動生成に頼らないで、自分でインターフェースを書く>IDLで型を明記して VARIANT ではない状態にする
下記は既に試しています。(こういう意味ではない?)
[id(90)] void SetPoints([in] SAFEARRAY(int) points);
VARIANTだとC#側の宣言はObjectになり、上記のようにSAFEARRAYだとArrayになるようです。
しかしどちらもDISP_E_TYPEMISMATCHエラーでメソッドへのアクセスに失敗してしまいます。
>という他に、現状の VARIANT のまま、利用する側で SAFEARRAY を生成するという手もあります。
これはみてみます。
-
-
すみません、大ボケかましていました。
DISP_E_TYPEMISMATCHエラーはDISPATCHマップのマクロに書く型が間違っていただけでした。
これを下記のようにVTS_VARIANTに変更したらうまくいきました。
DISP_FUNCTION(CXXXXCtrl, "SetPoints", SetPoints, VT_EMPTY, VTS_VARIANT)
配列からデータを正しく取り出せることも確認しました。
本当にどうもすみませんでした。
となると・・・あのMarshalAs宣言は結局なんだったのでしょうか?
あとSAFEARRAYの場合、型(VTS_VARIANT)のところをどう書けばよいのか分からないです。VTS_SAFEARRAYというのはないようですし・・
-
> あのMarshalAs宣言は結局なんだったのでしょうか?
自動でインポートした場合、MarshalAsAttribute も自動でついているんじゃないですか?
> あとSAFEARRAYの場合、型(VTS_VARIANT)のところをどう書けばよいのか分からないです。
私はATLしか使ったことないので MFC はわかりませんが、Visual Studio のヘルプには MFC では配列は未サポートとありますので、MFC を使った COM/ActiveX では利用できないと思われます。根本的に、利用するフレームワークと通信手段を見直したほうがよいかもしれません。
-
>自動でインポートした場合、MarshalAsAttribute も自動でついているんじゃないですか?
ですね。
でも、自動インポートは ActiveX コントロールの型ライブラリの情報(=IDL や ODL から生成される)から生成されるので、大元の IDL や ODL の情報の「精緻さ」に大きな影響を受けます>質問者の人
なので「マネージ定義を手書きすれば?」という案も出てくるわけです。
>根本的に、利用するフレームワークと通信手段を見直したほうがよいかもしれません。
ActiveX コントロールの方も自分で作っているなら、それが無難ですね。
- 編集済み 渋木宏明Moderator 2011年7月28日 1:29 推敲
-
こんなコードではだめでしょうか?
private object toVBArray(object[] arry, List<object> gomibako)
{
if(arry == null) {
return null;
}
var dictype = Type.GetTypeFromProgID("Scripting.Dictionary");
var dic = Activator.CreateInstance(dictype);
gomibako.Add(dic);
for(var i = 0; i < arry.Length; ++i) {
object obj = null;
if(arry[i] is object[]) {
obj = toVBArray((object[])arry[i], gomibako);
}
else {
obj = arry[i];
dictype.InvokeMember("Add", System.Reflection.BindingFlags.InvokeMethod,
null, dic, new object[] { i, obj });
}
return dictype.InvokeMember("Items", System.Reflection.BindingFlags.InvokeMethod, null, dic, null);
}
みたいなメソッドを用意しておいて
var gomibako = new List<object>();
int[] pnt = new int[4];
XXXXCtrl1.SetPoints(toVBArray(pnt, gomibako));みたいな感じで呼び出す
- 編集済み ぷろたん 2013年5月6日 1:49