引数がマーシャリングした配列のVBのDLLの関数をVBAから呼び出すには?


yy  2012-10-31 11:11:46  No: 147938  IP: [192.*.*.*]

お世話になります。

「引数が構造体のVBのDLLの関数をVBAから呼び出すには?」
http://madia.world.coocan.jp/cgi-bin/vbnet/wwwlng.cgi?print+201210/12100006.txt
で教えていただきましたマーシャリングの方法で、まだよく理解できていない部分がありますので教えてください。String型の変数をマーシャリングした場合は問題ないのですが、String型の配列をマーシャリングした場合にうまくいきません。

例えば、下記のコードでは、実行時に、Call vb_o.fx(t_o)の部分で「実行時エラー'2146233054(80131522)':オートメーションエラーです。」というエラーになります。マーシャリングする必要のないInteger型の配列などではエラーにならないのですが、マーシャリングすると配列がうまく渡せなくなるようです。どのように記述すれば、マーシャリングしたString型の配列を渡すことができるのでしょうか。

---VB.NET側DLL(プロジェクト名ClassLibrary1)---

Imports System.Runtime.InteropServices

Public Class Class1

    Public Structure T1
        <MarshalAs(UnmanagedType.BStr)> Public x() As String
        Public y As Integer
    End Structure

    Public Sub fx(ByRef t1_o As T1)
        Dim i As Integer
        t1_o.y = 2 'DLL側で決まる値(配列サイズ)
        ReDim t1_o.x(t1_o.y)
        For i = 0 To t1_o.y - 1
            t1_o.x(i) = "NO" & i
        Next
    End Sub

End Class

---VBA32側---------------------------------

Option Explicit

Public vb_o As New ClassLibrary1.Class1

Public Sub test()

    Dim t_o As T1
    Dim i As Long
    
    Call vb_o.fx(t_o)
    
    MsgBox t_o.y
    For i = 0 To t_o.y - 1
      MsgBox t_o.x(i)
    Next i
    
End Sub

-------------------------------------------

よろしくお願いします。
(WindowsXP,Excel2003_VBA,VisualStudio2010)

編集 削除
魔界の仮面弁士  2012-10-31 13:44:56  No: 147939  IP: [192.*.*.*]

構造体で渡すよりも、クラスで渡した方が問題が少ないような気も。

> String型の配列をマーシャリングした場合にうまくいきません。
String を BSTR にするのは分かりますが、String() を BSTR にしてはマズイかと。

> <MarshalAs(UnmanagedType.BStr)> Public x() As String

VBA の配列は、COM 的には SAFEARRAY です。BSTR ではありません。
この場合、MarshalAs 指定では UnmanagedType.SafeArray を使う事になります。
しかしこれだけでは何型の配列か分からないため、さらに SafeArraySubType で
VarEnum.VT_BSTR を追加してください。

http://msdn.microsoft.com/ja-jp/library/vstudio/z6cfh6e6.aspx
http://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.varenum.aspx

編集 削除
yy  2012-10-31 16:46:30  No: 147940  IP: [192.*.*.*]

魔界の仮面弁士さん。

回答ありがとうございます。

教えていただきましたように以下のコードにしましたところ、エラーはでなくなりましたが、返される値が文字列ではなく数値(2183444や2183468など)になってしまっています。この原因を調べているのですが、また、教えていただきました2つのリンク先も見てみましたが内容が高度すぎてよく分かりませんでした。

---VB.NET側DLL(プロジェクト名ClassLibrary1)---

Imports System.Runtime.InteropServices

Public Class Class1

    Public Structure T1
        <MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VarEnum.VT_BSTR)> Public x() As String
        Public y As Integer
    End Structure

    Public Sub fx(ByRef t1_o As T1)
        Dim i As Integer
        t1_o.y = 2 'DLL側で決まる値(配列サイズ)
        ReDim t1_o.x(t1_o.y)
        For i = 0 To t1_o.y - 1
            t1_o.x(i) = "NO" & i
        Next
    End Sub

End Class

---VBA32側---------------------------------
(前回と同じです)
-------------------------------------------

