はじめまして。
VB、VC++使い始めて一ヶ月ちょっとです。
使用しているのは.NETです。
VBからVCのDLLへ構造体配列を引き渡し、
VCにてデータの取得、また、結果の設定を行いたいと思っています。
VB、VCの6.0ならば、
SafeArrayを使用すれば構造体配列を獲得できるのは分かったのですが、
.NETでは、コンパイルは通るものの途中で飛んでしまいます。
なお、
VBを6.0で同じように作成してから.NETのVCをコールする場合、
SafeArrayでもOKでした。
ただ、
どちらも.NETになるためにVBとVCを結合すると問題になりました。
マニュアルなどを見ると、
System.Arrayから派生したCObArrayを使用するみたいなことが書いてありましたが、使用方法が読み取れませんでした。
また、過去ログにもVB6.0の引き渡し方のみのっているようでした。
よろしければ、
.NETによる構造体配列の引渡し方についてご指導お願いします。
以下のような構造体配列を引き渡すつもりです。
VB−−−−−−−
Private Structure EMPLOYEE
Dim Name As String
Dim Address As String
Dim Age As Short
End Structure
VC−−−−−−−
typedef struct {
BSTR Name;
BSTR Address;
short Age;
} EMPLOYEE;
その構造体が、レジストリに登録されたデータ型で
あるかどうかにもよりますが、とりあえず、
StructLayoutAttribute属性クラスや、
MarshalAsAttribute属性クラスについて調べて見るとか。
VBのプロジェクトを .NET で開いて変換してみるとか…(手抜きw)
VB6.0で作成して動作したプロジェクトを.NETへ変換しても動作はしませんでした。
ひとまず、
構造体のみで渡してみたら、その場合はうまく行くのに?
構造体配列になると無理でした。
VB−−−−
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Public Structure EMPLOYEE
Dim Name As String
Dim Address As String
Dim Age As Short
End Structure
Private Declare Function func Lib "vc.dll" (ByRef emp As EMPLOYEE) As Integer
Private Sub Command1_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles Command1.Click
Dim emp As EMPLOYEE
foo(emp) ' ← 構造体の引渡しをしてみました。
End Sub
VC−−−−
typedef struct {
BSTR Name;
BSTR Address;
short Age;
} EMPLOYEE;
extern "C" __declspec(dllexport) int __stdcall func(EMPLOYEE *ppsa);
extern "C" __declspec(dllexport) int __stdcall func(EMPLOYEE *ppsa)
{
char Name[128];
char Address[128];
short Age;
static wchar_t bufw[FOOBUFSIZE];
setlocale( LC_ALL, "" );
::SysFreeString( ppsa->Name );
::SysFreeString( ppsa->Address );
// とりあえず、構造体へデータを設定してみました。
memset( Name, NULL, sizeofName);
memset( Address, NULL, sizeof(Address) );
sprintf( Name, "vc−testname−−−−−−★!" );
sprintf( Address, "vc−testaddress−−−−−−★!" );
Age = 10;
mbstowcs(bufw, Name, FOOBUFSIZE);
ppsa->Name = ::SysAllocString( bufw );
mbstowcs(bufw, Address, FOOBUFSIZE);
ppsa->Address = ::SysAllocString( bufw );
ppsa->Age = Age;
return 0;
}
これがうまくいったので、
単純な配列として扱えればいいなぁとやってみたのですが、飛んでしまいました。
単純にVCで引数を配列のように使っても駄目みたいです。
一応、
VBから配列の1エントリ毎にCALLすると動くみたいでした。
VB−−−−
Private Sub Command1_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles Command1.Click
Dim emp(2) As EMPLOYEE
foo(emp(0)) ' ← 構造体(1番目)の引渡しをしてみました。
foo(emp(1)) ' ← 構造体(2番目)の引渡しをしてみました。
foo(emp(2)) ' ← 構造体(3番目)の引渡しをしてみました。
End Sub
これなら、
設定できるんですが、配列としての処理方法は無いのでしょうか?
VB6.0からはVC++での処理は可能なので、
VB側の設定を変更してVB6.0のように渡すことはできませんか?
StructLayoutAttribute属性クラス
MarshalAsAttribute属性クラス
についてもヘルプを見たりしたのですが、
使い方が分かりませんでした。
どこかにサンプルソースなどがあればご教授願います。
まず、構造体配列の場合、1次元なのですが、文字列情報が含まれて
います。よって、ポインタのポインタになります。
C言語の場合の配列を渡す場合、ポインタのポインタになるのと同様
です。また、配列ですから、文字情報が無くてもポインタのポインタ
になるはずです。
よって、文字情報が入っているので、ポインタのポインタの先に、
文字情報(固定ではない可変長文字列)が格納されているポインタが
あることになり、よって、文字領域ごとにコピーしてやらないと、
解決できないような格好になっています。
テスト的に文字情報を数値型の変数に変更すれば、いきなり動作して
しまいます。簡単にするならば、文字情報が入った構造体を全て1度
で渡さずに、構造体の要素1つづつを渡せば、現状でも動作している
ので、よいのではないでしょうか・・・
BSTRを含む構造体配列を渡す方法は、ActiveXの折に私もやっていま
したが、.NETになってからは、まだやっていません。
解決策は、魔界の仮面弁士の言われるとおり、
StructLayoutAttribute属性クラス
MarshalAsAttribute属性クラス
この部分を読破して理解しなければならないと思います。
参考になりませんが・・・
以上。
マイクロソフトのサイトで、このケースに非常に近いサンプル等が
あるのが解りました。
参考までに・・・
文字列に対する既定のマーシャリング
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpguide/html/cpcondefaultmarshalingforstrings.asp
OutArrayOfStructs のサンプル
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpguide/html/cpconosinfosample.asp
以上。
VBにて、
次のように設定すると構造体配列の値をVCへ渡すことはできました。
VC側では普通の構造体配列と同様に回せました。
Declare Auto Sub SomeFunc Lib "vc.dll" ( _
<MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> ByVal emp() As EMPLOYEE, ByVal size As Long)
しかし、
これでは「ByVal」しか設定ができないため、
VBから入力構造体配列を渡すときはともかく、
出力構造体配列を渡すときに結果を設定して戻すことができませんでした。
また、
岡田 之仁さんがおっしゃるようにポインタのポインタで渡しているため、
VBでは普通に渡してから、VCがわにて「char**」にて取得して、
構造体配列の先頭アドレスを取得もできました。
しかし、
その場合は構造体の書き換えが成功はしたものの、
複数領域を渡したはずなのに、VBに戻ってみると要素数が1つになっていました。
これは、
領域が壊れたんでしょうか?
配列の領域がどうアドレス空間で設定されているかについての
資料はヘルプにあるのでしょうか?
見つけれなかったので、良ければご教授お願いします。
VBのMarsaAsの指定では、
<MarshalAs(UnmanagedType.SafeArray)>
が使用できますが、
これはVB.NETで6,0のセーフ配列を作成すると言う意味なんでしょうか?
設定しても、パラメータがおかしいのか飛んでしまいます。
MarshalAsAttribute.SafeArraySubType フィールドとも関連して設定するらしいのですが、MSDNを見ると「VT_ARRAY」を設定すれば良いのかとやってみましたが、駄目でした。
MSDNには「SAFEARRAY ポインタを示します。」と記述されていたに?
もしかして、
この辺りをうまく使えば、6.0形式でVBからVCへ渡せるんでしょうか?
VBの関数宣言にて、
Declare Auto Sub SomeFunc Lib "vc.dll" ( _
<[In](), Out()> ByVal emp() As EMPLOYEE, ByVal size As Long)
InAttribute と OutAttribute をパラメータに使用したら、
構造体の配列をVCへ渡すことができました。
また、不思議なことに?
「ByVal」で渡しているのにもかかわらず値の変更が反映されたました。
「ByRef」が参照渡しなのに不思議です?
ツイート | ![]() |