VB.NETからVC++とかのUnmanagedDLLを使う

VB.NET からVC++とかのUnmanagedDLLを使うやりかた

○前提
使われる側:
 VC++などで作成されたアンマネージコードDLL
 作成方法はここでは言及しない
使う側:
 VB.NET(たぶん全バージョン)
 #検証にはVB2008、.NET Framework2.0を使用しました

○前提知識
VB6からVC++DLLを呼ぶ方法:
 Declare Function 関数名 Lib ファイル名 (引数) As 返却値型
 #VB.NETでも同様の書き方が出来るため(以下の説明でも使用)
  VB.NETから使える書き方もある(ここでは言及しない)
ネイティブコード:
 CPUが解釈できるマシン語バイナリのコード。
 ネイティブコードで出来たアプリケーションは
 ネイティブアプリケーション。
 WindowsネイティブアプリケーションはWindowsOS上で
 (必要なライブラリがあれば)動作する。

マネージコード
 CLR(≒.NET Framework)上で 動くコード。
 WindowsOSだけでなく(対応するバージョンの).NET Frameworkを
 インストールしてなければ動作しない。
  厳密には:
   CLI:仕様
   CLR:CLIのMicrosoftによる実装
   CIL:CLRが解釈するコード
   VES:CLI仮想マシン。CILをネイティブコードに翻訳して実行する

マネージメモリ:
 マネージコードで通常管理されるメモリ領域。
 つまりVES仮想マシン上におけるメモリ。
 ※要注意:
   マネージメモリはアンマネージコードに渡してはならない。
   もしテストで正常に動いたとしてもそれはあくまで偶然であり、
   常に正常に動くとは限らない。
   ↑マネージメモリ空間内でデータは勝手に移動されることがあるため

アンマネージコード:
 CLR上で 動くコード以外(≒ネイティブコード)。
アンマネージメモリ:
 アンマネージドコードで使われるメモリ領域。
 つまりOS上におけるメモリ。

マーシャリング
 マネージコードとアンマネージコードの橋渡しをすること。
 アンマネージコード間では単にインターフェースが相当するが、
 マネージコードではインターフェースを解釈・仲介するために
 さらにマーシャリングが必要になる。
 ↑メモリ空間が異なるため
マーシャラ
 マーシャリングするもの。VB.NETでは
 System.Runtime.InteropServices.Marshalクラス

○マネージメモリとアンマネージメモリ
  • アンマネージドコード側
    • すべてアンマネージメモリ
  • アンマネージドコードとマネージドコードのインターフェイス
    • すべてアンマネージメモリ
    • ただし標準的な数値型は自動的にマーシャリングされる
  • マネージドコード側
    • コードで扱えるのはマネージドメモリのみ
    • ポインタで受け渡されたインターフェース(文字列、構造体等含む)は自前でマーシャリングして、マネージドメモリにコピーしてから使う。
    • またポインタを渡す場合も自前でマーシャリングする

○方法(inとoutはDLL側から見た場合で記載)
以下の例はShapefile C Library V1.2
アンマネージDLL使用例としています。
APIはここ参照

数値型の場合:
①渡す値(in)
DLL側のインターフェースがdouble、int、long、bool等の
数値型の場合、渡す値(in)はByValとする。

②受け取る値のシングルポインタ(out)と
 受け渡し両方の値のシングルポインタ(in/out)
DLL側のインターフェースがdouble*、int*、long*、bool*等の
数値型のポインタの場合、値はByRefにするだけとする。
(ポインタという意識はしない=ByRef自体がポインタだから)

①②双方とも、自動的にマーシャリングされるので
VB6との相違は意識しなくともよい。
(=昔のVBからDLLを使う意識で問題なし)
利用例は以下例2参照

文字列型の場合:
①渡す文字列(in)
DLL側のインターフェースがconst char*、LPCSTR等の
文字列ポインタで引渡し文字列を渡す(in)場合、
値型と同様にByValで問題なし。
ただし自動マーシャリングによりinなのに変数内の値が
変わっていることがある。
そのような場合はラッパークラスを作って、呼び出しを
内部的に二重化すると解決できる。

例1(ラッパー):
   Public Module Module1
       ・ラッパークラス
       Public Class cSHPWrapper
           ・ラッパーメソッド
           Public Shared Function SHPOpen(ByVal stShapeFile As String, ByVal stAccess As String) As IntPtr
               Return Module1.SHPOpen(stShapeFile, stAccess)
           End Function
       End Class
   
       ・VB.NET側宣言
       Public Declare Function SHPOpen Lib "shapelib.dll" (ByVal pszShapeFile As String, ByVal pszAccess As String) As IntPtr
   
       ・コード
       Dim hSHP As IntPtr = cSHPWrapper.SHPOpen(fname, "rb")
   
   End Module

②受け取る文字列(out)と
 受け渡し両方の文字列(in/out)
DLL側のインターフェースがchar*、LPSTR等の
文字列ポインタで文字列を受け取る(out)場合、
または文字列を受け渡し両方する(in/out)場合、
文字列はStringBuilder型にして、ByValで受け渡す。
StringBuilderはVB側では固定長文字列(NULL終端)のような認識の
使い方(VC++DLL側から見ればLPTSTR≒char*そのもの)
Stringにするには.ToStringすればいいだけ。
例2(数値の例/文字列受け取りの例):
   ・DLL側I/F
   DBFFieldType DBFGetFieldInfo( DBFHandle hDBF, int iField, char * pszFieldName,
                               int * pnWidth, int * pnDecimals );

   ・VB.NET側宣言
   Public Declare Function DBFGetFieldInfo Lib ”shapelib.dll” (ByVal hDBF As IntPtr, _ ’inハンドル
                               ByVal iField As Integer, _ ’in数値
                               ByVal pszFieldName As StringBuilder, _ ’out文字列
                               ByRef pnWidth As Integer, ByRef pnDecimals As Integer)  _ ’out数値シングルポインタ
                               As Integer
   ・コード
   Dim stbFieldName As New StringBuilder(12)
   Dim iWidth As Integer,iDecimals As Integer,stName As String
   iDataType = DBFGetFieldInfo(hDBF, i, stbFieldName, iWidth, iDecimals)
   stName = stbFieldName.ToString

