none
構造体のマーシャリングについて RRS feed

  • 質問

  • 千倉木と申します。

     

    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 String 

            Dim 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 Structure

     Declare 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) ;

    }

     

    以上です。ごちゃごちゃと分かりにくいソースですみませんが、よろしくお願いします。

    2008年2月19日 9:20

回答

  • できなさそうに見受けられます。

    過去にも同様の問題を抱えた方がおられたようですが、解決しているようには見えません。

    別の解決法を探った方が最終的に早いかもしれません。

     

    類似のスレッドで解決策なし 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で扱ってはいけないもののはず?

     

    2008年2月19日 14:45
    モデレータ
  • 直接的な解法ではありませんが、C++/CLI で FuncA のラッパーを作るのはいかがでしょうか。

    2008年2月20日 6:30
  • AAA をクラスにすれば,VARIANT型 の SAFEARRAY で渡せますね。

    vt に何が入っているか見たら,13 って,VT_UNKNOWN (IUnknown*) です。

    やっぱりオブジェクトになってしまっています。

    ということで,各要素には IUnknownインターフェイスポインタが一応入っているので,

    AAA のインスタンスにはそのインタフェースを使ってアクセスするんでしょうね...orz,

    死ぬほど面倒なのと,そもそもそれでやれるのか謎ですし,

    こうなると,最初からCOM仕様でDLLを作った方が楽なんじゃないかな。

     

    もしくは,長めに確保した固定長でByValArray渡しするか。

     

     

      

     

     

    2008年2月20日 19:37
  • 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++ だけで書くことができます。
    2008年2月22日 4:49

すべての返信

  • できなさそうに見受けられます。

    過去にも同様の問題を抱えた方がおられたようですが、解決しているようには見えません。

    別の解決法を探った方が最終的に早いかもしれません。

     

    類似のスレッドで解決策なし 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で扱ってはいけないもののはず?

     

    2008年2月19日 14:45
    モデレータ
  •  

    やれるかどうかは不明ですが...

     

    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

     

     

     

    でしょうね。

     

     

    2008年2月19日 21:54
  • Azulean様、ご回答ありがとうございます。

     

    やはり難しそうですね。このまま試行錯誤を続けても時間の無駄でかもしれません。

     

    件の DLL は SAFEARRAY を多用していますが SafeArrayCreate は全く使っていません。 SafeArrayCreate が構造体の使用を認めていないためだとすれば、もともとトリッキーなことをしていたとも言えそうです。

    長年何の問題も無く動いてきた(らしい)DLLですが、おっしゃるようにこの機に別の方法を考えたほうがいいのかもしれません。

    2008年2月20日 6:04
  • yayadon様、ご回答ありがとうございます。

     

    VarEnum.VT_VARIANT は一度試したことがありましたが、念のために再度試してみたところ、ランタイムエラーになりました。

    試行錯誤もほとほと疲れてきたので、仕様の見直しに踏み切るべきかと考えているところです。

     

    2008年2月20日 6:12
  • 直接的な解法ではありませんが、C++/CLI で FuncA のラッパーを作るのはいかがでしょうか。

    2008年2月20日 6:30
  • structure でなく フォーマットした class でもやってみましたが,

    そもそも,配列の個数が間違って渡ってしまっているので,

    SAFEARRAYが構造体メンバの時にマーシャラが解釈できないようですね。

    2008年2月20日 13:36
  • AAA をクラスにすれば,VARIANT型 の SAFEARRAY で渡せますね。

    vt に何が入っているか見たら,13 って,VT_UNKNOWN (IUnknown*) です。

    やっぱりオブジェクトになってしまっています。

    ということで,各要素には IUnknownインターフェイスポインタが一応入っているので,

    AAA のインスタンスにはそのインタフェースを使ってアクセスするんでしょうね...orz,

    死ぬほど面倒なのと,そもそもそれでやれるのか謎ですし,

    こうなると,最初からCOM仕様でDLLを作った方が楽なんじゃないかな。

     

    もしくは,長めに確保した固定長でByValArray渡しするか。

     

     

      

     

     

    2008年2月20日 19:37
  • DLL 側のコードが、SafeArrayGetElement で直接 AAA に取得している事を考えると、
    この SAFEARRAY は、型が指定されていない(VT なし)か、VT_RECORD と推測します。

    通常、ユーザ定義型を SafeArray に入れる場合は VT_RECORD を使うのですが、
    この場合、IRecordInfo という追加情報が必要になります。
    AAA がタイプライブラリに定義されているなら、簡単に取得できて手間はかかりませんが、
    そうでない場合、呼び出し側が、自前で IRecordInfo を用意しないといけません。

    IRecordInfo を用意できないとなると、特別な用途である型指定なしの SafeArray を使うことになりますが、
    これは、マーシャラが対応してくれないので、コードで直接作って渡す方法になりますね。
    カスタムマーシャラを書けばすっきりできなくもないですが、
    構造体内の配列(LPArray)がマーシャリングできないため、少し手間がかかりそうです。
    2008年2月22日 1:49
  • 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++ だけで書くことができます。
    2008年2月22日 4:49
  • こんにちは。中川俊輔 です。

     

    皆様、大変参考になる回答ありがとうございます。

     

    千倉木さん、フォーラムのご利用ありがとうございます。

    勝手ながら問題解決の参考になりそうな回答へ回答済みチェックをつけさせていただきました。

     

    回答済みチェックが付くことにより、有用な情報を探している方が情報を見つけやすくなります。
    問題解決につながる回答があった場合は、なるべく回答済みボタンを押してチェックを付けてください。

    千倉木さんはチェックを解除することもできますので、ご確認ください。

     

    それでは!

     

    2008年2月29日 8:08