C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[10]: VB.netからVC++6.0のへの構造体配列


(過去ログ 127 を表示中)

[トピック内 28 記事 (1 - 20 表示)]  << 0 | 1 >>

■75306 / inTopicNo.1)  VB.netからVC++6.0のへの構造体配列参照渡し
  
□投稿者/ とら (1回)-(2015/03/12(Thu) 19:36:57)

分類:[VB.NET/VB2005 以降] 

2015/03/12(Thu) 19:55:24 編集(投稿者)
2015/03/12(Thu) 19:54:46 編集(投稿者)
2015/03/12(Thu) 19:54:40 編集(投稿者)

<pre><pre>VB6.0で作成した実行ファイルからVC++6.0で作成したDLLに値を参照渡ししているものがあり、
このたびVB.net2008にアップグレードする事になりました。(なぜ2013ではないのかは置いといてください)

アップグレードウィザードを通してエラーや警告を除外した後に動作確認をしたところ、DLLの呼出し時に
予期せぬ動作が起きていて詰まっています。

DLLを呼び出す際に構造体の配列を参照渡ししているのですが、配列を10で作成し参照渡しして
戻ってきた配列が1つになってしまいます。

色々調べてみたところ、値渡しにすると大丈夫(配列なのでそもそも参照型だから参照の値渡しになる)
といった記述があったので試してみたのですが、配列は10のままで戻ってくるようになったのですが、
値が格納されていません。


解決策をご存じの方、お教え頂けないでしょうか。


以下ソースになります。


-- 宣言部 ----

Declare Function Test1 Lib "TestFunc.DLL" (ByRef oStructure() as Structure1) as Integer


Public Structure Structure1
Dim int1 As Integer
Dim int2 As Integer
Dim int3 As Integer
End Structure


'配列の作成(10個の配列として宣言)
Public oStructure1(9) As Structure1


-- 処理 ----

'DLLの呼出し
Dim iRet as integer = -1
iRet = Test1(oStructure1)

'呼出し後に参照で渡した(oStructure1)の数が1つになっている



・構造体の中身はすべてInt型です。
・BV6.0で作成したモジュールからは正常に取得できています。
・VC++6.0のDLLは変更しない方向で決定なので、DLLには手を加える事ができません
・VC++6.0側が配列を受け取った時点で1つになっています。



以上、よろしくお願いします。
引用返信 編集キー/
■75307 / inTopicNo.2)  Re[1]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ Hongliang (294回)-(2015/03/12(Thu) 20:15:53)
C++の関数/構造体定義も見ないとはっきりとは断言できませんが…。

> (ByRef oStructure() as Structure1) as Integer
はByValになるはずです。

> 構造体の中身はすべてInt型です。
Int型って何でしょう? VBならIntegerですし、C++ならint型(C/C++は大文字小文字を区別する)です。
VBでもVB6までとそれ以降では別物です。

> 呼出し後に参照で渡した(oStructure1)の数が1つになっている
「(oStructure1)の数」というのは、どうやって確認した物ですか?
引用返信 編集キー/
■75308 / inTopicNo.3)  Re[2]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ とら (2回)-(2015/03/12(Thu) 20:22:17)
説明不足が結構ありました、すみません


>>(ByRef oStructure() as Structure1) as Integer
> はByValになるはずです。

値渡しのByValにして配列が10個で戻ってくる事は確認しました。
ただ、戻ってきた配列の中身を確認したところ、値が変化していませんでした。


>>構造体の中身はすべてInt型です。
> Int型って何でしょう? VBならIntegerですし、C++ならint型(C/C++は大文字小文字を区別する)です。
> VBでもVB6までとそれ以降では別物です。

Int型というかInteger型です、省略してすみません


>>呼出し後に参照で渡した(oStructure1)の数が1つになっている
> 「(oStructure1)の数」というのは、どうやって確認した物ですか?

VC++6.0をデバッグ実行し確認しました。


以上、引き続きお願い致します。

引用返信 編集キー/
■75309 / inTopicNo.4)  Re[1]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (226回)-(2015/03/12(Thu) 21:22:29)
No75306 (とら さん) に返信
> VB6.0で作成した実行ファイルからVC++6.0で作成したDLLに値を参照渡ししているものがあり、
> このたびVB.net2008にアップグレードする事になりました。

この文章だけだと、VB6 EXE から VB2008 DLL を呼ぶ方向にも読み取れてしまいますね。

なお、製品名に .NET の名を冠するのは 2003までなので、
正確には、VB.net2008 → VB2008 だったりしますが、それはさておき。


> アップグレードウィザードを通してエラーや警告を除外した後に

提示されたコードは恐らく、実際のコードとは別物ですよね。
(実コードを貼り付けたなら、as が小文字表記されているはずが無いので…)

「改修に失敗している現在のコード」だけではなく、
「正常動作している改修前のコード」も提示していただけないでしょうか。
もしくは DLL 側のコードでも良いですけど。


自分が、VB6 でそのような DLL を呼び出すとすれば、
 Declare Function Test1 Lib "TestFunc" (ByRef oStructure As Structure1) As Long
 Dim udt(9) As Structure1
 ret = Test1( udt(0) )
