引数が構造体のVBのDLLの関数をVBAから呼び出すには?

解決


yy  2012-10-10 09:11:50  No: 147864  IP: [192.*.*.*]

VB.NETのCOM相互運用機能の登録をONにして作成したDLLで定義されている構造体を、VBAから使用するにはどのように記述すればよいのでしょうか。

例えば、下記のコードでは、Dim t_o As T1の部分で「コンパイルエラー:Visual Basicでサポートさけていないオートメーションが変数で使用されています」というエラーになります。Dim t_o As vb_o.T1と記述しても「コンパイルエラー:ユーザ定義型は定義されていません。」というエラーになります。どのように記述すれば、VB側で定義した構造体T1をVBA側で使えるのでしょうか。

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

Public Class Class1

    Public Structure T1
        Dim x As String
        Dim y As Integer
    End Structure

    Public Sub fx(ByRef t1_o As T1)
        MsgBox(t1_o.x & t1_o.y)
    End Sub

End Class


---VBA側---

Option Explicit

Public vb_o As New ClassLibrary1.Class1

'Public Type T1
'  x As String
'  y As Integer
'End Type

Public Sub test()
' Dim t_o As T1
  Dim t_o As vb_o.T1
  t_o.x = "ABC"
  t_o.y = 123
  call vb_o.fx(t_o)
End Sub

-----------

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

編集 削除
shu  2012-10-10 10:18:34  No: 147865  IP: [192.*.*.*]

詳しくみてませんが、
vb_o.T1
ではなく
ClassLibrary1.Class1.T1
ではどうでしょう?

編集 削除
yy  2012-10-10 10:44:10  No: 147866  IP: [192.*.*.*]

Dim t_o As ClassLibrary1.Class1.T1
としてみましたが、t_o As ClassLibrary1.Class1.T1の部分で「コンパイルエラー:型が一致しません」というエラーが出ます。

なお、型が一致しない時は
Dim t_o As (型) ClassLibrary1.Class1.T1
というようにキャストする方法がありますが、(型)の部分の記述が分かりません。

よろしくお願いします。

編集 削除
shu  2012-10-10 11:44:53  No: 147867  IP: [192.*.*.*]

> Dim t_o As ClassLibrary1.Class1.T1
> としてみましたが、t_o As ClassLibrary1.Class1.T1の部分で「コンパイルエラー:型が一致しません」というエラーが出ます。
だとするとVBAでInnerクラスを使うことが出来ないかVariant宣言にしてClass1のメソッドによりT1のインスタンスを作成する必要があるかも。

編集 削除
yy  2012-10-10 15:01:58  No: 147868  IP: [192.*.*.*]

「Variant宣言にしてClass1のメソッドによりT1のインスタンスを作成する」の部分がよく分からなかったのですが、以下のようにしましたが、New ClassLibrary1.Class1.T1の部分で「コンパイルエラー:型が一致しません」というエラーが出ます。

---VBA側---

Option Explicit

Public Sub test()
  Dim VariantObject As Variant
  Set VariantObject = New ClassLibrary1.Class1.T1
  t_o.x = "ABC"
  t_o.y = 123
  call vb_o.fx(t_o)
End Sub

-----------

よろしくお願いします。

編集 削除
shu  2012-10-10 15:20:34  No: 147869  IP: [192.*.*.*]

こんな感じでどうでしょう?
これだとVariantなのでメンバアクセスが結局大変なので
T1の宣言をClass外にした方がVBA側からの処理は書きやすいと思います。

Public Class Class1
   〜
    Public Function CreateT1() as T1
        Dim ret as T1
        Return ret
    End Function
   〜
End Class

> Set VariantObject = New ClassLibrary1.Class1.T1
これだとInnerクラス(構造体)へアクセスしているので同じになってしまいます。
Set VariantObject = vb_o.CreateT1()
のようにClass1のメソッドの戻りとして取得する必要があると思います。

編集 削除
yy  2012-10-10 16:13:51  No: 147870  IP: [192.*.*.*]

なるほど。VB側に実装したメソッドを呼び出して構造体T1を取得するということですね。
下記のようなコードで確認してみましたが、取得した構造体T1がVariant型の変数に入らないような感じです。
Set t_o = vb_o.CreateT1()の部分で「実行時エラー'5':プロシージャの呼び出し、または引数が不正です。」というエラーになります。戻り値が未定義になっていましたので、下記のコメントのようにダミーの値を入れてみましたが同じエラーになります。

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

