- PR -

C# プラットフォーム呼び出しのためのstruct定義

1
投稿者投稿内容
サキ
会議室デビュー日: 2005/06/07
投稿数: 2
お住まい・勤務地: yokohama
投稿日時: 2005-06-07 11:33
以下のようなDLL(アンマネージド)の関数を
C#からプラットフォーム呼び出ししたいと考えています。

■メソッド
BOOL SampleMethod( LPVOID SAMPLEDATA);

■sampleData構造体
typedef struct{
char hi[2];
char hinmei[3][20];
char suuryou[3][4];
}SAMPLEDATA;

以下のような書き方になると思うのですが、
配列のstruct定義の方法がよくわかりません。

[StructLayout(LayoutKind.Sequential)]
struct SAMPLEDATA
{
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=2)]
public string hi;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=60)]
public string hinmei;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=12)]
public string suryou;
}

[DllImport("IwLnpApi.dll")]
private extern static bool sampleMethod(
SAMPLEDATA data
);

上記を試してもやはりうまくいきません。
多次元配列のところが間違っていると思います。
(その他もおかしい?)
どのように定義すればよいのでしょうか?

よろしくお願いいたします。

以上
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-06-07 13:11
SampleMethodの仮引数・LPVOIDは*voidのことですよね。
つまりなんらかの値のポインタを指すことになります。

C#において、structは値型です。
値型の場合、代入したりメソッド引数に渡したりした場合、値のコピーが渡されます
つまりSampleMethod(new SAMPLEDATA())とした場合、74バイトのデータがそのままコピーされ引数として渡されます。
これは明らかに望んでいることとは違うでしょう。
渡すのは飽くまでポインタ、Win32なら4バイトだけです。

ポインタを要求する関数に渡すのは、.NETでは参照渡しと言うことになります。
//厳密には違うはずですがその辺はマーシャラが巧くやってくれます。
参照を渡すには、
  • 参照型、つまりclassの場合、必ず参照を渡すことになる。
  • C#ではrefキーワードを仮引数・実引数に両方つけることで、そのオブジェクトへの参照を渡すことができる。
と2通りの手段があります。
DllImportで一般的なのは後者の手法でしょう。

ところでSAMPLEDATA構造体ですが、UnmanagedType.ByValTStrを使うときはStructLayout属性にCharSetも欲しいところです。
まあデフォルトではAnsiのはずなので問題はないでしょうけど。
それにしてもchar[C/C++]/string[C#]で宣言するには微妙なメンバ名ですが。
ちなみにUnmanagedType.ByValTStrを宣言した場合、データはインラインのバイト列で表現されるようになりますから、例えばhinmeiを
コード:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=20)]
public string hinmei1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=20)]
public string hinmei2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=20)]
public string hinmei3;


などとしても問題ありません。
サキ
会議室デビュー日: 2005/06/07
投稿数: 2
お住まい・勤務地: yokohama
投稿日時: 2005-06-10 14:14
Hongliangさん
ご指導ありがとうございました。

返信遅れましてすみません。
実はいろいろ試しているのですが、まだうまくいかないところがあります。

ご指摘のとおり以下の様にしてみました。

■Struct定義
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi )]
struct BIHIN
{
 [MarshalAs(UnmanagedType.ByValTStr,SizeConst=20)]
 public string hinmei1;
 [MarshalAs(UnmanagedType.ByValTStr,SizeConst=20)]
 public string hinmei2;
 [MarshalAs(UnmanagedType.ByValTStr,SizeConst=20)]
 public string hinmei3;
}

■メソッド
BOOL SampleMethod( ref SAMPLEDATA data);

■呼び出し
[DllImport("IwLnpApi.dll")]
private extern static bool sampleMethod(
ref SAMPLEDATA data
);

すると、結果がどれも20byteで渡せているはずが、
20byte目がマネージドのC++PGMで欠落してしまいます。

C#のほうでhinmei1を表示させてもしっかり20byte入っています。

何が問題なのでしょうか。。。
お手数ですがご指導よろしくお願いいたします。

以上

Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-06-10 15:24
C/C++において、文字列は終端文字\0(0x00のバイトデータ)をもって終了とされ、
その文字列を格納するchar配列の長さは終端文字を含みます。
つまりchar name[10]と宣言されている場合、
格納できる文字数は最大でASCIIで9文字+終端文字と言うことになります。

C#においては、文字列はstringクラスで表され、長さなどのメタデータをクラス自身が持つため、
文字列には終端文字は存在しません。不必要ですから。

よって、終端文字を持たないC#のstringから持つC/C++のchar[]へマーシャリングする際に、
終端文字をどこかで付加する必要があります。
問題はどこに付加するかです。
char[]の長さが十分に大きい場合は悩まずstringの一番後ろにつければ済む話です。
しかし確保されるchar[]よりもstringの方が長い場合、
途中で文字列を打ち切って、終端文字をつけねばなりません。
//そうでないとバッファオーバーフローしちゃいます。

さて、例えばstringがASCII10文字、char[]で確保する必要があるのが10バイト(つまりSizeConst=10)だった場合。
一見問題ないように見えますが、ここまで述べたとおり終端文字が必要です。
char[]が10バイト確保されている場合表現できるのは必ずASCII9文字分までです。
ですからこのstringの最後1文字は切り捨てられ、代わりに\0文字が付け足されます。

もしこのような処理が不要で、単純に10バイトのデータが欲しいだけなら、
C#の構造体宣言でstringを使用することはできません。
代わりにMarshalAs(UnmanagedType.ByValArray, SizeConst=10)で修飾されたbyte配列を使用する必要があります。

[ メッセージ編集済み 編集者: Hongliang 編集日時 2005-06-10 15:26 ]
1

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