もしくは
 Declare Function Test1 Lib "TestFunc" (ByVal oStructure As Long) As Long
 Dim udt(9) As Structure1
 ret = Test1( VarPtr( udt(0) ) )
で実装することが多いのですが、とらさんの提示された(改修後の)コードは
構造体配列の参照渡しになっているようですし…。


実際に検証してみたいところですが、現状は情報不足のため、以下、思いつきで回答。
未検証ゆえ、正しい内容であるかどうかは保証できませんが。


> Public Structure Structure1
構造体には StructLayoutAttribute を明示しておくことをお奨めします。
たとえば、その DLL のパッキングサイズが 4 バイト単位なら、
『<StructLayout(LayoutKind.Sequential, Pack:=4)>』など。


> '呼出し後に参照で渡した(oStructure1)の数が1つになっている
引数に MarshalAsAttribute を付与しておいてください。
SizeConst が指定されていない場合、マーシャリングされるのは 1 つの要素だけだったはず。



> 色々調べてみたところ、値渡しにすると大丈夫(配列なのでそもそも参照型だから参照の値渡しになる)
> といった記述があったので試してみたのですが、配列は10のままで戻ってくるようになったのですが、
> 値が格納されていません。
配列を値渡しした場合、それは In パラメーターとしてマーシャリングされるためかと。
結果を受け取る必要があるのなら、<InAttribute(), OutAttribute()> を付与しておいてください。

https://msdn.microsoft.com/ja-jp/library/hk9wyw21.aspx
引用返信 編集キー/
■75310 / inTopicNo.5)  Re[2]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ とら (3回)-(2015/03/12(Thu) 21:59:05)
No75306 (魔界の仮面弁士 さん) に返信


>>VB6.0で作成した実行ファイルからVC++6.0で作成したDLLに値を参照渡ししているものがあり、
>>このたびVB.net2008にアップグレードする事になりました。
>
> この文章だけだと、VB6 EXE から VB2008 DLL を呼ぶ方向にも読み取れてしまいますね。
>
> なお、製品名に .NET の名を冠するのは 2003までなので、
> 正確には、VB.net2008 → VB2008 だったりしますが、それはさておき。

なるほど、以後留意致します。



>>アップグレードウィザードを通してエラーや警告を除外した後に
>
> 提示されたコードは恐らく、実際のコードとは別物ですよね。
> (実コードを貼り付けたなら、as が小文字表記されているはずが無いので…)
>
> 「改修に失敗している現在のコード」だけではなく、
> 「正常動作している改修前のコード」も提示していただけないでしょうか。
> もしくは DLL 側のコードでも良いですけど。

アップグレードとエラー及び警告の対応は他の方がされていたので少々混乱しておりました。

アップグレード前のVB6.0の該当部分のソースになります。

-- 宣言部 ----

Declare Function Test1 Lib "TestFunc.DLL" (oStructure() As Any) As Long

Public Structure Structure1
Dim int1 As Long
Dim int2 As Long
Dim int3 As Long
End Structure


'配列の作成(10個の配列として宣言)
Public oStructure1(0 To 9) As Structure1


-- 処理 ----

'DLLの呼出し
Dim iRet as integer = -1
iRet = Test1(oStructure1())


>> Public Structure Structure1
> 構造体には StructLayoutAttribute を明示しておくことをお奨めします。
> たとえば、その DLL のパッキングサイズが 4 バイト単位なら、
> 『<StructLayout(LayoutKind.Sequential, Pack:=4)>』など。
>
>> '呼出し後に参照で渡した(oStructure1)の数が1つになっている
> 引数に MarshalAsAttribute を付与しておいてください。
> SizeConst が指定されていない場合、マーシャリングされるのは 1 つの要素だけだったはず。
>
>>色々調べてみたところ、値渡しにすると大丈夫(配列なのでそもそも参照型だから参照の値渡しになる)
>>といった記述があったので試してみたのですが、配列は10のままで戻ってくるようになったのですが、
>>値が格納されていません。
> 配列を値渡しした場合、それは In パラメーターとしてマーシャリングされるためかと。
> 結果を受け取る必要があるのなら、<InAttribute(), OutAttribute()> を付与しておいてください。


取り急ぎ、宣言部を下記のようにByValに変更し<InAttribute(), OutAttribute()> を付与してみましたが
配列数は10になったものの値は変わっていませんでした。

Imports System.Runtime.InteropServices

Declare Function Test1 Lib "TestFunc.DLL" (<InAttribute(), OutAttribute()> ByVal oStructure() As Structure1) As Long



確認は明日になりますが、引き続き宜しくお願い致します。

引用返信 編集キー/
■75311 / inTopicNo.6)  Re[3]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (227回)-(2015/03/13(Fri) 01:21:10)
No75310 (とら さん) に返信
> Declare Function Test1 Lib "TestFunc.DLL" (oStructure() As Any) As Long
> Public Structure Structure1
> Dim int1 As Long
> Dim int2 As Long
> Dim int3 As Long
> End Structure
> Dim iRet as integer = -1

上記は何のコードでしょうか?

