トップ回答者
構造体のマーシャリングについて

質問
-
千倉木と申します。
VB6のアプリケーションをVB2005に移行する作業を行っているのですが、DLLの呼び出しで問題が出ました。
構造体配列を含んだ構造体を引数として渡していて、構造体配列の部分がうまく渡らないようなのです。
配列になっていない構造体メンバは渡っています。
DLLはVC++6以前のVCで作成されたソースをVC++2005でリコンパイルしただけのものです。
できればDLLには手を付けたくないので、VB2005のマーシャリングだけで解決しないものかと、いろいろ記述
を変えてみましたがだめでした。
このようなパターンでは、そもそもマーシャリングは無理なのか、それだけでも知りたいのでご存知の方のご回答
をお待ちします。
もちろん具体的な解決策を提示していただけたらなお結構です。
VB2005の構造体と関数の定義はこんな感じになっています。
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure AAA
<MarshalAs(UnmanagedType.LPStr)> Dim name As StringDim type As Integer
Dim kind As Integer
<MarshalAs(UnmanagedType.LPStr)> Dim data As String
End Structure<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure BBB
<MarshalAs(UnmanagedType.LPStr)> Public name As String
<MarshalAs(UnmanagedType.LPStr)> Public name2 As String
Public type As Integer
Public No As Integer
<MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VarEnum.VT_USERDEFINED)> _
Dim AAA() As Object
End StructureDeclare Function FuncA Lib "LibA" (ByVal P1 As Integer, _
ByRef P2 As BBB, _
ByRef P3 As DDD, _
ByRef P4 As FFF) As Integer引数 P3、P4も構造体ですが長くなるので割愛します。
VC++2005側は以下のようになっています。
typedef struct {
char *name ;
long type ;
long kind ;
char *data ;
} AAA ;typedef struct {
char *name ;
char *name2 ;
long type ;
long no ;
SAFEARRAY *psa ;
} BBB ;long WINAPI FuncA( long p1, BBB *p2, DDD *p3, FFF *p4)
引数の取りこみは、まずp1を変数に memcpy した後、構造体領域を設定し、配列要素ごとに、
SafeArrayGetElementでコピーします。
BBB bbb ;
AAA stc[32] ;memcpy( &bbb, p2, sizeof( BBB )) ;
bbb.psa = stc ;FuncA1( bbb.psa, p2->psa, bbb.no, p4 )
----------------------------------------------------------------------------------------------------------------------------------
int FuncA1( AAA *a1_p1, SAFEARRAY *a1_p2, long a1_p3, FFF *a1_p4
int i ;
int j ;
long lowerBound ;
long upperBound ;
long indices[1] ;SafeArrayGetLBound( a1_p2, 1, &lowerBound )
SafeArrayGetUBound ( a1_p2, 1, &upperBound )
for ( i = 0, j = lowerBound ; i < a1_p3 ; i++, j++, a1_p1++ ) {
indices[0] = j ;
SafeArrayGetElement( a1_p2, indices, a1_p1) ;}
以上です。ごちゃごちゃと分かりにくいソースですみませんが、よろしくお願いします。
回答
-
できなさそうに見受けられます。
過去にも同様の問題を抱えた方がおられたようですが、解決しているようには見えません。
別の解決法を探った方が最終的に早いかもしれません。
類似のスレッドで解決策なし or 回答なし
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1881520&SiteID=1
http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=770221&SiteID=7
SafeArrayCreateのヘルプ
http://msdn2.microsoft.com/en-us/library/ms221234(VS.85).aspx
vt
The base type of the array (the VARTYPE of each element of the array). The VARTYPE is restricted to a subset of the variant types. Neither the VT_ARRAY nor the VT_BYREF flag can be set. VT_EMPTY and VT_NULL are not valid base types for the array. All other types are legal.この辺を読む限り、VARIANTに入らないような型(ユーザ定義構造体を含む)は、本来SafeArrayで扱ってはいけないもののはず?
-
AAA をクラスにすれば,VARIANT型 の SAFEARRAY で渡せますね。
vt に何が入っているか見たら,13 って,VT_UNKNOWN (IUnknown*) です。
やっぱりオブジェクトになってしまっています。
ということで,各要素には IUnknownインターフェイスポインタが一応入っているので,
AAA のインスタンスにはそのインタフェースを使ってアクセスするんでしょうね...orz,
死ぬほど面倒なのと,そもそもそれでやれるのか謎ですし,
こうなると,最初からCOM仕様でDLLを作った方が楽なんじゃないかな。
もしくは,長めに確保した固定長でByValArray渡しするか。
-
Marshal.AllocCoTaskMem や Marshal.StructureToPtr を駆使してカスタムマーシャラを書けば、
DLL 側に一切手を加えずとも、以下のような定義で使用することができることを確認しました。
BBBMarshaller がカスタムマーシャラのクラス名です。
カスタムマーシャラの都合上、BBB はクラスにしましたので、ByVal で In 方向のみの渡し方です。
Code Snippet
Public Class BBB
Public name As String
Public name2 As String
Public type As Integer
Public no As Integer
Public psa As AAA()
End Class
Declare Function FuncA Lib "LibA" ( _
ByVal P1 As Integer, _
<MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef:=GetType(BBBMarshaller))> _
ByVal P2 As BBB, _
ByRef P3 As DDD, _
ByRef P4 As FFF) As Integer
なので、VB だけでマーシャリングができるかという回答は、「可能」ということになります。
ただし、これ、カスタムマーシャラは結構なコード量になります。
何といっても SAFEARRAY を自前で用意しなければなりませんので、
現実的には、Abstract 様が書かれたように、.NET 用に、DLL のラッパを作った方が速いと思います。
ラッパとしては、C++/CLI を使って .NET 用のアセンブリを作ってもいいですが、
単に BBB のフィールドして AAA を受け取るのではなく、
別に AAA[] の配列を受け取るようなラッパ関数を書く方法もあると思います。
VB 側では BBB の psa メンバは IntPtr にでもしておけば良いでしょうし。
long WINAPI WrappedFuncA(long p1, BBB *p2, AAA *p2_psa, DDD *p3, FFF *p4)
要は、MarshalAs 属性で標準マーシャラがマーシャリングできる形であれば良いわけで。
これなら、ラッパ側関数は、AAA[] から SAFEARRAY(AAA) に変換して、
FuncA を呼び出すコードですので、C++ だけで書くことができます。
すべての返信
-
できなさそうに見受けられます。
過去にも同様の問題を抱えた方がおられたようですが、解決しているようには見えません。
別の解決法を探った方が最終的に早いかもしれません。
類似のスレッドで解決策なし or 回答なし
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1881520&SiteID=1
http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=770221&SiteID=7
SafeArrayCreateのヘルプ
http://msdn2.microsoft.com/en-us/library/ms221234(VS.85).aspx
vt
The base type of the array (the VARTYPE of each element of the array). The VARTYPE is restricted to a subset of the variant types. Neither the VT_ARRAY nor the VT_BYREF flag can be set. VT_EMPTY and VT_NULL are not valid base types for the array. All other types are legal.この辺を読む限り、VARIANTに入らないような型(ユーザ定義構造体を含む)は、本来SafeArrayで扱ってはいけないもののはず?
-
やれるかどうかは不明ですが...
VT_ARRAY | VT_VARIANT ととして,バリアント型のSafeArrayで渡さないといけないので,理屈的には,
Code Snippet<MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VarEnum.VT_USERDEFINED)> _
Dim AAA() As Objectは,
Code Snippet<MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VarEnum.VT_VARIANT)> _
Dim AAA() As Objectでしょうね。
-
AAA をクラスにすれば,VARIANT型 の SAFEARRAY で渡せますね。
vt に何が入っているか見たら,13 って,VT_UNKNOWN (IUnknown*) です。
やっぱりオブジェクトになってしまっています。
ということで,各要素には IUnknownインターフェイスポインタが一応入っているので,
AAA のインスタンスにはそのインタフェースを使ってアクセスするんでしょうね...orz,
死ぬほど面倒なのと,そもそもそれでやれるのか謎ですし,
こうなると,最初からCOM仕様でDLLを作った方が楽なんじゃないかな。
もしくは,長めに確保した固定長でByValArray渡しするか。
-
DLL 側のコードが、SafeArrayGetElement で直接 AAA に取得している事を考えると、
この SAFEARRAY は、型が指定されていない(VT なし)か、VT_RECORD と推測します。
通常、ユーザ定義型を SafeArray に入れる場合は VT_RECORD を使うのですが、
この場合、IRecordInfo という追加情報が必要になります。
AAA がタイプライブラリに定義されているなら、簡単に取得できて手間はかかりませんが、
そうでない場合、呼び出し側が、自前で IRecordInfo を用意しないといけません。
IRecordInfo を用意できないとなると、特別な用途である型指定なしの SafeArray を使うことになりますが、
これは、マーシャラが対応してくれないので、コードで直接作って渡す方法になりますね。
カスタムマーシャラを書けばすっきりできなくもないですが、
構造体内の配列(LPArray)がマーシャリングできないため、少し手間がかかりそうです。 -
Marshal.AllocCoTaskMem や Marshal.StructureToPtr を駆使してカスタムマーシャラを書けば、
DLL 側に一切手を加えずとも、以下のような定義で使用することができることを確認しました。
BBBMarshaller がカスタムマーシャラのクラス名です。
カスタムマーシャラの都合上、BBB はクラスにしましたので、ByVal で In 方向のみの渡し方です。
Code Snippet
Public Class BBB
Public name As String
Public name2 As String
Public type As Integer
Public no As Integer
Public psa As AAA()
End Class
Declare Function FuncA Lib "LibA" ( _
ByVal P1 As Integer, _
<MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef:=GetType(BBBMarshaller))> _
ByVal P2 As BBB, _
ByRef P3 As DDD, _
ByRef P4 As FFF) As Integer
なので、VB だけでマーシャリングができるかという回答は、「可能」ということになります。
ただし、これ、カスタムマーシャラは結構なコード量になります。
何といっても SAFEARRAY を自前で用意しなければなりませんので、
現実的には、Abstract 様が書かれたように、.NET 用に、DLL のラッパを作った方が速いと思います。
ラッパとしては、C++/CLI を使って .NET 用のアセンブリを作ってもいいですが、
単に BBB のフィールドして AAA を受け取るのではなく、
別に AAA[] の配列を受け取るようなラッパ関数を書く方法もあると思います。
VB 側では BBB の psa メンバは IntPtr にでもしておけば良いでしょうし。
long WINAPI WrappedFuncA(long p1, BBB *p2, AAA *p2_psa, DDD *p3, FFF *p4)
要は、MarshalAs 属性で標準マーシャラがマーシャリングできる形であれば良いわけで。
これなら、ラッパ側関数は、AAA[] から SAFEARRAY(AAA) に変換して、
FuncA を呼び出すコードですので、C++ だけで書くことができます。