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

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

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

VB.NETからC++のdll関数への参照渡しについて

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

■91199 / inTopicNo.1)  VB.NETからC++のdll関数への参照渡しについて
  
□投稿者/ まる (1回)-(2019/06/06(Thu) 07:46:56)

分類:[.NET 全般] 

分かりづらいタイトルで申し訳ございません。はじめての投稿になるため至らない点もあるかと思いますが、よろしくお願いいたします。
VisualStudio2013 Framework4.0を使用しております。

元々vb6で作られていたソースをvb.netで作り直すことになりました。
元のvb6ではc++で作られたdllを参照しており、まず、今回問題となる部分はVB6では大体以下のように記述されておりました。

(VB6)
sub Sample()
Dim intArray(0 To 999) As Integer
Dim objTest As New TestClass
Dim lngReturn As Long
Dim lngSize As Long = 368

(省略) ※省略した中でintArrayの全ての要素に0を設定しています。

lngReturn = objTest.AAA(intArray(0),lngSize)
end sub

上記で生成した「TestClass」が、C++のdllにあるクラスです。
C++での記述はおおまかには以下のようになっています。

(C++)
class TestClass{
Long AAA(Short Far* sAdd,Short sSize) {
Short bufSize = sSize * 2;
Char *buf = (Char *)sAdd;
Char *strValue;

memset(buf,0,bufSize);

(省略)※strValueに値を格納;

memcpy(buf,strValue,bufSize);


Char chStr[256];

wsprintf(chStr , "Add : %d, Value : %d", sAdd, *sAdd);
MessageBox(NULL , chStr , TEXT("Test") , MB_OK);

Return bufSize;
}
}

上記のメッセージボックスをVB6で実行したときに表示すると、
Add : (9ケタの可変の数字), Value : -12345
と表示されます。

vb6でのIntegerはVb.NETではShort、vb6でのLongはVB.NETではIntegerだと、Webを調べてそのような情報を見つけたので、それらを踏まえてVB.Netでは以下のように記述いたしました。

(VB.Net)
sub Sample()
Dim shtArray(999) As Short
Dim objTest As New TestClass
Dim intReturn As Integer
Dim intSize As Integer = 368

(省略) ※省略した中でshtArrayの全ての要素に0を設定しています。

intReturn = objTest.AAA(shtArray(0),intSize)
'※visualstudioでAAAにカーソルをあてて引数の型を確認したところ、
' AAA(Byref sBuf As Short,ByVal sSize As Short) As Integer
' と表示されておりました。

Msgbox(intReturn)
end sub


これを実行したところ、objTest.AAAのreturn前に表示されるメッセージボックスには、
Add : (7ケタの可変の数字), Value : -12345
と表示され、その直後、returnの部分でアプリが強制終了しました。この強制終了をなんとか解消したいです。VB6ではなぜ上手くいき、vb.netにした途端にダメになったのかも知りたいです。
(vb.netやC++のソースにTryを記述しましたが、何も例外を捕捉することができませんでした)

まず、VB6ではC++の関数に渡ったアドレスは9ケタであったのに対して、VB.Netの場合は7ケタになっているのが気になりました。
また、vb.netでは動的に変数のアドレスが変わる、と聞いたことがあるため、その点も原因の一つなのではないか、とは少し考えております。

AAA内のmemset,memcpyなどが怪しいと思い、試しにその2行だけコメントアウトしたところ、強制終了しなくなったため(逆に、その2行のコメントアウトを解除すると強制終了しました)、その2行に何かあるのではないかと考えておりますが、C++のソースコードは極力変えたくないです。
また、VB6ではなぜ問題なく、vb.netではダメなのか、なぜreturnの時にエラーが発生するのかがわかりません。

C++dll(objTest)の全ての関数やプロパティが呼べないわけではなく、
引数に参照渡しを行わない関数やプロパティをvb.netから呼び出した際には、VB6と同じように問題なく動作しているのですが、
上記のような配列変数の参照渡しを行う関数に対してはエラーが発生しております。
そのため、アドレスが固定できていないのだろうか、と考え、以下のようにして固定して試みましたが、方法がよくなかったのか同じようにreturnで強制終了してしまいました。

(VB.Net アドレス固定)
sub Sample()
Dim shtArray(999) As Short
Dim objTest As New TestClass
Dim intReturn As Integer
Dim intSize As Integer = 368
Dim objHandle As GCHandle = GCHandle.Alloc(shtArray(0),GCHandleType.Pinned)
Dim intPinned As Intptr = objHandle.AddrOfPinnedObject()