構造体やポインタ、配列の場合:(値のシングルポインタ除く)
型はIntPtr型にする。
①渡す(in)場合
ポインタの示すメモリはマネージコード側で確保する必要がある。
もちろん使い終わったら開放する必要も生じる。
確保はMarshal.AllocHGlobal、開放はMarshal.FreeHGlobal。
マネージメモリをアンマネージメモリにコピーするには
数値型・ポインタの場合Marshal.Copy。
文字列はMarshal.PtrToString*とMarshal.String*ToPtr。
アンマネージメモリのメモリリークに要注意。確保したら開放する。
例3(構造体・ポインタ・配列の例):
   ・DLL側I/F
   SHPObject* 
       SHPCreateSimpleObject( int nSHPType, int nVertices, 
                           double* padfX, double* padfY,
                           double* padfZ, );
   #ここのdouble*はdouble配列のポインタ

   ・VB.NET側宣言
   Public Declare Function SHPCreateSimpleObject Lib "shapelib.dll" (ByVal nSHPType As Integer, ByVal nVertices As Integer, _
                                          ByVal padfX As IntPtr, ByVal padfY As IntPtr, ByVal padfZ As IntPtr) As IntPtr
   
   ・コード
       Dim padfX() As Double = = {139.01, 140.11111111, 138.222222} '←マネージメモリ
       Dim size As Integer = Marshal.SizeOf(padfX(0)) * padfX.Length
       Dim ptrX As IntPtr = Marshal.AllocHGlobal(size) '←アンマネージメモリ確保
       Marshal.Copy(padfX, 0, ptrX, padfX.Length) '←マネージメモリをアンマネージメモリにコピー
       'ptrYの処理については同様のため略
       Dim SHPObj As IntPtr = SHPCreateSimpleObject(3, padfX.Length, ptrX, ptrY, 0) '←DLLコール
       If SHPObj = IntPtr.Zero Then Return -1
       SHPDestroyObject(SHPObj) '←DLLが確保したものは基本DLLに開放させる
       Marshal.FreeHGlobal(ptrX) '←アンマネージメモリ確保

構造体の場合Marshal.PtrToStructureとMarshal.StructureToPtrで
データをコピーするが、その際、構造体の宣言に注意。
LayoutKindで構造体内のメモリレイアウトを事前に宣言する
必要がある。
構造体はStructureでもClassでも可能だが、宣言等違いに注意。
以下例4参照。

②受け取る(out)場合と
 受け渡し両方(in/out)の場合
受け取る(out)場合も基本的には同じ。上の例のSHPObj参照。
ただしアンマネージメモリの値を取り出す場合は、逆に
マネージメモリにコピーしてから利用する必要がある。
例4(構造体・ポインタ・配列の受け取り例/構造体の場合の注意点):
   ・VB.NET側宣言
    (方法1)
   <StructLayout(LayoutKind.Sequential)> _
   Public Structure SHPObject
       Public nSHPType As Integer
       Public nShapeId As Integer
       Public panPartStart As IntPtr
   End Structure
    (方法2 より厳密な方法 以下では使用しない)
   <StructLayout(LayoutKind.Explicit)> _
   Public Class SHPObject
       <FieldOffset(4)> Public nSHPType As UInt32
       <FieldOffset(4)> Public nShapeId As UInt32
       <FieldOffset(4)> Public panPartStart As UInt32
   End Class 
   
   ・コード
       Public Shared Function SHPReadObject(ByVal hSHP As IntPtr, ByVal iShape As Integer, ByRef oSHP_SO As SHPSimpleObject) As Boolean
   
           Dim poSHP As IntPtr
   
           Try
               poSHP = Module1.SHPReadObject(hSHP, iShape)
               '返却値構造体ポインタがNULLなら異常終了
               If IntPtr.Zero.Equals(poSHP) Then Return False
               '返却値構造体ポインタから、アンマネージ構造体をマネージ構造体へコピー
               Dim oSHP As SHPObject = Marshal.PtrToStructure(poSHP, GetType(SHPObject))
               With oSHP_SO
                   .nSHPType = oSHP.nSHPType
                   .nShapeId = oSHP.nShapeId
                   .nVertices = oSHP.nVertices
                   .tZ.iLimit = .nVertices - 1
               End With
               '返却値構造体ポインタから、ポインタ格納されているアンマネージ配列をマネージ構造体へコピー
               With oSHP_SO.tZ
                   ReDim .x(.iLimit)
                   ReDim .y(.iLimit)
                   Marshal.Copy(oSHP.padfX, .x, 0, .iLimit + 1)
                   Marshal.Copy(oSHP.padfY, .y, 0, .iLimit + 1)
               End With
           Catch
               Return False
           Finally
               '返却値構造体ポインタを開放
               If IntPtr.Zero.Equals(poSHP) = False Then SHPDestroyObject(poSHP)
           End Try
   
           Return True
   
       End Function




タグ:

+ タグ編集
  • タグ:
最終更新:2010年05月31日 10:22