DOBON.NET DOBON.NETプログラミング掲示板過去ログ

VBで作成したActiveX DLLをC#で使用する方法

環境/言語:[C#]
分類:[.NET]

いつもお世話になってます。すえぞーです。

以前にVB6.0で作成したActiveX DLLをC#で使用したいのですが、
参照設定の追加で作成したDLLを追加して試してみてもどうしても
うまく使用できないので投稿させてもらいました。
どうも私が使おうとしているDLLのクラスの階層が2つになっていなければ
動きそうなのですが、このあたりの書き方がよくわかりません。

VB6.0やVB.netだとCreateObject関数を使用してオブジェクトを作成し、
作成したオブジェクトに対してobjTest.Sub1.TestProperty = "てすと1"
のような感じで動いているのでC#でもなんらかのやり方で動くと思うのですが
どうすればよいのでしょうか?

下にテスト用のソースも貼り付けておきますのでわかる方がいらっしゃれば教えてください。
すみませんがよろしくお願いします。


VB6.0のDLLのつくりは単純に書くと以下のようになっています。

<メインのクラス>TestMainClass
------------------------------------------------------------
Private objSub1 As New clsSub1 '下の階層のクラスを宣言
Private ClassCollection As New Collection

Property Get Sub1() As Object
Set Sub1 = objSub1
End Property

Private Sub Class_Initialize()
ClassCollection.Add objSub1
End Sub
------------------------------------------------------------

<下の階層のクラス>clsSub1
命令が呼び出せればなんでもいいのですがいちおうこんな感じでテストしています。
------------------------------------------------------------
Dim sMessageData As String

'テスト用プロパティ
Property Let TestProperty(sData As String)
sMessageData = sData
End Property
Property Get TestProperty() As String
TestProperty = sMessageData
End Property

'テスト用メソッド
Public Function TestMethod()
Call MsgBox("Sub1 :" & vbCrLf & sMessageData, vbInformation)
End Function
------------------------------------------------------------

これでActiveX DLLを作成し、VB6.0やVB.netだとこのようにして使用できます。
------------------------------------------------------------
Private Sub Command1_Click()
Dim objTest As Object
Set objTest = CreateObject("DLLTest.TestMainClass") 'オブジェクト作成
objTest.Sub1.TestProperty = "てすと1" 'プロパティのテスト
Call objTest.Sub1.TestMethod 'メソッドのテスト
Set objTest = Nothing 'オブジェクト開放
End Sub
------------------------------------------------------------
> VB6.0やVB.netだとCreateObject関数を使用してオブジェクトを作成し、
> 作成したオブジェクトに対してobjTest.Sub1.TestProperty = "てすと1"
> のような感じで動いているのでC#でもなんらかのやり方で動くと思うのですが
> どうすればよいのでしょうか?
遅延バインディングですか。(^^;
JScript.NET や VB.NET ならば楽なんですが……。

とりあえず、下記を参考にしてみてください。
後半に、C#での遅延バインディング手法が掲載されています。
http://support.microsoft.com/kb/302902/
2005/09/01(Thu) 15:21:56 編集(投稿者)

> VB6.0やVB.netだとCreateObject関数を使用してオブジェクトを作成し、

C# でレイトバインドを扱うのは面倒です。
せっかく参照設定してるんですから、素直に IDE が自動生成したラッパクラスを使いましょう。
返信ありがとうございます。

いちおう紹介されているところを見させてもらったのですが予想以上に難しいですね・・・。
ちょっとどうすればよいのかわかりませんでした^^;;
もうちょっと調べてみます。
返信ありがとうございます。

> C# でレイトバインドを扱うのは面倒です。
> せっかく参照設定してるんですから、素直が自動生成したラッパクラスを使いましょう。

自分もそう思ったのですが、DLLの中のクラスの階層が2つになっているのでうまく使えませんでした。
objTest.Sub1.TestPropertyというように間にSub1というクラスが入っているので、 自動生成したラッパクラスではobjTest.Sub1の下の関数群は自動生成されないようなので・・・。
このあたりのやり方があれば教えてください。
# 使用後は、Marshal.ReleaseComObject も忘れずに。

> objTest.Sub1.TestPropertyというように間にSub1というクラスが入っているので、
??? DLL側の実装がどうなっているのか、よく分かりません……。m(_ _;)m


1. 変数objTestは、Sub1というプロパティを持つオブジェクトである。
2. Sub1プロパティは、何らかのクラス(のインスタンス)を返す。
3. そのクラスインスタンスは、さらにTestPropertyプロパティを持っている。

という状況でしょうか?
> 自分もそう思ったのですが、DLLの中のクラスの階層が2つになっているのでうま
>く使えませんでした。

「クラス階層」が何をさしているのか分かりません。
COM はクラス階層なんか表現できないし。

まず、ActiveX DLL が何個あるのか、VB6 でどう使っていたのかをもう少し詳しく教えてください。
CreateObjectと同じことをC#で行う方法を私のサイトで紹介していますので、一応書いておきます。

DOBON.NET .NET Tips - C#でCreateObjectと同じことをするには?
http://dobon.net/vb/dotnet/vb2cs/createobject.html
説明不足ですみませんでした。

>>objTest.Sub1.TestPropertyというように間にSub1というクラスが入っているので、
> ??? DLL側の実装がどうなっているのか、よく分かりません……。m(_ _;)m
>
> 1. 変数objTestは、Sub1というプロパティを持つオブジェクトである。
> 2. Sub1プロパティは、何らかのクラス(のインスタンス)を返す。
> 3. そのクラスインスタンスは、さらにTestPropertyプロパティを持っている。
>
> という状況でしょうか?

はい、そのとおりです。

クラスの構造は最初の投稿にも書いたのですが、
もっと単純にすると以下のような感じです。
clsSub1の中にはTestMethodという機能があることとします。
---------------------------------------------
<TestMainClass>
'メインのクラスです。これをC#から呼び出します。
Private objSub1 As New clsSub1

Property Get Sub1() As Object
Set Sub1 = objSub1
End Property
---------------------------------------------

これがVB6やvb.netならば以下で呼び出せます。
---------------------------------------------
Dim objTest As Object
Set objTest = CreateObject("DLLTest.TestMainClass") 'オブジェクト作成
Call objTest.Sub1.TestMethod 'メソッドのテスト
---------------------------------------------

これと同等のことをC#で実現したいのです。
よろしくお願いします。
どうも説明不足ですみませんでした。

> 「クラス階層」が何をさしているのか分かりません。
> COM はクラス階層なんか表現できないし。
>
> まず、ActiveX DLL が何個あるのか、VB6 でどう使っていたのかをもう少し詳しく教えてください。

すみません、階層という言い方がまずかったですね。
単純に呼び出したクラスの中でさらに別のクラスを呼んでいるのですが、
そのクラスの機能を使いたいのです。

ActiveX DLLはひとつです。
VB6では以下のようにして使っています。
1.CreateObjectでAというオブジェクトを作成する。
2.Aオブジェクトの中の機能を呼び出す。
例)A.Sub1.TestMethod

もっと具体的に書くとこんな感じです。
---------------------------------------------
Dim objTest As Object
Set objTest = CreateObject("DLLTest.TestMainClass") 'オブジェクト作成
Call objTest.Sub1.TestMethod 'メソッドのテスト
---------------------------------------------

ちなみにクラスの構造は、以下のような感じです。
clsSub1の中にはTestMethodという機能があることとします。
---------------------------------------------
<TestMainClass>
'メインのクラスです。これをC#から呼び出します。
Private objSub1 As New clsSub1

Property Get Sub1() As Object
Set Sub1 = objSub1
End Property
---------------------------------------------

よろしくお願いします。
返信ありがとうございます。
紹介していただいた方法もまた試してみます。
参照設定せずに呼び出すなら……こんな感じかな。

---- for C# ----
using System;
using System.Reflection;
using System.Runtime.InteropServices;
class Dobon12575 {
  static void Main() {
    Object mainClass = null;
    Object childClass = null;
    try {
      Type classType = Type.GetTypeFromProgID("DLLTest.TestMainClass");
      mainClass = Activator.CreateInstance(classType);

      childClass = mainClass.GetType().InvokeMember(
        "Sub1", BindingFlags.GetProperty, null, mainClass, null
      );
      childClass.GetType().InvokeMember(
        "TestMethod", BindingFlags.InvokeMethod, null, childClass, null
      );
    } catch( Exception theException ) {
      Console.WriteLine("Error: " + theException.Message );
    } finally {
      if(childClass != null && Marshal.IsComObject(childClass)) {
        Marshal.ReleaseComObject(childClass);
      }
      if(mainClass  != null && Marshal.IsComObject(mainClass )) {
        Marshal.ReleaseComObject(mainClass );
      }
    }
  }
}


---- for VB(Option Strict On) ----
Option Explicit On
Option Strict On
Imports System
Imports System.Runtime.InteropServices
Imports Microsoft.VisualBasic
Module Dobon12575
  Sub Main()
    Dim mainClass, childClass As Object
    Try
      mainClass = CreateObject("DLLTest.TestMainClass")
      childClass = CallByName(mainClass, "Sub1", CallType.Get)
      CallByName(childClass, "TestMethod", CallType.Method)
    Catch theException As Exception
      Console.WriteLine("Error: " & theException.Message )
    Finally
      If Not childClass Is Nothing AndAlso Marshal.IsComObject(childClass)
        Marshal.ReleaseComObject(childClass)
      End If
      If Not mainClass Is Nothing AndAlso Marshal.IsComObject(mainClass )
        Marshal.ReleaseComObject(mainClass)
      End If
    End Try
  End Sub
End Module


---- for VB(Option Strict Off) ----
Option Explicit On
Option Strict Off
Imports System
Imports System.Runtime.InteropServices
Imports Microsoft.VisualBasic
Module Dobon12575
  Sub Main()
    Dim mainClass, childClass As Object
    Try
      mainClass = CreateObject("DLLTest.TestMainClass")
      childClass = mainClass.Sub1
      childClass.TestMethod()
    Catch theException As Exception
      Console.WriteLine("Error: " & theException.Message )
    Finally
      If Not childClass Is Nothing AndAlso Marshal.IsComObject(childClass)
        Marshal.ReleaseComObject(childClass)
      End If
      If Not mainClass Is Nothing AndAlso Marshal.IsComObject(mainClass )
        Marshal.ReleaseComObject(mainClass)
      End If
    End Try
  End Sub
End Module

---- for JScript ----
import System;
import System.Reflection;
import System.Runtime.InteropServices;
var mainClass:Object;
var childClass:Object;
try {
  mainClass = new ActiveXObject("DLLTest.TestMainClass");
  childClass = mainClass.Sub1;
  childClass.TestMethod();
} catch( theException : Exception ) {
  Console.WriteLine("Error: " + theException.Message );
} finally {
  if(childClass != null && Marshal.IsComObject(childClass)) {
    Marshal.ReleaseComObject(childClass);
  }
  if(mainClass  != null && Marshal.IsComObject(mainClass )) {
    Marshal.ReleaseComObject(mainClass );
  }
}
> すみません、階層という言い方がまずかったですね。

自分語で説明されても分かりましぇん。

> 単純に呼び出したクラスの中でさらに別のクラスを呼んでいるのですが、
> そのクラスの機能を使いたいのです。
>
> ActiveX DLLはひとつです。
> VB6では以下のようにして使っています。
> 1.CreateObjectでAというオブジェクトを作成する。
> 2.Aオブジェクトの中の機能を呼び出す。
> 例)A.Sub1.TestMethod
>
> もっと具体的に書くとこんな感じです。
> ---------------------------------------------
> Dim objTest As Object
> Set objTest = CreateObject("DLLTest.TestMainClass") 'オブジェクト作成
> Call objTest.Sub1.TestMethod 'メソッドのテスト
> ---------------------------------------------

