- PR -

C# クラスの配列を含むクラスをマーシャリングする方法

1
投稿者投稿内容
ccgrape
会議室デビュー日: 2008/01/18
投稿数: 7
投稿日時: 2008-01-18 09:56
Visual C# 2005でVC++6.0で作成されたDLLを使用する処理を作成しています。
このDLL内の"クラスの配列を含むクラス"を引数とする関数呼出を行うと、
DLL内の関数で、配列にしたクラスの値が正常に読めません。
また、DLL内の関数で、配列にしたクラスの値を変更してもC#側で受け取れません。
御存知の方がいらっしゃいましたら教えて下さい。

[環境]
VisualStudio.NET2005、Windows2000

[詳細]
・DLLのclass宣言+関数宣言
class Dllapi
{
////////////////////DLLの構造体宣言
[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
public class DLLDATA_SUB
{
public int dummy2;
}

[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
public class DLLDATA
{
public int dummy1;
public DLLDATA_SUB[] arry = new DLLDATA_SUB[2];
public DLLDATA(){
for (int cnt = arry.GetLowerBound(0); cnt <= arry.GetUpperBound(0); cnt++)
arry[cnt] = new DLLDATA_SUB();
}
}
////////////////////

////////////////////DLLの関数宣言
[DllImport("DllProc.dll")] public extern static int fnDllProc([In, Out] DLLDATA data);
}


・処理
Dllapi.DLLDATA dat = new Dllapi.DLLDATA();

dat.dummy1 = 10;
dat.arry[0].dummy2 = 20;

Dllapi.fnDllProc(dat); ←この関数内でdummy2が正常に受け取れない。(dummy1は正常に受け取れる)



もともと、DLLでは
以下の様な構造体を受け取る様に設計されているのですが、
ボクシング時に値のコピーになる等あるため、
構造体を使わずclassとして作成しています。
typedef struct {
int dummy2;
} DLLDATA_SUB;

typedef struct {
int dummy1;
DLLDATA_SUB arry[2];
} DLLDATA;

DLLに渡ったときに、
dummy1
arry[0].dummy2
arry[1].dummy2
の様なメモリ配置になっていない様です。
(DLLDATA_SUBを配列にしなければ正常に動いている様です。)

以上、よろしくお願いします。
ccgrape
会議室デビュー日: 2008/01/18
投稿数: 7
投稿日時: 2008-01-18 10:32
上の投稿ですが、
構造体ではなくclassにしたのは、
ボクシング時の値コピーを避けるためではなく、

class Dllapi
{
////////////////////DLLの構造体宣言
[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
public struct DLLDATA_SUB
{
public int dummy2;
}

[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
public struct DLLDATA
{
public int dummy1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]
public DLLDATA_SUB[] arry;
public void Initialize(){arry = new DLLDATA_SUB[2];}
}

////////////////////DLLの関数宣言
[DllImport("DllProc.dll")] public extern static int fnDllProc(ref DLLDATA data);
}

となり、
C#側で使用するときに、
Dllapi.DLLDATA dat = new Dllapi.DLLDATA();
dat.Initialize(); ←このInitial関数を呼ばなくてはならない。

dat.dummy1 = 10;
dat.arry[0].dummy2 = 20;

Dllapi.fnDllProc(ref dat);

の様に、Initial用の関数を呼ぶのを省略したかった為です。

以上、訂正させて頂きます。
よろしくお願いします。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2008-01-18 11:53
諦めて C# 側のを struct で定義して下さい。
class で定義した場合は何をどうやっても値として扱うことはできません。常にポインタとして扱われます。

で、引数なしコンストラクタが struct で定義できないのが不満なら、代わりに static な New メソッドでも用意すればいいのではないでしょうか。
コード:
struct Hoge {

public int StructSize;
public static Hoge New() {
Hoge hoge = new Hoge();
hoge.StructSize = Marshal.SizeOf(typeof(Hoge));
return hoge;
}
}



[ メッセージ編集済み 編集者: Hongliang 編集日時 2008-01-18 11:54 ]
ccgrape
会議室デビュー日: 2008/01/18
投稿数: 7
投稿日時: 2008-01-18 20:14
Hongliang様

早速のアドバイスありがとうございました。

大変参考になりました。

例えば以下のような場合、
もっときれいに書けるのでしょうか?

コード:
//構造体定義
class DllApi{
    [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
    struct HOGE_SUB_A{
        public int dummy1;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
        public sbyte[]  arry_sa;
        public static HOGE_SUB_A New(){
            HOGE_SUB_A hoge_sa = new HOGE_SUB_A();
            hoge_sa.arry_sa = new sbyte[3];
            return hoge_sa;
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
    struct HOGE_SUB_B{
        public int dummy2;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=12)]
        public sbyte[]  arry_sb;
        public static HOGE_SUB_B New(){
            HOGE_SUB_B hoge_sb = new HOGE_SUB_B();
            hoge_sb.arry_sb = new sbyte[12];
            return hoge_sb;
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
    struct HOGE {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
        public HOGE_SUB_A[] arry_a;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=5)]
        public HOGE_SUB_B[] arry_b;
        public static HOGE New() {
            HOGE hoge = new HOGE();
            hoge.arry_a = new HOGE_SUB_A[16];
            for (int cnt = hoge.arry_a.GetLowerBound(0); cnt <= hoge.arry_a.GetUpperBound(0); cnt++)
                hoge.arry_a[cnt]  = HOGE_SUB_A.New();
            hoge.arry_b = new HOGE_SUB_B[5];
            for (int cnt = hoge.arry_b.GetLowerBound(0); cnt <= hoge.arry_b.GetUpperBound(0); cnt++)
                hoge.arry_b[cnt]  = HOGE_SUB_B.New();
            return hoge;
        }
    }
}

//構造体の宣言
DllApi.HOGE cs_hoge = DllApi.HOGE.New();



ちなみに、HOGE_SUB_AやHOGE_SUB_Bも、

DllApi.HOGE_SUB_B tmp = DllApi.HOGE_SUB_B.New();

の様にHOGEとは別に単独で使用したいので、
HOGEと同じ形にしています。



是非、アドバイス頂けないでしょうか。
よろしくお願いします。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2008-01-18 23:07
いいんじゃないですかそれで。
// 私はガチで C/C++ とマーシャリングするならさっさと C++/CLI に行きます。
ccgrape
会議室デビュー日: 2008/01/18
投稿数: 7
投稿日時: 2008-01-20 07:46
Hongliangさま

アドバイスありがとうございました。

大変参考になりました。

解決とさせていただきます。

本当にありがとうございました。
1

スキルアップ/キャリアアップ(JOB@IT)