(省略) ※省略した中でshtArrayの全ての要素に0を設定しています。

intReturn = objTest.AAA(shtArray(0),intSize)
Msgbox(intReturn)

objHandle.Free()
end sub

となると、浅学ながら、やはり配列を参照渡しにしている部分が問題ではないか、とは思うのですが、改善策も何も目処が立っておりません。
どんな些細な情報でもよいので、どうかご指南いただけないでしょうか。
説明不足がございましたら、恐れ入りますがご指摘いただけますと幸いです。
何卒よろしくお願いいたします。
引用返信 編集キー/
■91202 / inTopicNo.2)  Re[1]: VB.NETからC++のdll関数への参照渡しについて
□投稿者/ 魔界の仮面弁士 (2196回)-(2019/06/06(Thu) 08:40:04)
No91199 (まる さん) に返信
> 元のvb6ではc++で作られたdllを参照しており、

Declare して使うタイプの DLL ではなく、
参照設定して使う ActiveX DLL ということでしょうか。


Declare や DllImport して使うタイプだとしたら、
 Function AAA( <MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> ByVal sAdd As Short(), ByVal sSize As Short) As Integer
でどうでしょう。

ActiveX DLL の場合はタイプライブラリ次第かな…。
引用返信 編集キー/
■91214 / inTopicNo.3)  Re[2]: VB.NETからC++のdll関数への参照渡しについて
□投稿者/ まる (2回)-(2019/06/06(Thu) 12:15:07)
No91202 (魔界の仮面弁士 さん) に返信
魔界の仮面弁士さん、早速のご回答ありがとうございます。
いつも様々な場所でのご回答に幾度となく助けられております。
本題と関係なく申し訳ございませんが、この場を借りてお礼申し上げます。

> Declare して使うタイプの DLL ではなく、
> 参照設定して使う ActiveX DLL ということでしょうか。
はい。oleのdllで、regsvr32で登録したものをプロジェクトからCOM参照しております。

> ActiveX DLL の場合はタイプライブラリ次第かな…。
ActiveXに対して不勉強のため、正しいかどうかわかりかねているのですが、
odlファイルにてタイプライブラリの作成に必要なものが記載されているようでした。
cpp,hpp,odlそれぞれに記載されたAAAの宣言を編集する必要がある、という事でしょうか…?
引用返信 編集キー/
■91223 / inTopicNo.4)  Re[3]: VB.NETからC++のdll関数への参照渡しについて
□投稿者/ 魔界の仮面弁士 (2198回)-(2019/06/06(Thu) 15:20:06)
2019/06/07(Fri) 11:30:36 編集(投稿者)

※Console.WriteLine 時にスペルミスしていたので、コードを再整理

No91214 (まる さん) に返信
> VB6ではなぜ上手くいき

参照設定で配列を渡す場合は、SAFEARRAY や VARIANT で丸ごと受け渡していたので、
配列の先頭要素を参照渡しする方法は経験が無いのですよね…。

Declare な方では、先頭要素を参照渡しする方法も良く使っていたのですが。


> cpp,hpp,odlそれぞれに記載されたAAAの宣言を編集する必要がある、という事でしょうか…?

C++ 側は専門外なので、こっちは何とも。


>>> Dim objHandle As GCHandle = GCHandle.Alloc(shtArray(0),GCHandleType.Pinned)
>>> Dim intPinned As Intptr = objHandle.AddrOfPinnedObject()


intPinned をどうやって DLL に渡すのかが読み取れなかったのですが、
先頭要素だけを Pinned しても駄目だと思います。


