(C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

(C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合

#1

投稿記事 by usao » 9年前

C#初心者です.

Cで作られたDLLで公開されている 配列領域の先頭を指すポインタと配列サイズとを引数に取る関数
をC#から使いたい状況です.

「メモリ上で連続した領域を指すポインタを渡す必要があるため,
 C#でデータ領域をIList<>等で表している場合には,
 下記コードのように一旦ToArray()で配列にコピーする必要がある」
と考えていますが,

・そもそもこの考えは合っているでしょうか?
・可能であれば,このコピーを行わずに済ませたいのですが,何か方法はありませんでしょうか?
・配列以外で,メモリ上の配置が連続である型は存在しますか?

コード:

//DLLの関数
static class dll_func
{
  [DllImport( DLLのファイル名 )]
  unsafe extern void Func( byte *pByteArray, int ByteArraySize );
}

//DLL使う側
class XXX
{
  void Work( IList<byte> data )
  {
    unsafe
    {
      byte[] ByteArray = data.ToArray();  //一旦配列にコピーする
      fixed( byte *p = ByteArray )
      {
        dll_func.Func( p, ByteArray.Count() );
      }
    }
  }
}
以上,ご教授頂きたく,よろしくお願い申し上げます.

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合

#2

投稿記事 by YuO » 9年前

usao さんが書きました:・そもそもこの考えは合っているでしょうか?
・可能であれば,このコピーを行わずに済ませたいのですが,何か方法はありませんでしょうか?
・配列以外で,メモリ上の配置が連続である型は存在しますか?
上から,
  • あっていますが,書かれているコードは通常の方法ではありません。
  • 配列を最初から使えばコピーが不要です。
  • マーシャリング時に,unmanagedメモリにコピーされますが,オブジェクト自体の領域は連続です。
    オブジェクトが参照しているオブジェクトがそこに含まれるためには,値型である必要があります。
となるかと思います。


P/Invokeで使える型は,原則として
  • 値型 (int等プリミティブを含む)
  • 値型の配列
  • System.String
に限られます。
クラスに対しても,ちゃんとマーシャリング動作は定められているのですが,COMで使う場合を除くとまず使いません。
ref) 相互運用性, 既定のマーシャリングの動作, プラットフォーム呼び出しによるデータのマーシャリング

さて,提示されているような場合だと,

コード:

internal static class NativeMethods // CA1060 https://msdn.microsoft.com/ja-jp/library/ms182161.aspx
{
    [DllImport( ... )]
    public static extern void Func ([In] byte[] pByteArray, int nByteArraySize);
}

class XXX
{
    void Work (IList<byte> data)
    {
        var temp = data.ToArray();
        NativeMethods.Func(temp, temp.Length);
    }
}
あたりの呼び出し方になるかと。
不要ではあるもののDllImport時に[In]を付けているため入力のみのマーシャリング (省略時も同様動作) ですが,出力で使うのであれば[Out]が必要になります。
ref) Bittable型と非Bittable型

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合

#3

投稿記事 by usao » 9年前

ありがとうございます.


>あっていますが,書かれているコードは通常の方法ではありません。


byte[] はBlittable型である(byteがBlittableで,その1次元配列だからBlittable型である)ので,
わざわざunsafeって書いてポインタを使わなくても,byte[]型として書けばよろしくやってもらえる.
・そうすればfixedと書かなくても自動で固定してもらえる
・なお,配列の用途がデータ受取である場合には[Out]が必要
 (用途に応じて [In],[Out],[In,Out] を書くべし)


…という理解で合っていますでしょうか.

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合

#4

投稿記事 by YuO » 9年前

usao さんが書きました:>あっていますが,書かれているコードは通常の方法ではありません。
byte[] はBlittable型である(byteがBlittableで,その1次元配列だからBlittable型である)ので,
わざわざunsafeって書いてポインタを使わなくても,byte[]型として書けばよろしくやってもらえる.
・そうすればfixedと書かなくても自動で固定してもらえる
通常のP/Invoke呼び出しにおいてはこれで問題ないです。

問題が起きるのは,ポインタを関数側が保持していて,そこへ非同期で読み書きするような場合です (ReadFileやWriteFileにlpOverlappedを指定した場合など)。
こういう場合は,Marshalクラスのメソッドでunmanagedなメモリを確保して,それを渡すようにします。

/unsafeコンパイラオプションが必要になるような呼び出しは,非常に特殊な場合だと思って下さい。
少し面倒になったりしますが,大抵の場面で/unsafeを使わずに処理することが出来ます。
usao さんが書きました:・なお,配列の用途がデータ受取である場合には[Out]が必要
 (用途に応じて [In],[Out],[In,Out] を書くべし)
In属性のみで書くかは個人の好みですが,それ以外に関してはその通りです。

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合

#5

投稿記事 by usao » 9年前

ありがとうございました.

実際に記述して試してみて「できました」となるには時間を要する状況にありますので,
まずは 現段階にてお礼を述べさせていただくと共に,解決チェックを付けさせていただきます.

本当に助かりました.

閉鎖

“C言語何でも質問掲示板” へ戻る