none
C#からOCXへポインタを渡す方法を教えてください。

    質問

  • C#は初心者です。どなたか教えてください。

     

    ActiveX(OCX)でポインタを渡すメソッドを用意したのですが、C#側からどのようにデータポインタを

    渡すのか分かりません。

     

    たとえばOCXで

     

    long SendData(byte *data, int len);

     

    のようなメソッドを用意します。

     

    これはC#でOCXを使用すると

     

    int SendData(ref byte, int len);

     

    で定義されています。

     

    この状態でC#のデータを渡すときに

     

    byte[] data = new byte[100];

     

    // データを読み込むなり編集するなりする。

     

    AxSendOcx.SendData(ref data[0], 100);

     

    としてみたのですが、うまくデータが受け渡せませんでした。

     

    何か良い方法があったら教えてください。

     

    2008年4月22日 5:03

回答

  • ありがとうございました。

     

    OCXのメソッドのパラメータをVARIANTで定義することにしました。

    VARIANTがByteArrayかチェックして、ポインタを取り出すように変更しました。

     

    それでうまくいくようになりました。

     

    2008年4月23日 1:33

すべての返信

  •  としぼ@jfe-tech さんからの引用

    long SendData(byte *data, int len);

    ポインタではなく、SafeArrayで実装して下さい。

     

    byte*はC/C++では配列とも解釈できますが、COM(ActiveX)の世界では単なるbyte型の参照でしかなく、配列としての意味を持ちません。

    そのため、SafeArrayで渡すことが必要になります。

     

    http://msdn2.microsoft.com/ja-jp/library/z6cfh6e6(vs.80).aspx#cpcondefaultmarshalingforarraysanchor4

     

     

    #length_is属性は微妙かなぁ…。ref byteとしてインポートされるのは代わりありませんし。

    2008年4月22日 14:19
    モデレータ
  • ありがとうございました。

     

    OCXのメソッドのパラメータをVARIANTで定義することにしました。

    VARIANTがByteArrayかチェックして、ポインタを取り出すように変更しました。

     

    それでうまくいくようになりました。

     

    2008年4月23日 1:33
  • 追加の質問です。

     

    byte[] のデータを受け渡してOCXで見ることは出来たのですが、

    C#で定義した byte[]のワークにOCXから書き込むことが出来ません。

     

    OCXのメソッドを

    long Write(VARIANT buff);

     

    と定義してC#からbyte[]のワークを渡してsafearrayデータに

    書き込んでみたのですがC#内のバッファには更新できませんでした。

     

    何がいけないのでしょうか?

     

    2008年4月25日 12:07
  •  としぼ@jfe-tech さんからの引用

    と定義してC#からbyte[]のワークを渡してsafearrayデータに

    書き込んでみたのですがC#内のバッファには更新できませんでした。

    そのOCX(COM)にはコピーされたバッファが渡されます。

    そして、引数にはin、in/out、outといった属性があります。

     

    inであれば渡すだけと解釈され、その中身が書き換わったことを認識しませんし、C#側の配列(マネージヒープ)に反映しません。

    in, outであれば渡した後、バッファからC#側の配列に書き戻します。

    outだとC#から配列のデータは渡されず、COMが生成したデータを受け取ることのみに働きます。

     

    このあたりは相互運用マーシャリングやCOMマーシャリングが働いています。

    少なくとも相互運用マーシャリングについてドキュメントを読んでみて下さい。

    http://msdn2.microsoft.com/ja-jp/library/04fy9ya1.aspx

    2008年4月25日 13:52
    モデレータ
  • あまりこの事に時間をかけられないので、ドキュメントをきちんと理解する余裕がありません。

     

    具体的に何をすればよいのでしょうか?

     

    OCX側:

    メソッドのパラメータとして指定できる種別は限られています。

    何を使用すればよいのでしょうか?

    long Read(VARIANT *buff);

    のように VTS_PVARIANTだとC#側では ref object として参照されるようになります。

     

    C#側:

    byte[] buff = new byte[100];

    として定義したワークを上記定義のメソッドに渡そうとするとコンパイルエラーになります。

    (byte[] を ref object に変換できない)

    ocx.Read(buff);

     

    このあたりがまだ良く分かりません。よろしくお願いします。

     

    2008年5月7日 3:38
  •  としぼ@jfe-tech さんからの引用

    あまりこの事に時間をかけられないので、ドキュメントをきちんと理解する余裕がありません。

    業務や研究でコードを書かれているのであれば、誰がその責任を負うのですか?

    例えば、私の理解が間違っていたとしても、責任はコードを書いたあなたにのしかかりますが、問題ないのですか?

     

    このあたりの仕組みの理解を疎かにすると、特定の状況下で変な動きをする、アクセス保護違反が起きるといった問題を生む温床になりますので、調査の時間を取るべきだと思います。

     

     としぼ@jfe-tech さんからの引用

    具体的に何をすればよいのでしょうか?

    上の私の発言を踏まえて頂いて上で、修正方法を希望するのであれば、まず、現状のビルド環境・プロジェクトの種別・コードを具体的に説明して下さい。

    VCのバージョンも不明ですし、MFCなのかATL(属性の有無)なのかも分かりません。

    その状況下で、「具体的に何をすれば良いか」とは言えません。

     

     としぼ@jfe-tech さんからの引用

    メソッドのパラメータとして指定できる種別は限られています。

    何を使用すればよいのでしょうか?

    ActiveXコントロールである以上、IDispatchを実装する形になりますので、VARIANT型で表現できる型の範囲になるのは必然です。

    C#で利用するに当たってはタイプライブラリに適切な型情報が格納されている必要があるでしょう。

     

    ActiveXやCOMは便利なように見えて、落とし穴がたくさんあります。

    仕組みを理解する時間がない、理解する意欲がないではいずれ問題を生む可能性が残りますよと書き置きをしておきます。

    2008年5月7日 14:49
    モデレータ
  • 昔は Window XP-SP3(32) + VS2005 でやっていました。

    この時は途中で飛んでしまいました。済みません。

    現在 Win7-sp1(64) + VS2010 で MFC の OCX を作成し、C#のフォームアプリからOCXを使用しています。

    --------

    質問1:

    COM のデータタイプ説明の中では、SAFEARRAY の項目があるのですが、実際に OCX でメソッドのパラメータにするには

    VARIANT でするしか無いんでしょうか?

    直接 SAFEARRAY タイプを指定できればいいんですが。

    また、VARIANTの構造体の中には SAFEARRAY が有りますが、これはどのような時に使えるのでしょうか?

    質問2:

    C#で作成した byte[] 内に、OCX側から書き込めるようにするには、C#側で、out 属性を付ければいいんでしょうか?

    いまさらですが、よろしくお願いします。

    2013年3月7日 4:47
  • 実験していない状態の記憶で書きますので間違っているかもしれません。

    メソッドの追加ウィザードからは SAFEARRAY を選べませんので VARIANT で追加することになったはずです。
    その後、IDL を自分で書き換えて SAFEARRAY であることの明示や in, out の指定がいるはずです。

    ちなみに、「C# 側で out 属性をつける」という発想は、考え方を間違えています。
    ActiveX を参照した時点で .NET のアセンブリが自動生成されますので、その関数に対して out 属性はつけられないですよね。
    このため、自動生成されるアセンブリが正しく out 属性を持てるように、ActiveX 側に属性を持たせないといけません。

    IDL とか、SAFEARRAY とか、VARIANT とかの理解が浅い状態だと、たぶん変なことになるので、その周りを勉強してください。
    ただ、今から和書で学ぶのは厳しいと思うので、英語圏を含めた Web サイトをいろいろと読んでいただかないといけないかと思います。
    (本当に ActiveX で実現しないといけないのでしょうか?要件次第では、C++/CLI でラップする方がかんたんかもしれません)

    2013年3月7日 13:48
    モデレータ
  • 回答ありがとうございます。

    SAFEARRAYに関しては、何か裏ワザがあるのかな・・・と淡い期待をして聞いてみただけです。

    やっぱりVARIANTしかないですよね。

    idl 内で in, out を付加するのは理解できました。

    satype という attribute も有るようですが、使ってみてもコンパイルエラーにしかなりませんでした。

    もう少し勉強してみます。

    でも、C#で定義した byte[] 内に、データを書き込みたいだけなのに、思ったようにはいかないものですね。

    2013年3月8日 3:56
  • 自己レスです。

    これまでに分かったことを、少しまとめておきます。

    ------------------------

    OCX <-> C#間で byte[] データを伝えあう方法。

    1.C#のデータを伝える。
     OCX 側でメソッドを以下のようにする。
      [id(1)] Test1(VARIANT varData)
      [id(1)] Test1([in] VARIANT varData) でも同じ
     そうすれば、C#側では
      ocx.Test1(object varData)
     として見える。そして、
      byte [] bData = new byte[100];
       ...(データ設定)
      ocx.Test1(bData);
     とすれば、C#内のデータをOCXに伝えられる。
     この場合、OCX側でこのバッファの中を更新してもC#側には伝わらない。
     C#側からOCXに伝えるときにコピーされて伝わっているから、コピー先を幾ら変えても無駄である。

    2. OCX 側のデータをC#側に通知する。
     OCX 側に在るデータを C# 側に byte [] として通知するには、イベントとして通知すれば良い。
     OCX 側でイベントを以下のように定義する。
      [id(1)] Event1(VARIANT *varData)
     C# 側のイベント関数内で
      byte [] bData = (byte [])e.varData;
     とすれば、OCX 内のデータをC#側で操作することが出来る。
     未確認だが、多分これもコピーされたデータが伝わっていると思われる。
     C#側でこのbyte[]内を変更しても、OCX で保持しているバッファは更新されないだろう。

    3.C#側で作成した byte [] を OCX で更新する。
     C# 側で out, ref の属性を持つようにメソッドを定義すればよいと思い、OCX 側でメソッドを以下のようにしてみた。
      [id(2)] Test2([out] VARIANT varData)
     これは out 属性はポインタでないといけない様で、OCXでコンパイルエラーになった。それで、以下に変更してみた。
      [id(2)] Test2([out] VARIANT *varData)
     そうすれば、C#側では
      ocx.Test2(out object varData)
     として見える。そして、
      byte [] bData = new byte[100];
      ocx.Test2(out bData);
     としてみたが、C#側でコンパイルエラーになる。 (byte[] を object に変換できない)
      byte [] bData = new byte[100];
      object obj = bData;
      ocx.Test2(out obj);
     としてみたら、コンパイルは成功したが、実行時に型変換エラーのexception が発生する。
     空の object ならどうなるか試してみた。
      object obj = new object;
      ocx.Test2(out obj);
     でもやはり実行時に exception が発生する。

     今度は、OCX 側で、以下のように定義してみた。
      [id(2)] Test2([in, out] VARIANT *varData)
     そうすれば、C#側では
      ocx.Test2(ref object varData)
     として見える。そして、C#側で out 指定時と同じことをやってみたが、結果は全く同じであった。
     (C#でパラメータ指定は ref に変更)

      OCX 側のディスパッチマップ内で、
        DISP_FUNCTION_ID(Ctrl, "Test2", dispidTest4, VT_I4, VTS_VARANT)
      とVTS_PVARIANT を無理矢理変え、内部関数も(.idl ファイル以外)
        LONG Test2(VARIANT &varData)
      に変更してみると、C# 側で変換エラーは発生しなくなった。
      OCX 側には バイト配列の参照(BYREF)として伝わっている。
      やった・・・と思い、データ内をOCX側で更新してみたが、やっぱりC#側のbyte[]は書き変わらなかった。

    4. 現状での結論
     C# から OCX に byte[] データを伝えるには SAFEARRAY で指定したパラメータ(バイト配列のVARIANT)のメソッドで伝える。

     OCX から C# に byte[] を伝えるにはイベントのパラメータに SAFARRAY(バイト配列のVARIANT)を指定して伝える。

     しかないようだ。
     直接C# の byte[] に OCX から書き込むのは無理のように思われる。

     伝達時にこれだけコピーが発生していれば、大量のデータを伝えあうのは、メモリーが余分に必要で、処理にも時間が掛かってしまうだろう。
     ミリ秒単位の速度制限が在るような処理には C# は向かないのかもしれない。

    以上。

    2013年3月10日 6:11
  • 追加です。

    3.C#側で作成した byte [] を OCX で更新する。

    ですが、直接の目的は果たせませんでしたが、以下の方法でメソッドでOCX側のデータを伝えることができました。

    MFCのイベント追加で VARIANT * のパラメータを指定する。

    OCX側の .idl ファイル内で

        [id(2)] Test2([in, out] VARIANT *varData)

    と定義を変更する。 OCX 側のディスパッチマップ内で、
       DISP_FUNCTION_ID(Ctrl, "Test2", dispidTest4, VT_I4, VTS_VARANT)
     とVTS_PVARIANT を無理矢理変え、内部関数も(.idl ファイル以外)
        LONG Test2(VARIANT &varData)

    とする。そうすれば、C#側では
      ocx.Test2(ref object varData)
    として見える。そして、C#側で
      byte [] bData = new byte[100];
      object obj = bData;
       ocx.Test2(ref obj);

    として使用する。

    OCX側の処理を以下のようにすると、OCX側のbyte[] 配列のデータがC#側に伝わる。

    LONG Test2(VARIANT &varData)

    {

     SAFEARRAY  *pSA = NULL;
     SAFEARRAYBOUND bd;
     byte    HUGEP *p;

     bd.lLbound = 0;
     bd.cElements = 12;
     pSA = SafeArrayCreate(VT_UI1, 1, &bd);
     SafeArrayAccessData(pSA, (void HUGEP **)&p);
     memcpy(p, "12345", 5);
     pSA->pvData = (LPVOID)p;
     SafeArrayUnaccessData(pSA);
      *pVarData.pparray = pSA;

    }

    C# 側で (byte[])obj として参照すれば、OCX側で設定した内容が見ることができる。

    問題点は、

    1. 標準のメソッド作成ではできなく、後で手動で加工する必要がある。

      特にパラメータの属性を書き換えているので、心配である。

      .IDL の定義と、.cpp, .h での関数定義が違っているのが気になる。

    2.C#側のbyte[]を無視している。

      OCXではbyte[]自体を入れ替えているので、メモリーの無駄であるが、byte[] で伝えないと

      OCX側で処理ができない。

    3. 直接 C# 内の byte[] を書き換えているわけではない。

      

    あと細かいチェックは必要だろうが、とにかくメソッドで byte[] のデータをやり取りすることはできそうだ。

    以上。

    2013年3月11日 0:23