Imports System.Runtime.InteropServices
Module Module1

  Sub Main()
    Dim bin As Byte() = {&H11, &H22, &H33, &H44, &H55, &H66, &H77, &H88, &H99, &HAA, &HBB, &HCC, &HDD, &HEE, &HFF}
    'Dim bin As Byte() = {&HFE, &HDC, &HBA, &H98, &H76, &H54, &H32, &H1F, &HED, &HCB, &HA9, &H87, &H65, &H43, &H21}

    Console.WriteLine("=== bin (サンプルデータ) ===")
    Dump(bin)

    ' bin よりも少し大きめに確保
    Dim ary1 As Short() = Enumerable.Repeat(&H1234S, 20).ToArray()
    Dim ary2 As Short() = Enumerable.Repeat(&H1234S, 20).ToArray()

    Console.WriteLine("=== Short 配列全体を Pinned した場合 ===")
    Dim h1 As GCHandle = GCHandle.Alloc(ary1, GCHandleType.Pinned)
    CopyMemory(bin, h1)
    h1.Free()

    Console.WriteLine("=== Short 配列の先頭要素だけを Pinned した場合 ===")
    Dim h2 As GCHandle = GCHandle.Alloc(ary2(0), GCHandleType.Pinned)
    CopyMemory(bin, h2)
    h2.Free()

    Console.WriteLine("=== ary1 は書き換わっている ===")
    Dump(ary1)

    Console.WriteLine("=== ary2 は書き換わらない ===")
    Dump(ary2)

    Console.ReadLine()
  End Sub

  Sub CopyMemory(source As Byte(), h As GCHandle)
    Dim t As Type = h.Target.GetType()
    Console.WriteLine("Target Is {0} / {1}", TypeName(h.Target), t.Name)
    Dim et As Type = If(t.IsArray(), t.GetElementType(), t)
    Dim sz As Integer = Marshal.SizeOf(et)
    Dim p As IntPtr = h.AddrOfPinnedObject()
    Console.WriteLine("p => 0x{0:X} (address)", p)

    Console.WriteLine("--- コピー前 ---", p)
    Console.WriteLine("p = 0x{0:X}", Marshal.ReadInt64(p))
    For n = 0 To 3
      Console.WriteLine("p[{0}] = 0x{1:X}", n, Marshal.PtrToStructure(IntPtr.Add(p, n * sz), et))
    Next

    Marshal.Copy(source, 0, p, source.Length)

    Console.WriteLine("--- コピー後 ---", p)
    Console.WriteLine("p = 0x{0:X}", Marshal.ReadInt64(p))
    For n = 0 To 3
      Console.WriteLine("p[{0}] = 0x{1:X}", n, Marshal.PtrToStructure(IntPtr.Add(p, n * sz), et))
    Next
  End Sub

  Sub Dump(Of T As Structure)(ary As T())
    Dim bytes As Integer = Marshal.SizeOf(Of T)()
    Console.Write(" Len={0}:", bytes * ary.Length)
    Dim fmt As String = " {0:X" & CStr(2 * bytes) & "}"
    For Each x As T In ary
      Console.Write(fmt, x)
    Next
    Console.WriteLine()
  End Sub
End Module
引用返信 編集キー/
■91224 / inTopicNo.5)  Re[4]: VB.NETからC++のdll関数への参照渡しについて
□投稿者/ とっちゃん (600回)-(2019/06/06(Thu) 15:56:48)
No91214 (まる さん) に返信
>>ActiveX DLL の場合はタイプライブラリ次第かな…。
> ActiveXに対して不勉強のため、正しいかどうかわかりかねているのですが、
> odlファイルにてタイプライブラリの作成に必要なものが記載されているようでした。
> cpp,hpp,odlそれぞれに記載されたAAAの宣言を編集する必要がある、という事でしょうか…?

C++のDLL(ActiveX コントロール)はそのまま使う想定ですか?
もしそのCOMDLLをそのまま使うのなら、開発マシンに登録して(しておかないと参照できない)
参照で追加すればいいと思います。

そのうえで、VB.NET から見えるインターフェース定義を改めて確認し
どういう風に変更すればいいかを見極めたほうが安定します。

VB6のCOM呼び出しは、当時のVB自身の仕組みの効果もあり信じられないくらい
それで行けるのか?という項目があるので、ちょっと怪しい独自COMの場合
移植不可能というのがあります。

No91223 (魔界の仮面弁士 さん) に返信
> Declare な方では、先頭要素を参照渡しする方法も良く使っていたのですが。
>
基本は一緒です。
ただ、declare しないから、取り込まれた定義に合わせた書き方にする必要があるというくらいかな。
どういう風に取り込まれるかで書き方も変わるので何とも言えないところもありますけど。

引用返信 編集キー/
■91268 / inTopicNo.6)  Re[5]: VB.NETからC++のdll関数への参照渡しについて
□投稿者/ まる (3回)-(2019/06/09(Sun) 23:38:16)
2019/06/09(Sun) 23:38:54 編集(投稿者)
2019/06/09(Sun) 23:38:46 編集(投稿者)