Dim 宣言部で値を挿入していることから、VB2008 のようにも見えますが、
VB.NET に「Any」は無いはずです。別途 Class Any などを作ってあるなら別ですが。


> アップグレード前のVB6.0の該当部分のソースになります。
VB6 に「Structure」という宣言はありませんし、どうみても文法エラーです。
できるだけ実際の状況に即したコードの提示をお願いします。


もし、元のコードをそのまま提供することに問題があるのであれば、
『第三者が現象を再現できる最小限の実験用コード』を提示してみてください。
質問内容がぶれたままだと回答側もキツイです…。


> Declare Function Test1 Lib "TestFunc.DLL" (<InAttribute(), OutAttribute()> ByVal oStructure() As Structure1) As Long

戻り値の型も変更されたのですか?
もし、旧VB で As Long なのだとしたら、.NET 側では Int32 。最初の投稿通りの As Integer で良いですよ。

もしも本当に 64bit整数型 を扱っていたのだとしても(GetDiskFreeSpaceEx API など)、
当時それに見合う型は Currency ぐらいしかなかったはずです。
(7.0 以降であれば、VB.NET の As Long に相当する As LongLong があったりしますが)



> 取り急ぎ、宣言部を下記のようにByValに変更し<InAttribute(), OutAttribute()> を付与してみましたが
> 配列数は10になったものの値は変わっていませんでした。

ByVal oStructure() As Structure1 な引数で呼び出してみましたが、
 Out 属性なし → 呼び出し前後で引数の内容は変化しない。(DLL 側に値は渡っている)
 Out 属性あり → 呼び出し後に、DLL で書き換えた構造体値が反映されている。
ということで、当方では問題ないことを確認しています。


なお、とらさんの DLL がどういう実装になっているのか説明が無かったので、
ひとまず int __stdcall Test1(Structure1 *a); な実装で実験しています。
(手元に VC++6.0 はないので、2010 を用いています)
引用返信 編集キー/
■75312 / inTopicNo.7)  Re[3]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (228回)-(2015/03/13(Fri) 09:36:15)
No75308 (とら さん) に返信
>>>呼出し後に参照で渡した(oStructure1)の数が1つになっている
>>「(oStructure1)の数」というのは、どうやって確認した物ですか?
> VC++6.0をデバッグ実行し確認しました。

VB EXE 側ではなく、VC++ DLL 側のデバッガで要素数を確認されたのでしょうか。

今回の配列って、VB 側でメモリ確保されたものですよね。

配列要素の先頭要素をアドレス渡ししているのだとしたら、
C++ 側では要素数は分からないように思うのですが、違うのかな。


もし、引数が C スタイルの配列ではなく、COM の SAFEARRAY で宣言されていたなら
C++ 側で要素数を確認することもできるでしょうけれども、その場合、VB6 では
「Type ステートメントで型定義されたユーザー定義型」の配列ではなく、
「タイプライブラリにて型定義された構造体」の配列になっていたはず。

でも、実際にはそうでは無かったのですよね…?
引用返信 編集キー/
■75314 / inTopicNo.8)  Re[4]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ とら (4回)-(2015/03/13(Fri) 11:28:42)
No75312 (魔界の仮面弁士 さん) に返信

お手数をお掛けして申し訳御座いません。
再度徹底して見直してまとめなおしました。


>VB EXE 側ではなく、VC++ DLL 側のデバッガで要素数を確認されたのでしょうか。
VB2008側及びVC++側のデバッグ双方にて確認致しました。
VCで受け取った際に既に要素数が1個になっていたようなのでVB2008側での問題だと思いました。



VC++6.0側は内容が理解できなかったのでメソッドの宣言部のみです

VC++6.0側のType1に関しては、「受信データを格納する構造体へのポインタ
または受信データを格納した共有メモリの管理構造体へのポインタ」
と記載がありました


## VC++6.0 ###########

Test1_API int __stdcall Test1(LPSAFEARRAY *Type1)


## VB6.0 #############

-- 宣言部 ----

Declare Function Test1 Lib "TestFunc.DLL" (oType() As Any) As Long

Public Type Type1
Long1 As Long
Long2 As Long
Long3 As Long
End Type

Public oType(0 To 9) As Type1


-- 処理 ----

Dim iRet As Long
iRet = Test1(oType())



## VB2008 アップグレード後にエラー及び警告を排除した状態で渡されたソース #############

-- 宣言部 ----

Declare Function Test1 Lib "TestFunc.DLL" (ByRef oStructure() As Structure1) As Integer

Public Structure Structure1
Dim int1 As Integer
Dim int2 As Integer
Dim int3 As Integer
End Structure

Public oStructure1(9) As Structure1


-- 処理 ----

Dim iRet As integer = -1
iRet = Test1(oStructure1)

'結果としては配列の要素数が1個になっており、値も変更されていない



## VB2008 ご指摘部分反映後 #############


'参照渡しを値渡しに変更しマーシャリングを追加

Imports System.Runtime.InteropServices

Declare Function Test1 Lib "TestFunc.DLL" (<InAttribute(), OutAttribute()> ByVal oStructure() As Structure1) As Integer

'結果としては配列の要素数は10個になったものの値は変更されていない



