ページ 1 / 1
(C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合
Posted: 2016年8月10日(水) 10:19
by usao
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() );
}
}
}
}
以上,ご教授頂きたく,よろしくお願い申し上げます.
Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合
Posted: 2016年8月10日(水) 13:24
by YuO
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型
Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合
Posted: 2016年8月10日(水) 15:48
by usao
ありがとうございます.
>あっていますが,書かれているコードは通常の方法ではありません。
byte[] はBlittable型である(byteがBlittableで,その1次元配列だからBlittable型である)ので,
わざわざunsafeって書いてポインタを使わなくても,byte[]型として書けばよろしくやってもらえる.
・そうすればfixedと書かなくても自動で固定してもらえる
・なお,配列の用途がデータ受取である場合には[Out]が必要
(用途に応じて [In],[Out],[In,Out] を書くべし)
…という理解で合っていますでしょうか.
Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合
Posted: 2016年8月10日(水) 16:10
by YuO
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属性のみで書くかは個人の好みですが,それ以外に関してはその通りです。
Re: (C#)領域先頭を指すポインタを引数に取るC(DLL)の関数を呼ぶ場合
Posted: 2016年8月10日(水) 18:24
by usao
ありがとうございました.
実際に記述して試してみて「できました」となるには時間を要する状況にありますので,
まずは 現段階にてお礼を述べさせていただくと共に,解決チェックを付けさせていただきます.
本当に助かりました.