Public Class Class1

    Public Structure T1
        Dim x As String
        Dim y As Integer
    End Structure

    Public Sub fx(ByRef t1_o As T1)
        MsgBox(t1_o.x & t1_o.y)
    End Sub

    Public Function CreateT1() As T1
        Dim ret As T1
       'ret.x="XXX"
       'ret.y=0
        Return ret
    End Function

End Class

---VBA側(標準モジュール名Module1)---
Option Explicit

Public vb_o As New ClassLibrary1.Class1

Public Sub test()

  Dim t_o As Variant
  Set t_o = vb_o.CreateT1()
  t_o.x = "ABC"
  t_o.y = 123
  Call vb_o.fx(t_o)

End Sub
-------------------------------------

よろしくお願いします。

編集 削除
shu  2012-10-10 17:22:24  No: 147871  IP: [192.*.*.*]

レス内容に間違いがあったようです。
Inner宣言が駄目なのではなく構造体がCom公開されていないのが
原因のようです。ComClassによる公開が必要そうです。
Structure宣言をCom公開することが出来ないようでした。

編集 削除
魔界の仮面弁士  2012-10-10 17:25:55  No: 147872  IP: [192.*.*.*]

Class1 クラスの下に、T1 構造体を配置しているようですが、
COM 側の名前空間は階層構造に出来ないと思います。

この場合タイプライブラリ上では、T1 ユーザー定義型の位置は
Class1 の下層ではなく、Class1 と同等の階層となるでしょう。

VBA 側から見える型名が As ClassLibrary1.T1 になるのか、
それとも、As ClassLibrary1.Class1_T1 になるのかは、
タイプライブラリ次第ですが、いずれにせよ、
ClassLibrary1.Class1.T1 のようにはなれないはずです。



> 「コンパイルエラー:Visual Basicでサポートさけていない
> オートメーションが変数で使用されています」
VBA で使われる String 型は、COM 的には「BSTR」と呼ばれる型です。

ユーザー定義型で String 型を用いたい場合には、String 型を
BSTR 型にマーシャリングする必要があります。


'----------------------------------------------
Imports System.Runtime.InteropServices
Public Class Class1
    Public Structure T1
        'MarshalAs を指定していない場合は、
        <MarshalAs(UnmanagedType.BStr)> Public x As String
        Public y As Integer
    End Structure

    Public Sub fx(ByRef t1_o As T1)
        MsgBox(t1_o.x & t1_o.y)
    End Sub
End Class
'----------------------------------------------
Public vb_o As New ClassLibrary1.Class1
Public Sub test()
    Dim t_o As T1

    t_o.x = "Hello," & ChrW(&H4F60) & "好"
    t_o.y = 123

    Call vb_o.fx(t_o)
End Sub
'----------------------------------------------



>  call vb_o.fx(t_o)
現状のコードだと、VBA 側で「fx」がコード補完されない気がします。

レイトバインドにて呼び出せるので、今のコードでも問題は無いですが、
VBA 側の IntelliSense を使えるようにしたいのであれば、
インターフェイスを明示実装した上で、ClassInterfaceType.None を
指定しておくとよいでしょう。


Imports System.Runtime.InteropServices

<ClassInterface(ClassInterfaceType.None)> _
Public Class Class1
    Implements IClass1

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

    Public Sub fx(ByRef t1_o As T1) Implements IClass1.fx
        MsgBox(t1_o.x & t1_o.y)
    End Sub

    Public Interface IClass1
        Sub fx(ByRef t1_o As T1)
    End Interface
End Class


余力があれば、GuidAttribute も指定しておくと安全です。

編集 削除
yy  2012-10-11 09:38:43  No: 147873  IP: [192.*.*.*]

shuさん。

  いろいろ調べていただきましてありがとうごさいました。


魔界の仮面弁士さん。

  まさに教えて欲しかったことそのものです。
  サンプルコードがありましたのでとてもよかったです。
  問題が解決しただけでなく
  ・COM側の名前空間は階層構造に出来ない。
  ・VBAのString 型は、COMではBSTR型になる。
  ・マーシャリングの記述の仕方
  ・インターフェイスの記述の仕方
  などのノウハウも理解できて、
  初心者の私にとってはたいへん参考になりました。
  ありがとうございました。

編集 削除
yy  2012-10-12 08:16:12  No: 147874  IP: [192.*.*.*]

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

編集 削除