返信が遅れてしまい申し訳ございません。投稿者(まる)です。

No91224 (とっちゃん さん) に返信
> VB6のCOM呼び出しは、当時のVB自身の仕組みの効果もあり信じられないくらい
> それで行けるのか?という項目があるので、ちょっと怪しい独自COMの場合
> 移植不可能というのがあります。
とっちゃんさん、ご回答いただきありがとうございます。
そうだったのですね。VB6ではなぜ可能だったのにvb.netでは駄目になったのか、少し納得できたような気がします。


No91223 (魔界の仮面弁士 さん) に返信
>intPinned をどうやって DLL に渡すのかが読み取れなかったのですが、
すみません。まだGCHandleに対して勉強不足な部分があり、
 GCHandle.Alloc(shtArray(0),GCHandleType.Pinned)
でshtArray(0)を固定したら、intPinnedをAAAに渡さずshtArray(0)を渡しても、固定されたまま渡せるのかな?と
浅い知識で書いてしまいました。仮面弁士さんに書いて頂いたわかりやすいソースコードのおかげで、
アドレス固定の方法について、より理解することができました。ご丁寧に説明して頂き、ありがとうございます。
ただ、
 GCHandle.Alloc(shtArray,GCHandleType.Pinned)
と書いた上で再度挑んでみましたが、エラーを解消することはできませんでした。


あの後、vb.net側が駄目ならC++のソースコードを少し変えてみよう、と自分なりに視点を変えて挑み、
以下のURLを参考にC++の内容を変えてみたところ、とりあえずはエラーなく上手くいくようになりました。
https://limbioliong.wordpress.com/2011/06/08/passing-a-managed-array-to-a-c-activex-via-a-pointer/
https://social.msdn.microsoft.com/Forums/ja-JP/254aadda-5558-4076-b598-95aebc208f43/65315652831236312425653276531565336124081250912452125311247912?forum=csharpgeneralja


(c++ .odlファイル)
[id(xx)] AAA(SHORT *add,SHORT size)

[id(xx)] AAA([in, out] SAFEARRAY(Short)*varData,Short data)


(c++ .hpp)
Long AAA(Short Far* sAdd,Short sSize)

Long AAA(VARIANT &varData,Short sSize)


(C++ .cppファイル)
※ディスマッチマップの部分
DISP_FUNCTION_ID(xxx, "AAA", AAA, VT_I4, VTS_PI2, VTS_I2)

DISP_FUNCTION_ID(xxx, "AAA", AAA, VT_I4, VTS_VARANT, VTS_I2)


※AAA関数処理の部分
LONG AAA(VARIANT &varData, sSize)
{
Short bufSize = sSize * 2;
Char *buf;
Char *strValue;
SAFEARRAY *pSA = NULL;
SAFEARRAYBOUND bd;
Short HUGEP *p;

bd.lLbound = 0;
bd.cElements = 12;
pSA = SafeArrayCreate(VT_I2, 1, &bd);
SafeArrayAccessData(pSA, (void HUGEP **)&p);
buf = (Char *)p;

memset(buf,0,bufSize);

(省略)※strValueに値を格納;

memcpy(buf,strValue,bufSize);

pSA->pvData = (LPVOID)p;
SafeArrayUnaccessData(pSA);
*pVarData.pparray = pSA;

return bufSize;
}


(VB.Net)
sub Sample()
Dim shtArray(999) As Short
Dim objTest As New TestClass
Dim intReturn As Integer
Dim intSize As Integer = 368

(省略) ※省略した中でshtArrayの全ての要素に0を設定しています。

intReturn = objTest.AAA(shtArray,intSize)
'※visualstudioでAAAにカーソルをあてて引数の型を確認したところ、
' AAA(Byref sBuf As System.Array,ByVal sSize As Short) As Integer
' となっておりました。

Msgbox(intReturn)
end sub


しかし、この件についてメンバーに相談したところ、結局、ActiveXは古いので、この機会にC++をvb.netにするところからはじめよう、ということになり、
この件は保留となってしまいました。
ですが、個人的には今回の件の中で、皆さまのおかげで色々な知見を得られ、成長することができたので、エラー解決に挑んだことは無駄ではなかったと感じております。
ご回答頂いた仮面弁士さん、とっちゃんさん、目を通していただいた方々、本当にありがとうございました。
またの機会がございましたら、どうぞよろしくお願いいたします。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