- PR -

VC#にてDLL内の構造体を取得する方法

投稿者投稿内容
Vandoross
会議室デビュー日: 2006/02/22
投稿数: 7
投稿日時: 2006-03-23 13:28
VS.NET 2003を使用し、開発を行っている者です。

下記のようなソースで、VC++で作成したDLL内で使用している構造体を、VC#にも定義し、DLL内の構造体ST_Bを、VC#でも使用したいのですが、どのような方法があるのでしょうか?

DLLにST_Bのアドレスを返す関数を作って、VC#からコールする形などを試してみたのですが、「マネージ型 ('****') の変数のアドレスまたはサイズを取得できません。」というコンパイルエラーがでたり、実行時に「MarshalDirectiveException 内部的な制限 : 構造体が複雑すぎるか、大きすぎます。」という例外が発生したりとうまくいきません。

ご存知の方いらっしゃいましたら、ご教授のほどよろしくお願い致します。

-----------------------
VC++ DLL
-----------------------
typedef struct{
  short A;
  short B;
  long C;
  long D
} stA;
typedef struct{
  stA A[16];
  long B[4096];
  long C[4096];
  long D[4096];
  unsigned char E[1024];
  unsigned char F[1024];
} stB;

stB ST_B;

-----------------------
VC#
-----------------------
[StructLayout(LayoutKind.Sequential)]
struct stA{
  ・・・
}
[StructLayout(LayoutKind.Sequential)]
struct stB{
  [MarshalAs( UnmanagedType.ByValArray, SizeConst=16)]
  public stA [] A;
  [MarshalAs( UnmanagedType.ByValArray, SizeConst=4096)]
  public long [] B;
  ・・・
}
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2006-03-23 13:53

.NET 1.1 までは、単純型でない型の固定長配列をメンバに含む構造体をマーシャリングすることはできません。
単純に解決するには、その個数だけメンバを並べることです。
コード:
public struct StructB {
    public StructA A01;
    public StructA A02;
    (略)
    public StructA A16;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4096)]
    public int[] B;
    (以下略)
}


// 一番簡単なのは .NET 2.0/VS2005 に移行することかも知れませんが。
Vandoross
会議室デビュー日: 2006/02/22
投稿数: 7
投稿日時: 2006-03-23 14:59
早速の返信ありがとうございます。

> 単純型でない型の固定長配列をメンバに含む構造体をマーシャリングすることはできません。
構造体の配列は使用しないように変更したのですが、次のような問題がおきてしまいました。

typedef struct{
  short A;
  short B;
} stA;
のような場合は、VC#で
GetAdr(out stA* p); // DLLで定義されているstAのアドレスを返す関数
stA* p = (stA*)pp;
というような書き方をしてもコンパイルはOKなのですが、