以上、引き続きお願い致します。

引用返信 編集キー/
■75321 / inTopicNo.9)  Re[5]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (231回)-(2015/03/13(Fri) 15:08:55)
No75314 (とら さん) に返信
> Test1_API int __stdcall Test1(LPSAFEARRAY *Type1)
そっちでしたか! まさかと思ったら、本当にセーフ配列だったという。(^_^;


> VC++6.0側は内容が理解できなかったのでメソッドの宣言部のみです
私も C++ は専門外(というかホボ素人)なので、そこまで想像が追いつきませんでした。
ユーザー定義型の配列を SAFEARRAY でやりとしたことは無いんですよね…。


> Declare Function Test1 Lib "TestFunc.DLL" (ByRef oStructure() As Structure1) As Integer

MarshalAs 指定は必要になるだろうということで、SafeArray 指定で
 ・SafeArray
 ・SafeArray, SafeArraySubType:=VT_USERDEFINED
 ・SafeArray, SafeArraySubType:=VT_RECORD, SafeArrayUserDefinedSubType:=GetType(Structure1)
などを試してみましたが、HRESULT が E_INVALIDARG(0x80070057) を返すばかり。

LPArray でも試してみましたが、
 ・LPArray, ArraySubType:=AsAny
 ・LPArray, ArraySubType:=LPStruct
 ・LPArray, ArraySubType:=Struct
実行はできるものの、配列が 1 個になってしまう様子。

かといってここに SizeConst や SizeParamIndex を指定すれば、
MarshalDirectiveException が発生するという状況。


はてさて…。

https://msdn.microsoft.com/ja-jp/library/z6cfh6e6.aspx
引用返信 編集キー/
■75332 / inTopicNo.10)  Re[6]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ とら (6回)-(2015/03/16(Mon) 14:18:00)
No75321 (魔界の仮面弁士 さん) に返信

>>Test1_API int __stdcall Test1(LPSAFEARRAY *Type1)
> そっちでしたか! まさかと思ったら、本当にセーフ配列だったという。(^_^;

すみません、VC++との連携自体が初なので、どの程度情報が必要なのかもよく理解しておりませんでした。


>>Declare Function Test1 Lib "TestFunc.DLL" (ByRef oStructure() As Structure1) As Integer
>
> MarshalAs 指定は必要になるだろうということで、SafeArray 指定で
>  ・SafeArray
>  ・SafeArray, SafeArraySubType:=VT_USERDEFINED
>  ・SafeArray, SafeArraySubType:=VT_RECORD, SafeArrayUserDefinedSubType:=GetType(Structure1)
> などを試してみましたが、HRESULT が E_INVALIDARG(0x80070057) を返すばかり。

> LPArray でも試してみましたが、
>  ・LPArray, ArraySubType:=AsAny
>  ・LPArray, ArraySubType:=LPStruct
>  ・LPArray, ArraySubType:=Struct
> 実行はできるものの、配列が 1 個になってしまう様子。
>
> かといってここに SizeConst や SizeParamIndex を指定すれば、
> MarshalDirectiveException が発生するという状況。

マーシャリング自体もよく理解しておらず、私の方でも調査しているものの難航しております。


引き続き他の方々もご存知の方がおられましたらご回答頂ければ助かります。

引用返信 編集キー/
■75334 / inTopicNo.11)  Re[7]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (238回)-(2015/03/16(Mon) 17:22:24)
No75332 (とら さん) に返信
> マーシャリング自体もよく理解しておらず、私の方でも調査しているものの難航しております。

カスタムマーシャラを用意することになるかも。
https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/9fa766b2-885e-48e3-9eb8-8f4148af04e4
引用返信 編集キー/
■75336 / inTopicNo.12)  Re[4]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (239回)-(2015/03/16(Mon) 19:34:59)
2015/03/17(Tue) 10:06:34 編集(投稿者)

No75312 (魔界の仮面弁士) に追記
> もし、引数が C スタイルの配列ではなく、COM の SAFEARRAY で宣言されていたなら
> C++ 側で要素数を確認することもできるでしょうけれども、その場合、VB6 では
> 「Type ステートメントで型定義されたユーザー定義型」の配列ではなく、
> 「タイプライブラリにて型定義された構造体」の配列になっていたはず。

駄目元で、型定義を公開してみました。
P/Invoke の際に必要な手続きなのかどうかは分かりませんけど。
(COM Interop だった場合は、型公開が必要なはず…?)


(1) AssemblyInfo.vb にて、
   <Assembly: ComVisible(True)>
   <Assembly: Guid("……")>
  を指定しておきます。プロジェクトのプロパティの[アプリケーション]-[アセンブリ情報]でも指定可。

(2) 構造体側にも、また別の GUID を割り当てておきます。

  <ComVisible(True), Guid("……"), StructLayout(LayoutKind.Sequential)> _
  Public Structure Structure1
    <MarshalAs(UnmanagedType.I4)> Public aaa As Integer
    <MarshalAs(UnmanagedType.I4)> Public bbb As Integer
    <MarshalAs(UnmanagedType.I4)> Public ccc As Integer
  End Structure