よろしくお願いします。

編集 削除
魔界の仮面弁士  2012-10-31 17:06:30  No: 147941  IP: [192.*.*.*]

手元の環境では、提示のコードで "No0" と "No1" が返されてきました。

VBA 側で [F2] を押してオブジェクトブラウザを表示させたときに、
T1 型の x プロパティの定義が「x() As String」になっていますか?

編集 削除
yy  2012-11-01 09:00:50  No: 147942  IP: [192.*.*.*]

魔界の仮面弁士さん。

そうですか。そちらでは正しく動作しているということですね。
こちらでは以下のような状況になっています。
なぜなんでしょうかね???

【現象】
実行するとメッセージボックスが3回表示されますが
その各々は
2
2260580
2017484
と表示されています。
この状態でF2キーでオブジェクトブラウザを表示させて
<すべてのライブラリ>のプルダウンメニューで
ClassLibrary1を選択して、
クラスのリストボックスの中のT1を選択して
T1のメンバのリストボックスの中のxを選択すると
リストボックスの下に
X() As String
と表示されています。
オブジェクトブラウザを閉じて
再度実行すると今度は
2
1784292
1910700
と表示されます。
実行する度に異なる値が返されているようです。

よろしくお願いします。

編集 削除
魔界の仮面弁士  2012-11-01 20:33:17  No: 147943  IP: [192.*.*.*]

> そちらでは正しく動作しているということですね。
期待動作はしていましたが、私が提示した方法が
正しいのかどうかまでは保証できません。


> こちらでは以下のような状況になっています。
こちらの環境は下記の2パターンです。
  ・Win7 Enterprise x64, VB2010 x86, Excel 2010 x86
  ・Win7 Ultimate x64, VB2010 AnyCPU, Excel 2010 x64


> 1784292
> 1910700
> と表示されます。

そのライブラリを、(VBA ではなく)VB.NET から参照した場合はどうですか?


> 実行する度に異なる値が返されているようです。

MsgBox VarType(t_o.x(i))
MsgBox "[" & t_o.x(i) & "]"
MsgBox TypeName(t_o.x(i))

とした場合、
  「8(vbString の意味)」「不定値(1784292など)」「String」
が返されるのでしょうか?

VBA においては、
  S = "No1"
  MsgBos StrPtr(S)    '文字列の先頭ポインタ
  MsgBox VarPtr(S)    '変数のアドレス
のようにすると数値が表示されますが、何か関係があるのかな…。


> リストボックスの下に
> X() As String
> と表示されています。
この時点で何か妙ですね。元の定義を考えると、
"X() As String" ではなく、
"x() As String" になりそうなものですが…。

編集 削除
yy  2012-11-02 10:53:14  No: 147944  IP: [192.*.*.*]

魔界の仮面弁士さん

いろいろ確認していただきましてありがとうございます。


(1) >構造体で渡すよりも、クラスで渡した方が問題が少ないような気も。

    昨日からクラスで渡す方法も検討していました。
    以下のコードで確認しているのですが、
    VBE側の MsgBox t_o.x(i) のところで、
   「実行時エラー'450' 引数の数が一致しません。
     または不正なプロパティを指定しています。」
    というエラーが出るのですが、何がいけないのでしょうか。


---VB.NET側DLL(プロジェクト名ClassLibrary1)---

Public Class Class1
    Public Sub fx(ByRef t1_o As T1)
        Dim i As Integer
        t1_o.y = 2
        ReDim t1_o.x(t1_o.y)
        For i = 0 To t1_o.y - 1
            t1_o.x(i) = "NO" & i
        Next
    End Sub
End Class

Public Class T1
    Public x() As String
    Public y As Integer
End Class

---VBA側------------------------------------

Option Explicit

Public vb_o As New ClassLibrary1.Class1
Public t_o As New ClassLibrary1.T1

Public Sub test()
    Dim i As Long
    Call vb_o.fx(t_o)
    MsgBox t_o.y
    For i = 0 To t_o.y - 1
      MsgBox t_o.x(i)
    Next i
End Sub