なら、何にも問題ないはずですよ。

「参照設定」によって、少なくとも DLLTest.TestMainClass のラッパクラスは出来てますよね?

それを単純に new すれば、DLLTest.TestMainClass の COM オブジェクトが生成されます。

TestMainClass objTest = new TestMainClass();
objTest.Sub1.TestMethod();

みたいな感じで TestMainClass のインスタンスを作って、メソッドを呼び出すだけ。

もし Sub1 が Object 型のプロパティか何かになっていて TestMethod() の呼び出しがエラーになるなら、また別途質問してください。
どうもありがとうございます。無事に動作いたしました。
このあたりの知識が浅かったもので非常に勉強になりました。
今後ともよろしくお願いします。
解決済み!
> もし Sub1 が Object 型のプロパティか何かになっていて TestMethod() の呼び出しがエラーになるなら、また別途質問してください。

まさにおっしゃられてるとおりのエラーになりました^^;;
これはどうすれば解決できるのでしょうか?
>>もし Sub1 が Object 型のプロパティか何かになっていて TestMethod() の呼び出しがエラーになるなら、また別途質問してください。
>
> まさにおっしゃられてるとおりのエラーになりました^^;;
> これはどうすれば解決できるのでしょうか?

汎用的な解決策は、弁士さんが示されたたように「C# でレイトバインドでメソッドを呼び出す」ことです。
ただし、VB に比べて、非常に長いコードを書かなくてはなりません。