実際の Guid 属性には、 Visual Studio 付属の GUIDGEN.EXE もしくは
「TextBox.Text = Guid.NewGuid().ToString("D")」などで生成した値を指定します。


(3) スタートメニューの "Visual Studio コマンドプロンプト" を右クリックして管理者起動し、
 「RegAsm C:\YourFolder\YourAppName.EXE」を実行します。(実際のパスにあわせてください)

 『型は正常に登録されました。』などのメッセージがされれば登録完了。

この段階で、レジストリの
 HKEY_CLASSES_ROOT\Record\{構造体のGuid}\バージョン番号\
に、構造体が記録されるはず。


---------

上記を実施したうえで、SafeArraySubType を変更してみたり、
Object 型でしてみたりと試行錯誤していたところ、

・配列が 1 件になることもなく呼び出せたものの、
 DLL 側での編集結果が反映されないケース(中身が呼び出し時のまま)。

に至りました。好転したかと思いきや、同じコードのまま
 AccessViolationException「保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。」
が発生することもあったので、恐らくは誤った呼び出し方のようです。…難しいですね。

他のパターンとしては、
 SafeArrayTypeMismatchException「指定された配列は期待されたタイプではありません。」
で呼び出せなかったり、呼び出せても配列が 1 件になってしまう状況だったりしており、
解決までの道のりは長そうです。


ただ、以前と同じ Declare 宣言でも、構造体定義を RegAsm するかどうかで
エラーの内容が変わる組み合わせがあったので、公開すること自体は
まったくの無意味では無さそうに思います。(確証は持てませんが)


No75332 (とら さん) に返信
> 引き続き他の方々もご存知の方がおられましたらご回答頂ければ助かります。

SAFEARRAY** や LPSAFEARRAY* での構造体受け渡しに関する質問は
ネット上でも度々見かけますが、成功事例がなかなか見当たりません。

セーフ配列を使わず、配列先頭ポインタと配列長を渡す実装に変更して回避するケース、
質問を取り下げて諦めるケース、中継用のヘルパーライブラリを用意して回避したケース、
VB/C# 側でSAFEARRAY 相当のメモリ構造を組み上げてそれを IntPtr で渡すケース、
カスタムマーシャラを作るケースなどなど…。


そんな中、こんな記事を見つけました。参考になりますか?

"Marshaling a SAFEARRAY of Managed Structures by P/Invoke"
https://limbioliong.wordpress.com/category/netcomactivex-interop/safearrays/

私自身まだ読みきっていませんが、標題から推察するに、今回の案件に相当する話題かと。
引用返信 編集キー/
■75340 / inTopicNo.13)  Re[5]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ とら (8回)-(2015/03/17(Tue) 17:13:07)
No75336 (魔界の仮面弁士 さん) に返信

> ただ、以前と同じ Declare 宣言でも、構造体定義を RegAsm するかどうかで
> エラーの内容が変わる組み合わせがあったので、公開すること自体は
> まったくの無意味では無さそうに思います。(確証は持てませんが)

色々と詳細に調査して頂き感謝しきれません、有難う御座います。


> セーフ配列を使わず、配列先頭ポインタと配列長を渡す実装に変更して回避するケース、
> 質問を取り下げて諦めるケース、中継用のヘルパーライブラリを用意して回避したケース、
> VB/C# 側でSAFEARRAY 相当のメモリ構造を組み上げてそれを IntPtr で渡すケース、
> カスタムマーシャラを作るケースなどなど…。

今回はあまり時間的な余裕もなく、これ以上VC++側に手を加えない方向で進むのは難しいと考え
不本意ではありますが他の手法で行ってみます。

取り急ぎ、
> セーフ配列を使わず、配列先頭ポインタと配列長を渡す実装に変更して回避するケース、
について調査してみます。

一応質問は閉めず、結果の書き込みまたは別途上記ケースについて質問があった場合に追記致します。
(恐らくVC側での質問を行う事になると思いますが……。)

引用返信 編集キー/
■75354 / inTopicNo.14)  Re[6]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ とら (9回)-(2015/03/18(Wed) 14:04:58)
今回の問題点を解決するにあたっての方針を以下のようにするつもりです。
(ただし、当方VC++というかC++系はほとんど触れた事がありません、ポインタ等も概念程度を僅かに知っている程度です)

@ VC++6.0をVC++2008へとアップグレードする。

A VC++にて構造体配列の先頭データ1つと配列の要素数を受け取るように変更する。

B VBで使用している構造体と同じように構造体を宣言し、VBから呼び出されたメソッドにて構造体配列を作成する。

C VBから呼び出されたメソッドでは、引数で受け取った構造体配列を他のメソッドにそのまま渡していたので、
  引数で受け取った構造体配列を他メソッドに渡すのではなく、Bにて作成した構造体配列を渡すようにする。

D VCで作成した構造体配列をVBから受け取った構造体配列に代入する事により値を返却する。


