Visual Basic テクニック |
VB.NET2002、VB.NET2003、VB2005からCOMオブジェクト(ActiveX)にアクセスしたり、逆にVB6, VBA, VBScriptから.NET Frameworkアセンブリにアクセスする方法を説明します。VB2005 Express Editionでもこれらの機能を利用することができます。
概要 ・VB.NET2002、VB.NET2003、VB2005ではCOMに対して参照設定するだけで、簡単にCOM(ActiveX)を使用することができる。 ・VB6、VBA、VBScriptで.NET Frameworkのアセンブリを使用するには、VB.NET2002、VB.NET2003、VB2005側であらかじめCOM仕様に準拠したアセンブリを作成しておく必要がある。 ・COM仕様に準拠したアセンブリを作成するにはCOMクラステンプレートを使用する。 ・VB2005 Express EditionにはCOMクラステンプレートが標準ではインストールされないが、フリーで公開されているのでダウンロードして組み込むことができる。 |
※Visual Basic 中学校ではコード例を掲載するときにそれがどのバージョンのVBで使用できるのかをアイコン で示していますが、今回は複数のバージョンのVBを使用するので、混乱を避けるために今回に限ってこのアイコンは「コード例がどのバージョンのVBで使用できるか」ではなく、「どのバージョンのVBについて現在説明しているか」を示すものとします。
COMとは.NET Framework以前に広く利用されていたオブジェクトの仕様です。.NET Frameworkで言うと「アセンブリ」に相当します。COM仕様に基づいたオブジェクトがActiveXです。これらはCOMコンポーネントと呼ばれる場合もあります。
VBでも参照設定を使ってActiveXを使用したり、CreateObject関数を使用してActiveXのインスタンス化を行うことができます。
マイクロソフト社はCOMを過去のものとして今後は.NET Framework アセンブリの普及に努めていくという戦略をとっていますから、新しくCOMについて勉強する魅力はそれほどありません。
けれども、2007年1月現在ではWindowsの世界では.NET Framework アセンブリよりもCOMコンポーネントの方が多く使われているのが実情です。これは過去の遺産であって長年にわたってCOMが使用され続けてきた証でもあります。
このような状況では好むと好まざるとにかかわらずVBからCOMを操作する必要があったり、逆にCOM側の環境からVBで作成したアセンブリにアクセスする必要があったりというCOMと.NET Framework アセンブリの混在環境を構築しなければならない場合があります。
特にExcelなどのOffice製品は完全にCOM仕様に準拠したオブジェクトを標準で公開しており、VBからExcelを操作する場合にはCOM仕様を利用して制御する場合が多くなります。
幸いVBではこのようなCOMと.NET Framework アセンブリの混在環境があらかじめ想定されており、かなり簡単に扱えるようになっています。マイクロソフトはこの状況を「COM相互運用」と呼んでおり、MSDNライブラリにもまとまった説明があります。
COM相互運用にはもう1つ重要な側面があります。それはVB6など過去のバージョンのVBから.NET Frameworkにアクセスするケースです。VB6をはじめOfficeに搭載されているVBAやVBScriptではCOMオブジェクトの制御は簡単に行えますが.NET Frameworkアセンブリの制御をおこなうことはできません。そこでこのような制御を行いたい場合は.NET側でCOM仕様に準拠させる形でDLLファイルを生成する必要があります。
今回はこういったCOM相互運用の具体的な利用方法を説明します。
メモ - 言葉の整理 今回使用する専門用語をごく簡単にまとめておきます。ここでは今回の説明内容から見た側面だけを書いておきますので正式な意味はヘルプ等で調べてください。
|
COMも仕様は異なるものの考え方としてはクラスと同じものですからメソッドがあってプロパティがあってイベントがあります。つまり、表面上は通常のクラスと変わらないわけで、その動作する基盤となっているテクノロジーが違うだけです。
VBではこの基盤となるテクノロジーの違いを巧妙に隠して、プログラマにCOMオブジェクトであるのか.NET Framework アセンブリであるのか意識させないような仕組みを用意しています。
VBとCOMとの間に仲介役となるラッパーを用意するという仕組みです。このようなラッパーは「相互運用機能アセンブリ」と呼ばれ自動的に生成されるのでプログラマは対象のオブジェクトが.NETのアセンブリなのかCOMなのか気にしなくても良いようになっているわけです。
これを実際に体験してもらうためにVBを使ってCOMオブジェクトを利用する簡単なプログラムを書いてみましょう。
COMオブジェクトの1つにShellオブジェクトというものが存在します。この ShellオブジェクトのToggleDesktopメソッドはすべてのウィンドウを最小化してくれる便利で簡単なメソッドです。 このメソッドをVBから呼び出してみましょう。
COMオブジェクトを使用するにははじめに参照設定を行う必要があります。
[プロジェクト]メニューの[参照設定]で、[COM]タグを選んで、一覧の中からMicrosoft Shell Controls And Automationを選んでOKを押してください。これだけでVBの内部では前述した相互運用機能アセンブリが自動的に生成されます。プログラマにとっては.NET Frameworkのアセンブリを参照設定する手順とまったく同じ手順でCOMを使用することができるのです。
■画像1:COMへの参照設定
プログラムは次の通りになります。
Dim o As
New Shell32.Shell o.ToggleDesktop() |
■リスト1
これだけ見てもShellクラスが.NET Framework アセンブリなのかCOMなのかわかりませんね。同じように扱えるのです。プログラム中のインテリセンスもまったく同様に機能します。
■画像2:COMオブジェクトでもインテリセンスは作動します。
実行するとすべてのウィンドウが最小化されます。なお、すべてのウィンドウが最小化されている状態ではすべてのウィンドウを標準の大きさに戻します。
さて、COMオブジェクトであるShellクラスの実体はシステムフォルダのShell32.dllです。先ほどから繰り返しているようにShell32.dllはCOM仕様に基づいているのでVBから直接参照することはできません。アプリケーションのビルド対象フォルダ、つまりexeがあるフォルダを開いてみるとInterop.Shell32.dllというファイルが生成されているのが確認できます。
このInterop.Shell32.dllこそが相互運用機能アセンブリの実体です。VBはこのDLLを参照しています。このDLLは.NET Framework仕様に基づいており、Shellオブジェクトが持っているすべてのメソッド、プロパティ、イベントと同じ名前、同じ引数、同じ戻り値、つまり同じシグネチャのメンバが定義されています。VBがこれらのメソッドを呼び出すとInterop.Shell32.dllは呼び出しをスルーしてShell32.dllに中継するわけです。そして、戻り値などもこの中継を利用して受け取ることになります。
要するに相互運用機能アセンブリはVBとCOMの間に入って相互の呼び出しを中継してくれているのです。
この仕組みのおかげでVBからは何も考えずにCOMが使用できるわけです。
今度はVB6から.NET Framework仕様のアセンブリにアクセスする方法を考えてみます。VB6ではVB6標準の実行可能ファイル(EXE)も作成できますし、COM仕様準拠のオブジェクトも簡単に生成できます。このどちらの場合でも同じようにして.NETのアセンブリを呼び出すことができます。
なお、以下ではVB6を前提に話を進めますが、VBAやVBScriptでも事情は同様ですのでこれから説明する内容が当てはまります。
さて、 VB6から.NETアセンブリを呼び出すためには.NETアセンブリを作成するときに仕掛けを作っておく必要があります。この仕掛けとはアセンブリをCOMとして レジストリに登録する仕掛けであり、これをしておかないとVB6から.NETアセンブリを呼び出すことはできません。
つまり、VB6からアクセスできる.NETアセンブリを用意する手順は、
1.VB.NET2002またはVB.NET2003またはVB2005を使用してCOM仕様準拠のDLLファイルを作成する。
2.そのDLLファイルをCOMとしてレジストリに登録する。
の2段階になります。
VB.NET2002、VB.NET2003、VB2005ではこの2つの段階を自動的に行ってくれます。VB2005 Express Editionでも可能です。これ以降の説明ではVB6と区別するためにこれらのバージョンのVBのことをまとめてVB.NETと表現します。
メモ - VB2005 Express
Editionを使用している場合 VB2005のExpress Editionを使用している場合は、標準ではCOM連携機能が組み込まれていないので別途ダウンロードして追加する必要があります。ダウンロードはフリーです。
直接のダウンロードはできず、まずはこのテーマの記事が記載されているMSDNマガジン 2006年5月号をダウンロードする必要があります。 https://docs.microsoft.com/ja-jp/previous-versions/ee310108(v=msdn.10)#2006 このファイルはchmという古いドキュメントの形式です。右クリックのプロパティから「ブロックを解除」をしないと内容が表示されません。 ブロックの解除をしたらダブルクリックすると内容が表示できます。 いくつか記事がある中の先頭の「Wrap It Up Call Into The .NET Framework From Existing Visual Basic 6.0 Apps」の記事の先頭の方に「Code download available at: VBFusion05.exe (150KB) 」というリンクがあり、VBFusion05.exeをダウンロードできます。 このexeは単なる圧縮ファイルで、解凍するとComClass.zipというファイルが入っています。このzipファイルを\My Documents\Visual Studio 2005\Templates\ItemTemplates\Visual Basicにコピーすれば準備完了です。 コピーするときはzipファイルのままコピーしてください。 なお、chmファイルの記事はまさにこの話題について扱っているMSDN Magazine英語版の記事です。この記事を読めば理解が深まるでしょう。 |
では、具体例の説明に移ります。今回はいろいろな特殊フォルダのパスを簡単に取得できるSystem.Environment.GetFolderPathメソッドをVB6で使用できるようにします。もうひとつ、単純に足し算を行うだけのシンプルなメソッドも作成してこれもVB6で使用できるようにします。
まず、クラスライブラリを選択して新規プロジェクトを作成します。名前は「COMTest1」にしてください。ここで指定する名前は重要ですが、どのような名前でも構いません。ただし説明の便宜のために同じ名前を付けることをお勧めします。
この手順までは普通の.NET用のクラスライブリを作成する手順と同じです。ここからが独特になります。
プロジェクトに新しい項目を追加します。ここで、「COMクラス」を選択してください。名前は既定値の「ComClass1.vb」をそのまま使用します。この名前も重要ですが本来は自由につけて構いません。VB2005 Express Editionを使用している場合は上記の囲み記事の説明に従ってあらかじめComClass.zipをコピーしておかないとここでCOMクラスが表示されません。
新しく追加されたComClass1.vbのプログラムは次のようになっています。
<ComClass(ComClass1.ClassId,
ComClass1.InterfaceId, ComClass1.EventsId)> _ Public Class ComClass1 #Region "COM GUID" ' これらの GUID は、このクラスおよびその COM インターフェイスの COM ID を ' 指定します。この値を変更すると、 ' 既存のクライアントはクラスにアクセスできなくなります。 Public Const ClassId As String = "d30b5622-15eb-4496-98b0-a5a7522101b3" Public Const InterfaceId As String = "bd081d5e-7dd5-4747-bcf0-370998ec4837" Public Const EventsId As String = "5c3b0d2f-c975-4552-8e57-54c189cdd4e8" #End Region ' 作成可能な COM クラスにはパラメータなしの Public Sub New() を指定しなければ ' なりません。これを行わないと、クラスは COM レジストリに登録されず、 ' CreateObject 経由で ' 作成できません。 Public Sub New() MyBase.New() End Sub End Class |
■リスト2:空のCOMクラス
これがVB.NETのプログラムがCOM仕様に準拠するための最小限のコードです。VB2005 Express Editionを使用している場合で、ComClass.zipをダウンロードして使用している場合はコメントは英語になっていますが機能は変わりません。また、途中に埋め込まれているClassId、InterfaceId、EventsIdの値はみなさんとは異なるはずです。これらの値はCOMの世界でオブジェクトを識別するために使用される一意の値でGUID(読み方:GUID = グイッド)と呼ばれています。GUIDはVB.NETによって自動生成されます。GUIDはどこかからコピーして貼り付けるようなことをしてはいけません。必ず世界中に1つしかないあなた専用の値を使用してください。つまりVBの自動生成に任せておけば確実で、自分でいじらないのが賢明です。
メモ - GUIDの手動生成 何かの都合で自分でGUIDを生成する必要がある場合は、Guidgen.exeを使用すると便利です。コンピュータの中を検索してみてください。VB.NETの上位エディションであれば[ツール]メニューの[GUIDの作成]から起動することもできます。VBで自分でプログラムしてGUIDを生成させることもできます。 |
ここに独自のメソッドを2つ追加します。次の通りです。
<ComClass(ComClass1.ClassId,
ComClass1.InterfaceId, ComClass1.EventsId)> _ Public Class ComClass1 #Region "COM GUID" ' これらの GUID は、このクラスおよびその COM インターフェイスの COM ID を ' 指定します。この値を変更すると、 ' 既存のクライアントはクラスにアクセスできなくなります。 Public Const ClassId As String = "d30b5622-15eb-4496-98b0-a5a7522101b3" Public Const InterfaceId As String = "bd081d5e-7dd5-4747-bcf0-370998ec4837" Public Const EventsId As String = "5c3b0d2f-c975-4552-8e57-54c189cdd4e8" #End Region ' 作成可能な COM クラスにはパラメータなしの Public Sub New() を指定しなければ ' なりません。これを行わないと、クラスは COM レジストリに登録されず、 ' CreateObject 経由で ' 作成できません。 Public Sub New() MyBase.New() End Sub Public Function GetFolderPath(ByVal folder As Environment.SpecialFolder) As String Return Environment.GetFolderPath(folder) End Function Public Function Add(ByVal X As Integer, ByVal Y As Integer) As Integer Return X + Y End Function End Class |
■リスト3:メソッドを2つ追加したCOMクラス
最後にビルドをして完了です。ビルドと同時にレジストリへの登録も行われます。開発環境ではレジストリにはビルド対象のパスが書き込まれますので、ビルドした後でDLLファイルを移動・削除などするとこのあとの手順が実行できなくなりますから注意してください。
何かの事情で自分でレジストリへの登録・削除を行いたい場合はregasmというツールを使って行います。たとえば、DLLの場所を移動したい場合はコマンドプロンプトでregasm /uに続けて現在のDLLをフルパスで指定します。そして、移動完了後にregasmに続けて新しいDLLのフルパスを指定すれば完了です。
通常のコマンドプロンプトではregasmとだけ打ってもパスが見つからないので、カレントディレクトリを移動させてから実行するか、.NET Framework SDKのSDKコマンドプロンプトを実行することになります。SDKコマンドプロンプトは最初から必要なパスが設定されるので楽です。regasm.exeはWindows\Microsoft.NET\Framework\v2.0.50727にあります。
新しい環境に手動でDLLを移行する場合も、DLLをコピーしてからregsvr32を実行します。VB6のディストリビューションウィザードを使用してsetup.exeを作成した場合はこの作業は自動化されます。こういったCOMに関する説明は今回のテーマではありませんので割愛しますが、実務で必要な方はざっと本や資料に目を通しておいた方がよいでしょう。
メモ - できそうでできないこと クラスを追加するときにCOMクラスを選択した場合は何の問題もありませんが、普通のクラスを使用して上のコードをコピー&貼り付けした場合はうまくいきません。ビルド自体は成功しますがCOMとして機能しないのです。 ですから、ここに書いてあるコードやあなたがどこかにメモしておいたコードを貼り付けるのではなく必ずCOMクラスのテンプレートを利用してクラスを作成するようにしましょう。 |
では、このアセンブリにVB6からアクセスしてみましょう。これはVB6からActiveXにアクセスする通常の手順と同じであり、特別に配慮することは何もありません。
まず、VB6で参照設定の一覧の中からCOMTest1を選択します。この一覧の中にCOMTest1が記載されているのは正常にレジストリに登録されている証拠です。また名前が「COMTest1」になっていることからわかるようにプロジェクトの名前がここに反映されます。ですからプロジェクトの名前が重要になってくるわけです。
■画像3:VB6から.NET Frameworkアセンブリへの参照設定
参照設定がすんだらボタンのClickイベントなどに次のように書いてテストしてみてください。
Dim Test
As New COMTest1.ComClass1 Dim Desktop As String Desktop = Test.GetFolderPath(SpecialFolder_DesktopDirectory) MsgBox Desktop |
■リスト4
プログラムの入力中にGetFolderPathメソッドの引数を入力して時点で次のようなメッセージが表示されます。
■画像4:なお、私はVista上でVB6を動かしています。
英語 The Library which contains this symbol is not referenced by the current project, so the symbol is undefined. Would you like to add a reference to the containing library now? 日本語訳 このシンボルを含むライブラリは現在のプロジェクトでは参照されていないため、 このシンボルは未定義状態です。 このシンボルを含むライブラリを今すぐ参照設定に追加しますか? |
日本語訳は私が作成しました。ひょっとすると環境によってははじめから日本語で表示される場合もあるかもしれません。そうすると私の訳とは違う日本語になっていると思いますのが気になさらないでください。
このメッセージの意味はGetFolderPathの引数の型であるSpecialFolder型が定義されていませんよという意味です。確かにVB.NETで作成したプログラムではSpecialFolder型を使用していますが定義はしていません。何しろSpecialFolder型はSystem名前空間であらかじめ定義されている列挙体ですから。
しかし、このメッセージに対して「はい」を選択すれば自動的にこのSpecialFolder型を定義しているDLLへの参照設定が作成されるので問題はありません。「はい」を選択した後に参照設定を確認してみるとmscorlib.dllが追加されているのがわかります。
これで実行すれば見事にデスクトップフォルダのパスが表示されます。
Addメソッドの例は省略します。試してみると.NET側ではIntegerだった引数がVB6側ではLongと表現されていることがわかります。これはVB.NET2002から型の定義が少し変更されて、VB6でLongと呼んでいたものがVB.NET2002以降ではIntegerと呼ばれているせいです。なお、VB6で言うIntegerはVB.NET2002以降ではShortになっています。
なお、VB.NETで作成したDLLを動作させるにはあくまでも.NET Frameworkが必要です。COM仕様に準拠しているからと言って.NET Frameworkがいらなくなるわけではありません。ですから、DLLだけコピーしてレジストリに登録しても.NET Frameworkがない環境では動作しません。
VB6には参照設定をしなくてもActiveXにアクセスできるCreateObjectという関数が用意されています。上述のDLLファイルもCreateObject関数を使ってアクセスすることができますが、少し首をひねってしまうような現象が発生します。
これはひょっとするとVB.NETのバグかなとも思うのですが、とにかく試してみましょう。
CreateObject関数を使ってCOMTest1のComClass1にアクセスするコードは本来は次のようになります。
Dim o
As Object Set o = CreateObject("COMTest1.ComClass1") MsgBox o.Add(2, 3) |
■リスト5
このコードが正常に作動する場合もありますが、VB2005のExpress EditonでCOMTest1.dllを作成した場合はエラーになります。「ActiveXコンポーネントはオブジェクトを作成できません。」と言われます。CreateObjectでこのエラーが出るときは"COMTest1.ComClass1"という文字列のどこかに誤字があるケースがほとんどです。しかし、今回は正確に書いており誤字はありません。ここが首をひねってしまうところです。
実は次のように記述すれば正常にアクセスできます。
Dim o
As Object Set o = CreateObject("COMTest.ComClass1") MsgBox o.Add(2, 3) |
■リスト6
なにが違うかよく見てみると「COMTest1」ではなく「COMTest」となっており、「1」が取れているのです。どうしてでしょうか。
繰り返しますが、この現象は私のパソコンでVB2005 Express Editionを使用した場合に発生することを確認しています。私のパソコンのOSはVistaで、VB2005にはサービスパックを適用していない状態です。
みなさんの環境でも発生するのでしょうか?あまり歓迎できない現象ですね。
念のためにCreateObjectに渡す文字列の設定を確認する方法を説明しておきましょう。基本的にはDLLを作成したときのプロジェクト名.クラス名となるのですが、今回のようにおかしなことになっている場合はレジストリに登録されている値を直接確認する必要があります。
レジストリエディタを起動してDLLを作成したときに定数ClassIdに割り当てられていた値(GUID)で検索してください。ヒットした場所を展開すると「ProgID」というキーがありここに書かれている文字列がCreateObjectに指定すべき文字列です。