C# 的な解決は「アーリーバインドを利用する」ことです。
もし、「参照設定」によって Sub1 の実装クラスのラッパクラスが自動作成されているなら

TestMainClass objTest = new TestMainClass();
Sub1Class objSub1 = (Sub1Class) objTest.Sub1;
objSub1.TestMethod();

という手順でイケるはずです。

あるいは、TestMainClass.Sub1 プロパティを Object 型ではなく、Sub1 の実装クラスの型で公開するのが簡単です。
そうすれば、シンプルに

TestMainClass objTest = new TestMainClass();
objTest.Sub1.TestMethos();

という手順でイケます。
ただし、これを行うためには元の ActiveX DLL に手を加えることになります。
> 汎用的な解決策は、弁士さんが示されたたように「C# でレイトバインドでメソッドを呼び出す」ことです。
> ただし、VB に比べて、非常に長いコードを書かなくてはなりません。

確かにVBと比べるとちょっと長くなっちゃいそうですね。

> C# 的な解決は「アーリーバインドを利用する」ことです。
> もし、「参照設定」によって Sub1 の実装クラスのラッパクラスが自動作成されているなら
>
> TestMainClass objTest = new TestMainClass();
> Sub1Class objSub1 = (Sub1Class) objTest.Sub1;
> objSub1.TestMethod();
>
> という手順でイケるはずです。

Sub1 の実装クラスをPublicにしてDLLを作成すると確かにできました。

>
> あるいは、TestMainClass.Sub1 プロパティを Object 型ではなく、Sub1 の実装クラスの型で公開するのが簡単です。
> そうすれば、シンプルに
>
> TestMainClass objTest = new TestMainClass();
> objTest.Sub1.TestMethos();
>
> という手順でイケます。
> ただし、これを行うためには元の ActiveX DLL に手を加えることになります。

早速試してみましたが確かにシンプルにVBと同じようにできますね。
効率を考えると元の ActiveX DLL に手を加えるのが一番いいのですが・・・。
まあいろいろとありまして難しそうです(泣

とにかくいろいろとありがとうございました。
回答をくださった皆さんのおかげでいろんなやり方がわかりました。
これからいろいろと周りと相談したりしながらBESTなやり方を選択させてもらいます。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板