改修イメージは以下のようになります。

 改修前
    TEST_API int __stdcall FuncTest(LPSAFEARRAY *Type1)
      {
        int nRet=0;
        nRet = FuncTestSub(Type1);
        return nRet;
      }

 改修後
    //A
    TEST_API int __stdcall FuncTest(LPSAFEARRAY *Type1, int AryUBound)
      {
        //B
        typedef struct {
          int lMemoryHandle;
          int lMemoryAddress;
          int lSemaphoreHandel;
        } T_Struct;

        //B
        T_Struct aryT_Struct[AryUBound];

        //C
        int nRet=0;
        nRet = FuncTestSub(aryT_Struct);

        //D
        Type1 = aryT_Struct

        return nRet;
      }


現状では、とりあえずVCのデバッグ実行にて値が格納されるか確認しようとしたところ、下記のようなエラーが発生しました。

「保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。」

構造体配列の宣言または渡し方が悪いのか分かりません。


また、Dの代入する際の注意点等あればお教え頂ければと思います。


以上、宜しくお願い致します。

引用返信 編集キー/
■75355 / inTopicNo.15)  Re[7]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 774RR (242回)-(2015/03/18(Wed) 14:35:51)
えっと、提示 C コードは全面的にどうにもならない感じなんだけどどこまで解説がいるのかな?

2.は文字で書いている仕様とは意味が違う
3.は自動変数だからこの関数終了時に消失する
5.はポインタのコピーなので配列のコピーぢゃない
ということで、現在のコードはまるっきり意味が無い感じ。

VB (というかマネージ側) でどういう marshal をしたいのかが(少なくとも俺には)わからないのでまずはそこから。
仕様を先に決めて、実装はそれに合致するようにするもんだ。

C/C++ 使ったこと無いんだよね・・・
とりあえず http://www.pinvoke.net の何か適当な Win32API の関数でわかるものある?
http://www.pinvoke.net/default.aspx/hid.HidD_GetAttributes
とか読める?

引用返信 編集キー/
■75356 / inTopicNo.16)  Re[7]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (244回)-(2015/03/18(Wed) 15:16:21)
No75354 (とら さん) に返信
> 今回の問題点を解決するにあたっての方針を以下のようにするつもりです。
C++ 側の実装に自信が無いのなら、元の DLL はそのままにしておいて、
その DLL を呼ぶための中継役として、VB6 で ActiveX DLL を作っておき、
それを VB2008 側で参照設定するという逃げ方とか。(泣)

構造体のままだとマーシャリングに不安がありそうなので、
中継段階でクラスモジュールに詰め替えるとか。

まぁ、これは冗長的過ぎるので、元の DLL を変更して良いのなら、そっちの方が早そう。


> (ただし、当方VC++というかC++系はほとんど触れた事がありません、ポインタ等も概念程度を僅かに知っている程度です)
同じく。



> TEST_API int __stdcall FuncTest(LPSAFEARRAY *Type1, int AryUBound)
セーフ配列の受け渡しでトラブっているですから、
LPSAFEARRAY 型のままにしておいたら意味が無いですよ。


> VC++にて構造体配列の先頭データ1つと配列の要素数を受け取るように変更する。
こんな実装かな。

 TEST_API int __stdcall Test1(T_Struct*type1, int length)
引用返信 編集キー/
■75357 / inTopicNo.17)  Re[8]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ とら (10回)-(2015/03/18(Wed) 16:21:20)
No75355 (774RR さん) に返信
> えっと、提示 C コードは全面的にどうにもならない感じなんだけどどこまで解説がいるのかな?
> ということで、現在のコードはまるっきり意味が無い感じ。

紛らわしくて誠に申し訳御座いません、提示したソースはあくまで「改修【イメージ】」のつもりでした。
前述した通り、当方にはVC++に関する知識が殆ど御座いませんので、VC++のお勉強をして下さいというご指摘でしたら
大変申し上げ難いのですが、今回はあまり時間的な余裕が御座いませんのでまた別の機会にしたく考えております。


> VB (というかマネージ側) でどういう marshal をしたいのかが(少なくとも俺には)わからないのでまずはそこから。
> 仕様を先に決めて、実装はそれに合致するようにするもんだ。

これまでのやり取りには目を通されてますか?過去のやりとりを見てそれでも解らない、または情報がばらばらで読み難い
ということであれば再度まとめ直させて頂きます。
それとも毎回きっちりまとめ直した方が宜しいのですか?こちらの掲示板はそういった方針なのでしょうか。
如何せんこのような掲示板にて質問するのは初なもので、色々と不慣れでご迷惑をお掛けしてしまい誠に申し訳御座いません。


> C/C++ 使ったこと無いんだよね・・・
> とりあえず http://www.pinvoke.net の何か適当な Win32API の関数でわかるものある?
> http://www.pinvoke.net/default.aspx/hid.HidD_GetAttributes
> とか読める?

何を以て読めるという判断になるか解りませんが、構造体の宣言を行っている例なのかなと
当方では認識しましたが如何でしょうか。




No75356 (魔界の仮面弁士 さん) に返信
> C++ 側の実装に自信が無いのなら、元の DLL はそのままにしておいて、
> その DLL を呼ぶための中継役として、VB6 で ActiveX DLL を作っておき、
> それを VB2008 側で参照設定するという逃げ方とか。(泣)

当方の知識的にはその方法が一番良いのですが、VB6.0を残すのはだめだそうです。