typedef struct{
  [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
  short [] A;
  short B;
} stA;
のように、配列を含んだ場合に、上と同じ形で使おうとすると、「マネージ型 ('***.stA') の変数のアドレスまたはサイズを取得できません。」というコンパイルエラーが出てしまいます。

もしかして、.NET 1.1では、配列を含む構造体のアドレスを使用することはできないのでしょうか?
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2006-03-23 15:31
unsafe 構文における制限とマーシャリングにおける制限は全く別物である点をまず理解してください。

unsafe によるポインタの使用は制限が厳しく、.NET 1.1 までは固定長配列をメンバに持つ構造体を扱うことはできません。
// .NET 2.0 でも使えることは使えるけれど、非 unsafe との互換性が極めて制限されています。
しかし、.NET がアンマネージドとの相互運用時に型の変換を行うマーシャラは、MarshalAs 属性を理解して固定長配列メンバを C/C++ が正しく理解できる形に変換してくれます。
多くの場合、System.Runtime.InteropServices 名前空間の Marshal クラスを利用して、System.IntPtr(ポインタやハンドルを表すマネージド型です)を通して扱うことになります。
Marshal クラスはマーシャラを利用して IntPtr との相互変換(というか読み書き)を行うため、固定長配列メンバを持つ構造体も扱えます。
例えば構造体と IntPtr の相互変換には Marshal.PtrToStructure / Marshal.StructureToPtr メソッドがあります。

例えばこんな感じですかね。
コード:
[DllImport("hoge.dll")]
private static extern bool Alloc(out IntPtr ptr);
[DllImport("hoge.dll")]
private static extern void Free(IntPtr ptr);

private struct Hoge {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
    public int[] Values;
}

public static void Main() {
    IntPtr ptr;
    if (Alloc(out ptr)) {
        try {
            Hoge hoge = (Hoge)Marshal.PtrToStructure(ptr);
            /* ほげほげ */
        }
        finally {
            Free(ptr);
        }
    }
}


// しかし DLL 側でメモリを確保するというのは怖いですな。
Vandoross
会議室デビュー日: 2006/02/22
投稿数: 7
投稿日時: 2006-03-23 19:31
Hongliangさん、度々ありがとうございます。

教えていただいた方法で、試してみました。
Hoge hoge = (Hoge)Marshal.PtrToStructure(ptr, typeof(Hoge));
の処理で、Alloc時にDLL内で作成された構造体のコピーができてしまうようです。
DLL内の構造体と、C#内の構造体とで、同じ空間を操作したいと思っているのですが、何か方法があるのでしょうか?

> // しかし DLL 側でメモリを確保するというのは怖いですな。
ご指摘の通り、DLL側でメモリ確保するのは危険な気もするので、C#側で、
Hoge hoge = new Hoge();
としておいて、hogeのアドレスをDLLに渡すという方法もありかなと思ってはいるのですが、hogeのアドレスを取得する方法が見つかりません。
こちらの方も、もしご存知でしたら教えていただけませんでしょうか。

知識が乏しく、繰り返しの質問になり、申し訳ありません。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2006-03-23 19:48
はい? ひょっとして C++ 側のソースもいじれるのですか?
それなら話は極めて単純になりますが。
  • C# 側は構造体を new して、それを ref でアンマネージドに参照渡しする。
  • C++ 側は構造体のポインタとして受け取って操作する。
<追記>
ああ、どうせこんな大きい構造体はポインタでしかやりとりしないでしょうから、class で宣言してやるのも手ですね。
そうすれば値渡しすることでポインタを渡すことになりますし。

[ メッセージ編集済み 編集者: Hongliang 編集日時 2006-03-23 19:55 ]
Vandoross
会議室デビュー日: 2006/02/22
投稿数: 7
投稿日時: 2006-03-23 22:03
すばやい返信とても感謝です。

> ・C# 側は構造体を new して、それを ref でアンマネージドに参照渡しする。
> ・C++ 側は構造体のポインタとして受け取って操作する

について、次のような形で試してみました。
---------------------------
■C++(DLL)
Hoge* pHoge;
void SetAdr(Hoge* p){ pHoge = p; }
---------------------------
■C#
[DllImport("***.dll")]
public static extern void SetAdr(ref Hoge p);

Hoge hoge = new Hoge();
SetAdr(ref hoge); ←※1
---------------------------
※1に入る前に、次の例外が発生してしまいます。
「System.Runtime.InteropServices.MarshalDirectiveException
 内部的な制限: 構造体が複雑すぎるか、大きすぎます。」
何かやり方がおかしいのでしょうか?

> ああ、どうせこんな大きい構造体はポインタでしかやりとりしないでしょうから、class で宣言してやるのも手ですね。
> そうすれば値渡しすることでポインタを渡すことになりますし。

について、試してみようと思ったのですが、何を値渡しすればよいのか良く分かりません。
classで宣言というのは、
public class csHoge{
public Hoge hoge = new Hoge();
}
という感じでよいのでしょうか?
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2006-03-23 22:47
そんなの出ないけどなーと思ったら……orz
コード:
typedef struct{ 
  stA A[16]; 
  long B[4096]; 
  long C[4096]; 
  long D[4096]; 
  unsigned char E[1024]; 
  unsigned char F[1024]; 
} stB;
[StructLayout(LayoutKind.Sequential)] 
struct stB{ 
  [MarshalAs( UnmanagedType.ByValArray, SizeConst=16)] 
  public stA [] A; 
  [MarshalAs( UnmanagedType.ByValArray, SizeConst=4096)] 
  public long [] B; 
  ・・・ 
}


C/C++ での long は通常 32bit です。
C# での long は System.Int64 のエイリアス、つまり 64bit です。
構造体のサイズが全く違ってますよ……。

引用:

> ああ、どうせこんな大きい構造体はポインタでしかやりとりしないでしょうから、class で宣言してやるのも手ですね。
> そうすれば値渡しすることでポインタを渡すことになりますし。

について、試してみようと思ったのですが、何を値渡しすればよいのか良く分かりません。
classで宣言というのは、
public class csHoge{
public Hoge hoge = new Hoge();
}
という感じでよいのでしょうか?


いえ、こんな感じです。
コード:
[StructLayout(LayoutKind.Sequential)]
public class StructB {
    public StructA A01;
    (略)
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4096)]
    public int[] B;
    (以下略)
}
[DllImport("Hoge.dll")]
public static extern void Hoge([In, Out] StructB b);



StructLayout 属性で Sequential(または Explicit)を付加することによって、マーシャリング時に疑似構造体として扱うことができるようになります。
もちろんクラスつまり参照型ですから、常に参照( C/C++ 側から見ればポインタ)が渡ることになります。ref/out で渡すとポインタのポインタです。
ただし、正しく値をやりとりするのに In 属性と Out 属性のいずれかまたは両方を指定する必要があります。
// デフォルトでは In 属性のみだったかな。このとき渡すことはできますがアンマネージドの操作結果を受け取りません。
もしこの StructB を他の構造体のメンバにした場合、参照つまりポインタとして扱われることになるので気を付けなければなりません。

なお、いずれにせよ渡したポインタを DLL 側が保持しておくようなコードはさけるべきですよ?

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