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

  • 全般的な情報交換

  • いつもお世話になっております。

    標記のとおり、配列の引数を持つ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 自己解決されているようなのでステータスを変更しました。
    2011年7月26日 12:05

すべての返信

  • 試していない状況で気になったのでお聞きします。

    なぜ、VARIANT 型はポインタなのでしょうか?単に in だけなら、ポインタである必要性がなさそうだったので。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年7月26日 13:56
    モデレータ
  • もう 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 相互運用機能。とりわけ「マーシャラ」)がどう扱うのか、について体系的に調べてみてください。

    「配列のマーシャリング」で検索すると、

    http://www.google.co.jp/search?hl=ja&q=%E9%85%8D%E5%88%97%E3%81%AE%E3%83%9E%E3%83%BC%E3%82%B7%E3%83%A3%E3%83%AA%E3%83%B3%E3%82%B0&lr=lang_ja

    などみつかります。

    そのヘンをベースに、わからないところを調べてみてください。


    2011年7月26日 14:28
    モデレータ
  • IDL をインポートした際の C# 側の定義はどうなっていましたか? VARIANT なので何もついていないと思いますが、VT_ARRAY なので void SetPoints([MarshalAs(UnmanagedType.SafeArray), In] points) などのように SafeArray を明示してあげればよいかと思います。

    2011年7月26日 23:40
  • >なぜ、VARIANT 型はポインタなのでしょうか?単に in だけなら、ポインタである必要性がなさそうだったので。
    >
    最初はVARIANT型にしていたのですがうまくいかず、参考にしたURLがポインタで扱っていたのでそれを試してみたものです。

    2011年7月27日 0:31
  •  

    >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~という宣言が必要なんですね。 ただそれをどこに書けばよいのかがよく分かっていません。

     


     


    2011年7月27日 0:55
  • IDL をインポートした際の C# 側の定義はどうなっていましたか?

    >

    COMというのはActiveXで、C#のフォームにそのコントロールを貼り付けただけで、明示的に「IDLのインポート」をおこなったわけではありません。

    内部で自動的にIDLがインポートされているのだと思いますが、すべてのソースを確認しても関数の定義のようなものは見当たりませんでした。

    この場合、マーシャルの定義はどこに書けばよいのでしょうか?

    2011年7月27日 1:01
  • >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 コントロールのカスタムインターフェースのマネージ定義を自分で書く

    とか試すかな?
     

    2011年7月27日 2:29
    モデレータ
  • >「ActiveX コントロールをフォームに貼っただけ」だと、もう書くタイミングがありません。

    そうですか・・・。ということは世にある市販のActiveXコントロールでも、配列を使ったメソッドをC#から利用することはできないということになるのでしょうか?
    IDLは自分で書いていますが、どのように書こうがC#のMarshalAs宣言ができない以上、無意味なんですよね?

    2011年7月27日 2:56
  • >ということは世にある市販のActiveXコントロールでも、配列を使ったメソッドをC#から利用することはできないということになるのでしょうか?

    前回の投稿で回避策を書きましたよ?

    >ActiveX コントロールのカスタムインターフェースのマネージ定義を自分で書く

     

    2011年7月27日 3:20
    モデレータ
  • >前回の投稿で回避策を書きましたよ?

    失礼いたしました。

     

    >ActiveX コントロールのカスタムインターフェースのマネージ定義を自分で書く

    これはよく意味が分かっていませんが、

    融通が利くようにATLで書き直した後、クライアント側のインポートしたときの定義にMarshalAs宣言を追加する

    というようなことでしょうか?

    こちらの都合なのですがATLで作り直すというのはほぼ不可能なんです。


     


    2011年7月27日 4:08
  • > どのように書こうが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/

    2011年7月27日 4:10
  • >回避策としては、既に書かれているように

    >自動生成に頼らないで、自分でインターフェースを書く

    >IDLで型を明記して VARIANT ではない状態にする

    下記は既に試しています。(こういう意味ではない?)

    [id(90)] void SetPoints([in] SAFEARRAY(int) points);

    VARIANTだとC#側の宣言はObjectになり、上記のようにSAFEARRAYだとArrayになるようです。

    しかしどちらもDISP_E_TYPEMISMATCHエラーでメソッドへのアクセスに失敗してしまいます。

     

    >という他に、現状の VARIANT のまま、利用する側で SAFEARRAY を生成するという手もあります。

    これはみてみます。

    2011年7月27日 4:33
  • > 下記は既に試しています。(こういう意味ではない?)
    > [id(90)] void SetPoints([in] SAFEARRAY(int) points);
    > VARIANTだとC#側の宣言はObjectになり、上記のようにSAFEARRAYだとArrayになるようです。

    これでよいです。上記の場合には、short[] と変換可能なはずです。最初の投稿にあったように int[] を与えているとエラーになると思います。 (IDL の int は signed 16bit ですので、C# では short になります)

    2011年7月27日 5:13
  • すみません、大ボケかましていました。

    DISP_E_TYPEMISMATCHエラーはDISPATCHマップのマクロに書く型が間違っていただけでした。

    これを下記のようにVTS_VARIANTに変更したらうまくいきました。

    DISP_FUNCTION(CXXXXCtrl, "SetPoints", SetPoints, VT_EMPTY, VTS_VARIANT)

     

    配列からデータを正しく取り出せることも確認しました。

    本当にどうもすみませんでした。

     

    となると・・・あのMarshalAs宣言は結局なんだったのでしょうか?

    あとSAFEARRAYの場合、型(VTS_VARIANT)のところをどう書けばよいのか分からないです。VTS_SAFEARRAYというのはないようですし・・


    2011年7月27日 5:43
  • >となると・・・あのMarshalAs宣言は結局なんだったのでしょうか?

    tlbimp などに頼らず、COM インターフェースのマネージ定義を自分で書く場合に使います。

    >あとSAFEARRAYの場合、型(VTS_VARIANT)のところをどう書けばよいのか分からないです。VTS_SAFEARRAYというのはないようですし・・

    どこの話ですか? C++ の実装ですか?

     

     

    2011年7月27日 13:10
    モデレータ
  • > あのMarshalAs宣言は結局なんだったのでしょうか?

    自動でインポートした場合、MarshalAsAttribute も自動でついているんじゃないですか?

    > あとSAFEARRAYの場合、型(VTS_VARIANT)のところをどう書けばよいのか分からないです。

    私はATLしか使ったことないので MFC はわかりませんが、Visual Studio のヘルプには MFC では配列は未サポートとありますので、MFC を使った COM/ActiveX では利用できないと思われます。根本的に、利用するフレームワークと通信手段を見直したほうがよいかもしれません。

    2011年7月27日 23:59
  • >自動でインポートした場合、MarshalAsAttribute も自動でついているんじゃないですか?

    ですね。

    でも、自動インポートは ActiveX コントロールの型ライブラリの情報(=IDL や ODL から生成される)から生成されるので、大元の IDL や ODL の情報の「精緻さ」に大きな影響を受けます>質問者の人

    なので「マネージ定義を手書きすれば?」という案も出てくるわけです。

    >根本的に、利用するフレームワークと通信手段を見直したほうがよいかもしれません。

    ActiveX コントロールの方も自分で作っているなら、それが無難ですね。

     


    2011年7月28日 1:25
    モデレータ
  • こんなコードではだめでしょうか?

    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));

    みたいな感じで呼び出す

    gombakoは後でMarshal.ReleaseComObjectを呼び出すために使う
    2013年5月6日 1:48