Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9749

[C#7.2] unsafeステートメントにもマーシャラーにも頼らないで、固定サイズ配列を含む構造体をP/Invokeで受け渡しする方法

$
0
0

TL;DR

  • StructLayout属性でSizeを指定した構造体を用意する。
  • それをrefSpan構造体で読み出す。

unsafeステートメントとマーシャラーを使用する場合

P/Invokeで、固定長サイズ配列を含む構造体を受け渡す場合、主に下記の2つの方法を用いると思います。
GetMonitorInfoMONITORINFOを例にすると、下記のような定義です。

ネイティブの定義(UNICODEビルド前提で簡略可)

#define CCHDEVICENAME 32
structMONITORINFOEX{DWORDcbSize;RECTrcMonitor;RECTrcWork;DWORDdwFlags;WCHARszDevice[CCHDEVICENAME];};BOOLWINAPIGetMonitorInfo(HMONITORhMonitor,LPMONITORINFOlpmi);

unsafeステートメントの場合

[StructLayout(LayoutKind.Sequential)]unsafestructMONITORINFOEX{publicintcbSize;publicRECTrcMonitor;publicRECTrcWork;publicuintdwFlags;publicfixedcharszDevice[32];};[DllImport("User32.dll",CharSet=CharSet.Unicode)]staticexternboolGetMonitorInfo(IntPtrhMonitor,refMONITORINFOEXlpmi);

この後、unsafeコンテキストでMONITORINFOEX.szDeviceから文字列などにコピーする必要があります。

マーシャリングする場合

[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]structMONITORINFOEX{// ...省略...[MarshalAs(UnmanagedType.ByValArray,SizeConst=32)]publicchar[]szDevice;};[DllImport("User32.dll",CharSet=CharSet.Unicode)]staticexternboolGetMonitorInfo(IntPtrhMonitor,refMONITORINFOEXlpmi);

MONITORINFOEX.szDevicestring/ByValTStrにしたほうが扱いやすいですが、対比のためchar[]/ByValArrayにしています。

問題点

unsafeを使った方がヒープにオブジェクトを作成しない分効率がいいんですが、呼び出し元でもunsafeコンテキストが必要になるので、取り扱いづらいです。
(まぁP/Invokeを使う時点でパフォーマンスを気にしないほうがいいんですが。)

StructLayout属性でSizeを指定した構造体を用意する。

ここからが本題です。
昔からですが、StructLayout属性にはSizeフィールドがあり、構造体のサイズを設定出来ます。

[StructLayout(LayoutKind.Sequential,Size=32*sizeof(char),CharSet=CharSet.Unicode)]publicstructFixedLengthCharArray32{publiccharFirst;}

ただサイズを設定できるだけで、中身を参照を参照しようとするとunsafeステートメントが必要なので、この構造体を使う意義がありませんでした。

refSpan構造体で読み出す

そこでrefSpan構造体です。
フィールドの参照からSpan構造体を構築すれば、後はToStringなりインデクサーでアクセス出来ます。
unsafeコンテキストも要らないので扱いやすいです。

varmonitorInfo=newMONITORINFOEX{cbSize=Unsafe.SizeOf<MONITORINFOEX>()};GetMonitorInfo(hMonitor,refmonitorInfo);vars=MemoryMarshal.CreateReadOnlySpan(refmonitorInfo.szDevice.First,32);

残念ながら.NetStandard 2.0(.NET Framework)だとMemoryMarshal.CreateReadOnlySpanが無いので、相当するメソッドをunsafeコンテキストで実装する(もしくはライブラリを使用する等の)必要があります。

一般化

配列の型とサイズ毎に構造体を用意する必要があり定型コードが多いので、自動生成などをしたほうがいいでしょう。
T4テンプレート等で作成してみたバージョンはこちら。


Viewing all articles
Browse latest Browse all 9749

Trending Articles