>>TEST_API int __stdcall FuncTest(LPSAFEARRAY *Type1, int AryUBound)
> セーフ配列の受け渡しでトラブっているですから、
> LPSAFEARRAY 型のままにしておいたら意味が無いですよ。

>>VC++にて構造体配列の先頭データ1つと配列の要素数を受け取るように変更する。
>  TEST_API int __stdcall Test1(T_Struct*type1, int length)

なるほど、配列の先頭だけというのは要素数1の配列を受け取らせるのではなく、
1つの構造体として受け取らせるという事なのですね。

VB側は配列の先頭である「構造体配列(0)」と「構造体配列の要素数」を渡し、
VC++側は「構造体」と「構造体配列の要素数」を受け取ってから、構造体配列を作成して
作成した構造体配列の先頭のポインタ?に対して受け取った構造体のポインタ?を設定する
感じになるのですかね……。
イメージは解るのですが手法がさっぱり……、まぁイメージが間違ってる可能性も多々ありますが。


なお、VC++側が構造体配列を受け取った後に次の関数へ渡していたのですが、その受け先はvoid型となっていました。
TestSub_API int __stdcall FuncTestSub (void *Type1);


VC++側において、今のところも書き方が良く解っておらず難航しております。
解決に向けたアドバイスが頂ければとても助かります。

引用返信 編集キー/
■75358 / inTopicNo.18)  Re[9]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 774RR (243回)-(2015/03/18(Wed) 17:10:07)
俺は、今までの発言の中に SAFEARRAY の実体を取得しているのがどこかは書かれていないと読んでいる。
現在既にある VB6 プログラムの動作上、通信バッファに相当する SAFEARRAY の実体を
1. VB6 EXE 側が確保して VC DLL に渡し、用済み後 VB6 EXE が処分している
2. VC DLL が取得・処分処理をカプセル化していて (malloc/free 相当処理を VC DLL 側で行っていて)
VB6 EXE 側はメモリの取得・処分の詳細を知らなくてすむようになっている
どちらだろう。

1. なら通信バッファは VB.NET 側で取得した manage memory を使うことになり、
 単純に VC DLL に向けて marshal すればいいので簡単だろう。
2. なら通信バッファは VC DLL 側で取得した unmanage memory を使うことになり
 VB.NET 側で unmanage memory を使う細工が必要で面倒と思われる。

どう marshal したいかってのはこういう意味だし 1 2 で実装は異なってくる。

引用返信 編集キー/
■75359 / inTopicNo.19)  Re[9]: VB.netからVC++6.0のへの構造体配列参照渡し
□投稿者/ 魔界の仮面弁士 (245回)-(2015/03/18(Wed) 17:38:11)
2015/03/18(Wed) 17:50:38 編集(投稿者)

No75357 (とら さん) に返信
> 前述した通り、当方にはVC++に関する知識が殆ど御座いませんので、VC++のお勉強をして下さいというご指摘でしたら
> 大変申し上げ難いのですが、今回はあまり時間的な余裕が御座いませんのでまた別の機会にしたく考えております。

DLL の改修を行うのはとらさんでしょうか?
それとも専任者がいるのでしょうか?

セーフ配列を C 形式配列に置き換える作業を、
VC++の知識無しで行うのは無理があるかと…。



> 1つの構造体として受け取らせるという事なのですね。

C 配列なので、配列先頭要素のポインタを渡すというだけで。
1 つの構造体として渡す、という表現だと語弊があるかな…。


そもそも 1 つだけならば、 配列である必要性も無いので、
T_Struct* ではなく T_Struct でも渡せますしね。
(DLL 側で構造体の値を書き換える必要がある場合は別ですが)

VB6 の場合はユーザー定義型を値渡しできないため、Declare の段階で
構造体メンバーのスタックを展開して引数化する必要がありましたが、
VB2008 はそうした制限が無いので、そのまま ByVal x As T_Struct で渡せます。

WindowFromPoint API などがこれにあたります。
 HWND WindowFromPoint( POINT Point );
上記は、POINT 構造体(≠POINT 構造体のポインタ) を受け取るものですが、
VB6 では構造体を ByVal で渡せないため、「ByVal x As Long, ByVal y As Long」
と書き換える必要がありましたが、VB2008 では「ByVal x As POINT」と書けます。



話を戻して:

セーフ配列やマネージ配列の場合は、配列自体が要素数情報を保持していますが、
C 配列はただのポインタなので、サイズ情報がありません。
要素数が必要になるのはそのためです。
配列と共に、その配列の要素数を表す引数を併用する場合は、
MarshalAs 属性の SizeParamIndex で指定してください。

ただし、常に要素数が固定という仕様なのであれば、
配列要素数をわざわざ指定しなくても良いでしょう。


> VC++側は「構造体」と「構造体配列の要素数」を受け取ってから、
それだと、配列を受け取れませんよね。

VC++側は構造体ではなく、構造体のポインタを受け取るようにします。
VB2008 側は「<[In](), Out()> ByVal x() As 構造体, ByVal length As Integer」という感じですね。


> VB側は配列の先頭である「構造体配列(0)」と「構造体配列の要素数」を渡し、
「構造体配列(0)」ではなく、配列そのものを渡して下さい。