-----------------------------------------------

(2) >そのライブラリを、(VBA ではなく)VB.NET から参照した場合はどうですか?

VB.NETのCOMのDLLをVBAではなくVB.NETから参照するということでよろしいでしょうか。
VBA32のコードをVB.NETのコードとして以下のように書き換えてみましたが、
参照の追加ダイアログのCOMタブから追加すると「'ClassLibrary1'への参照を追加できませんでした。ActiveXタイプライブラリ'C…ClassLibrary1.tlb'が.NETアセンブリからエクスポートされましたが、参照として追加できません。.NETアセンブリへの参照を追加してください。」というエラーになるのですが、VBAで使用したDLLとは異なってしまうのですが、一度、COM相互運用機能の登録のチェックボックスをOFFにして、再度ビルドし直したDLLでの確認ということでよろしいでしょうか。

---VB.NET(ConsoleApplication)--------------------------------
Imports ClassLibrary1.Class1
Module Module1
    Public vb_o As New ClassLibrary1.Class1
    Sub Main()
        Dim t_o As New T1
        Dim i As Long
        Call vb_o.fx(t_o)
        MsgBox(t_o.y)
        For i = 0 To t_o.y - 1
            MsgBox(t_o.x(i))
        Next i
    End Sub
End Module
-------------------------------------------------------------

よろしくお願いします。

編集 削除
yy  2012-11-09 08:35:19  No: 147945  IP: [192.*.*.*]

魔界の仮面弁士さん

前回(11月2日)の事前確認の返答を踏まえて回答するつもりでいましたが、返答がまだ頂けないようですので、事前確認の条件で回答します。

> こちらの環境は下記の2パターンです。
>  ・Win7 Enterprise x64, VB2010 x86, Excel 2010 x86
>  ・Win7 Ultimate x64, VB2010 AnyCPU, Excel 2010 x64

  こちらの環境は下記の1パターンです。
   ・WindowsXP Professional Version 2002 SP3
     Visual Studio 2010
     Excel 2003 SP3,VBA:Version 6.5.1054
      
> そのライブラリを、(VBA ではなく)VB.NET から参照した場合はどうですか?

  前回(11月2日)の事前確認の条件であれば、.NETから(COMでなく).NETの呼び出しなので正しく表示されています。

> MsgBox VarType(t_o.x(i))
> MsgBox "[" & t_o.x(i) & "]"
> MsgBox TypeName(t_o.x(i))
> とした場合、
>   「8(vbString の意味)」「不定値(1784292など)」「String」
> が返されるのでしょうか?

  はい。その通りです。

>VBA においては、
>  S = "No1"
>  MsgBos StrPtr(S)    '文字列の先頭ポインタ
>  MsgBox VarPtr(S)    '変数のアドレス
>のようにすると数値が表示されますが、何か関係があるのかな…。

   これらの2つの数値の表示の挙動と比べてみて、数値の桁数が同じなのと、値が毎回変化しているなどの点から、文字列の先頭ポインタが返されているような気がします。

>この時点で何か妙ですね。元の定義を考えると、
>"X() As String" ではなく、
>"x() As String" になりそうなものですが…。

  "x() As String"が正しいです。x()の部分が太字で表示されていましたので大文字のように見えましたがコピーペーストして確認しましたら小文字でした。申し訳ありませんでした。


そちらの環境で再現しないということなので、こちらから再現する方法を提示するなどしない限り問題の解決が難しいような気がしていました。また、「構造体で渡すよりもクラスで渡した方が問題が少ない」とも言われていましたので、構造体でなくてもクラスでもよいことをお伝えしたかったのと、クラスでも同様の現象が起きるのかどうかを確認したかったのでクラスでも確認してみましたが、その前に別のエラーが出てしまっていましたので、前回のようなご質問をしたのですが、構造体でなくてもクラスでもできればよいので、前回のご質問(1)の原因を教えていただけないでしょうか。もし私のコードが根本的におかしいのであれば、そちらで正しく動作するコードのサンプルを提示していただけると助かります。

よろしくお願いします。

編集 削除