「構造体配列(0) のアドレスを値渡し」したり
「構造体配列(0) を参照渡し」する手法が通じるのは、
あくまでも VB6 の場合です。

VB2008 の場合、マネージ配列のメモリ位置が動的に変更される可能性があるため、
先頭要素だけ渡す方法は安全ではありません。もし、そのような渡し方をする必要が
あるのなら、GCHandle.AddrOfPinnedObject でピン止めする必要があります。


> なお、VC++側が構造体配列を受け取った後に次の関数へ渡していたのですが、その受け先はvoid型となっていました。
> TestSub_API int __stdcall FuncTestSub (void *Type1);
おぉ、ブラックボックス…!

DLL 側で、C 配列をセーフ配列に事前に詰め替えれば、上記を呼び出すことはできるでしょう。
問題は、上記が Type1 引数の情報を読み取るだけでなく、変更していた場合の書き戻し処理です。

構造体の値を書き換えるぐらいなら、さほど問題はありません。
呼び出し時に OutAttribute をつけておく程度で良いと思います。


問題は、配列サイズの変更がありえる場合です。

セーフ配列と異なり、C 配列だと DLL 側で配列サイズを
動的に増やすといったことはできないですから。


要素数を表す引数については、int length ではなく int* length に
するだけで良いと思いますが(この場合は、VB 側も ByRef Integer になる)、
問題は配列本体の方。
もしもサイズの動的変更がありえるのでれば、呼び出し元(VB2008 側)にて、
あらかじめ十分な要素数を確保しておき、その中でやりくりするような
DLL 実装に書き換えるなど、少々面倒な実装になりそうです。
引用返信 編集キー/
■75362 / inTopicNo.20)  Re[9]: VB.netからVC++6.0のへの構造体配列
 
□投稿者/ Azulean (453回)-(2015/03/18(Wed) 22:14:29)
2015/03/19(Thu) 07:19:40 編集(投稿者)

No75357 (とら さん) に返信
> 前述した通り、当方にはVC++に関する知識が殆ど御座いませんので、VC++のお勉強をして下さいというご指摘でしたら
> 大変申し上げ難いのですが、今回はあまり時間的な余裕が御座いませんのでまた別の機会にしたく考えております。

ビジネスですか?趣味ですか?学術的なものですか?
趣味や研究、学問の範囲であればその判断に異議を唱えるつもりはありません。
しかし、ビジネスであるのであれば、再考を提案します。


C/C++ は VB6/VB.NET に比べるとかなり難しい領域です。
いかようにも書けてしまうため、サンプルコード単独では正しくとも、周囲のソースコードと組み合わせたらまずいケースもあり得ます。
ソースコードを理解し、適切な対応をとるための準備ができないのであれば、C/C++ を修正するという考えは捨てた方がいいと断言できるぐらいです。


私が選択肢を考えるとすれば…

(1)C/C++ 経験のある熟練した開発者を助っ人に頼む
(2)リスクを説明し、「VB6 を捨てる」という方針を緩和してもらい、問題の部分だけ VB6 でラップする。
(3)リスクを説明し、十分な学習時間・検証時間をとる
(4)リスクを説明し、リーダーや上長に責任を持ってもらう(責任転嫁)

でしょうか。

「第三者がそんな強権的な発言するな」と思われるかもしれません。

ソフトウェア開発業界で仕事している立場からすると、今のまま進めたら、後で悲惨な未来が待っているかもしれないことを危惧しております。
納期ばかり気にするのではなく、修正したソースコードで不具合が出たら誰が責任を負うのか、提供先で不具合が出たときの損害額はいかほどか、そのとき自分はどんな立場に立たされるのか、責任追及されるのか、それらの発生確率・影響をご考慮いただければ幸いです。


------

> TestSub_API int __stdcall FuncTestSub (void *Type1);

SafeArrayAccessData で得た構造体配列の先頭ポインタを渡しているだけであれば、SafeArray の要素数が変わることはないと思っています。
予想できる振る舞いとしては、内容の書き換えぐらいまでですかね。

いずれにせよ、どのように修正するべきかは、FuncTestSub の実装とその呼び出し側のロジック次第です。
全体を見通せない第三者では、正解を示せない可能性があることをご了承ください。

(手元にソースコードがあっても、レガシーコードを触るのに十数時間から数十時間の調査・検討を重ねてやっと修正できるようなことも現実的にある世界です。。。)

----
以下推測(まったく保障のない、私の勝手な予想に過ぎません)

"TestSub_API int __stdcall FuncTestSub (void *Type1)" なら、渡した先で構造体のポインタにキャストしていると予想される。
呼び出し元で事前にキャストしていないことから、先頭ポインタを渡すことを前提としているはず。
であれば、要素数を渡さないこの関数のシグネチャからは要素数は一定以上、事実上固定のサイズと想定していそう。
そうであれば、固定長の C 形式配列を渡すように関数を書き換え、不要となった SafeArray 系のコードを整理し、P/Invoke 側も書き換えればいけそうではある。
予想もつかない突飛な実装をしていない限り…。
引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -