0. このドキュメントについて
この資料では、Visual Basic
6.0から.NETで作成したクラスをCOMとして利用する為の方法について説明する。.NET側の言語はC#を用いるが、他の言語にも応用可能である。
同様の情報が「.NET COM
相互運用」のようなキーワードでネットやヘルプ等の様々な場所に点在しているが、VBから.NETを利用するという観点で通して説明した資料が見つからなかった為、自分なりの解釈でそれらをまとめたものである。
解釈が間違えている箇所もあると思われるので、お気づきの点は shinde@sev.or.jp
までご連絡頂ければ幸いである。内容については各自の責任で利用されたい。
1. 最も簡単にVB6から.NETコンポーネントを利用する方法
(1) .NETのDLLを作成する。
例) DotNetCOMTest.dll |
DotNetCOMTestClass void TestA() - メッセージボックスを表示する。 |
このDLLはVB6.exeと同じディレクトリに置いておく。
(2) COMとして利用できるように、レジストリに登録する。
VB6から利用する為に、.NETのDLLをCOMとして利用できるようにレジストリに登録する。
これをやってくれるのがregasmコマンドである。
% regasm DotNetCOMTest.dll |
(3) tlbexp コマンドで、タイプライブラリ(*.tlb)を作成する。
% tlbexp DotNetCOMTest.dll /out:DotNetCOMTest.tlb |
(4) VB6のプロジェクトから参照設定する。
VB6の「参照設定」からDotNetCOMTest.tlb を読み込む。
これでVBの統合開発環境上で各クラスを呼び出す準備が整った事になる。
(5) VB6から.Netクラスを生成し、呼び出す。
VB6のフォームにボタンを貼り付け、ボタンのClickイベントに次のようなコード
を書く。
尚、クラス名以外のコード補完機能は働かない為注意が必要だ(コード補完機能を使えるようにするには4.を参照)。
VBソースコード(Form1) |
Private Sub
Command1_Click() On Error GoTo Err_Proc Dim obj As DotNetCOMTest.DotNetCOMTestClass ' (a) Set obj = New DotNetCOMTest.DotNetCOMTestClass ' (b) Call obj.TestA ' (c) Exit Sub Err_Proc: MsgBox Err.Description End Sub |
(a)
DotNetCOMTestパッケージの、DotNetCOMTestClassの変数objを宣言する。
(b) DotNetCOMTestClassを生成し、objに代入する。
(c) objのTestAメソッドを呼び出す。
(6) プロジェクトをコンパイルし、exeを作成する。
VB6上から直接実行するとエラーになるのは、VB6から実行する際にはカレントフォルダがVB6.exeのフォルダになっており、DLLをVB6が見つけられない為である。
対策は、DLL(ここではDotNetCOMTest.dll)をvb6.exeと同じフォルダにコピーするだけでよい。
※DotNetCOMTest.dllが参照している他のDLLがあればそれもコピーする。
このやり方でも特に問題はないが、このDLLが他のアプリケーションからも使われる「共有DLL」である場合は3.の対処を行う必要がある。3.の対処を行う場合は2.の対処は不要である。
コラム:共有DLLは必要か? |
「3.DLLの共有方法」で説明しているDLLの共有方法は、昔ながらのCOMの共有方法に近い。 しかし、本来.NETの思想ではDLLはそれぞれのアプリケーションにローカルにすべきであり、共有を避けることによって「DLL地獄」に陥らないようにしたり、「XCOPYインストール」を実現したりするのが本筋であろう。 「3.DLLの共有方法」で確かに共有は実現されるが、代償としてそのアプリケーションのインストール時にGACにアクセスしなければならなくなる。GACにアクセスするためのツールであるgacutil.exeは.NET SDKをインストールしなければ存在しない為、VBアプリケーションの配布が非常に面倒になる。 では一般に.NET DLLを利用するVBアプリケーションを配布するにはどうすればいいのかというと、このドキュメントの1.と2.で説明した通り、配布したいVB用アプリケーションのexeと同じフォルダに.NET DLLを配置してregasm.exeを実行すればいいだけである。regasm.exeは.NETランタイムをインストールすれば一緒にインストールされる。 複数のアプリケーションから共有したい場合は、それぞれのアプリケーションフォルダにDLLをコピーすれば良い。一箇所にDLLをまとめたいのであれば、そのVBアプリケーション用の「アプリケーション構成ファイル」を作成してcodebase属性でDLLの位置を指定すれば良い。 「3.DLLの共有方法」は、最後の手段と考えておくべきである。 ※参考:<codebase>属性 http://www.microsoft.com/japan/msdn/library/default.asp? url=/japan/msdn/library/ja/cpgenref/html/gngrfcodebase.asp ※アプリケーション構成ファイルについて ちなみに、アプリケーション構成ファイルは「exe名.config」という名前で作成すればOKだが、これは.NETアセンブリを利用するVBアプリケーションにもそのまま当てはまる。 つまり、「MyVBApp.exe」というVBアプリなら、「MyVBApp.exe.config」という名前で作成すれば良い。codebase属性などについては上記URLを参照のこと。 ※DLLの配布の際は、4.1の問題点についても対処しておくこと。 |
COMの場合、system32フォルダに置いてregsvr32を実行することでDLLを共有できた。
.NETにも共有の仕組みが用意されている。
DLLを共有化すると、どのアプリケーションからでもそのDLLを利用することができる。但し、どうしても共有しなければならない理由がない限り、DLLは各アプリケーションにローカルにしておいた方が良い。
共有化するためにはグローバルアセンブリキャッシュ(GAC)にDLLを登録すれば良い。
しかし、GACに登録する為にはDLLに対して先に「署名」を行うことにより厳密名をつける必要がある。
署名を行う為には「キー・ペア」と呼ばれるものが必要になる。
ここでは開発時のキー・ペアの運用を考慮して、遅延署名と呼ばれるテクニックを用いた手順を詳しく解説する。
この方法はDLLに対して厳密名を付ける方法としても参考にして良い。
尚、既にDLLが完成していて後は共有するだけ、という場合は以下の項目だけを実行すれば良い。
3.1(1) キー・ペアを作成する。
3.2(2) 公開キーを使って遅延署名する。
(※3.1(1)のキー・ペアを使って正式署名をすれば良い)
3.3(2) GACに登録する。
3.4(1) system32フォルダにDLLをコピーする。
3.1 会社固有のキー・ペアを作成する。
3.2 DLLに遅延署名する。
(1) キー・ペアから公開キーを取り出す。
% sn.exe -p companyname.snk companyname_public.snk |
この結果、companyname_public.snkが作成される。このキーは公開用のキーなので誰に配っても良い。
この後、この公開キーを使ってDLLに「遅延署名」を行う。
コラム: 「キー・ペア」とは何か? |
「キー・ペア」とは暗号化方式の一つである「公開鍵暗号化方式」の用語である。 よく知られる「秘密鍵暗号化方式」では一つの暗号化キーを使って暗号化と復号を行うが、公開鍵暗号化方式では「個人鍵」と「公開鍵」という二つの暗号化キーのペアを用いる。 これがキー・ペアと呼ばれる由縁である。 個人鍵を使って暗号化された文書は、対となる公開鍵でしか復号できない。また、公開鍵を使って暗号化された文書は対となる個人鍵でなければ復号できない。 例えばA氏が自分の個人鍵を使って暗号化した文書は、A氏が公開している公開鍵を使ってしか復号できない為、その文書がA氏のものだという証拠となる(もちろん個人鍵は厳重に管理されているという前提がある)。 逆にA氏に秘密の文書を送りたい場合は、送信者がA氏の公開鍵を使って暗号化し、A氏に送れば良い。A氏は自分の個人鍵を使って復号することができるが、他の人がもしこの文書を入手しても復号することはできないのである。 これを署名検証に応用する場合、目的は「対象のアセンブリが正式に署名されたものかを確認する」ことである。その為には、アセンブリ自身に、アセンブリのハッシュをアセンブリの個人鍵で暗号化したものと公開鍵を持たせておけば良い。検証する側は、自分が持っている公開鍵を使ってその暗号文を復号し、それがアセンブリのハッシュと一致すれば、身元が正しい事を証明できる。もちろんこれは、検証する側がそのアセンブリの正しい公開鍵を事前に知っている場合に限られる。(実際にはもっと複雑な手順を踏んでいると思われる)。 |
(2) 公開キーを使って遅延署名を行う。
AssembyInfo.cs(一部) |
[assembly: AssemblyDelaySign(true)] // falseにすると正式に署名される。 [assembly: AssemblyKeyFile(@"..\..\..\companyname_public.snk")] |
これでコンパイルすれば、DLLに遅延署名がされる。
尚、AssemblyKeyFile属性はDLLの出力先からの相対パスとなるので、各環境に合わせて変更しなければならない。
また、署名された(厳密名を持った)DLLは厳密名を持ったDLLしか参照できない為、必要ならばそれらも署名する必要がある。
3.3 グローバルアセンブリキャッシュ(GAC)に登録する。
(1) 署名検証機能をオフにする。
% sn.exe -Vr DotNetCOMTest.dll |
3.4 VB6から使用できるようにする。
3.5 署名を行う。
(1) 遅延署名DLLへ署名する。
% sn.exe -Rc DotNetCOMTest.dll companyname.snk |
(2) 署名検証機能をオンにする。
% sn.exe -Vu DotNetCOMTest.dll |
4.1 最新のタイプライブラリを使って再コンパイルすると、実行時エラー429が発生する。
DLL側の開発中は、頻繁にDLLそのものやタイプライブラリが更新されることになる。
ところが更新されたタイプライブラリをVBに読み込んで再コンパイルしたexeを実行すると、実行時エラー429が発生する場合がある。
regasmを使ってDLLを再登録するとこのエラーは起こらなくなる。しかし、DLLが更新されるたびにregasmを行っていたのでは非常に面倒であるし、配布もしづらい。
これは以下のような理由から起こる。
タイプライブラリを生成すると、クラスの固有キーがCLSIDとして生成され、タイプライブラリに埋め込まれる。既定ではこのCLSIDは毎回違うものになる為、タイプライブラリに埋め込まれるCLSIDも毎回異なることになる。
regasmによってレジストリに登録される.NETクラスのキーはこのCLSIDであり、exeはこのCLSIDを用いて.NETクラスを探しに行く。最新のタイプライブラリ(つまり新しいCLSID)を使ってコンパイルされたexeがいくらレジストリを探しても、まだregasmされていない.NETクラスは見つけられないのである。
これを回避する為にはクラスやインターフェイスに対してGUID属性を指定すれば良い。
[GuidAttribute("25731EF3-88ED-4322-9A90-E665598A4889") ] public class Person : IPerson { |
4.2 VB6のコード補完が使えない。
そのままの設定だと.NETのクラスに対してはVB6のコード補完が働かない。これについては以下のように対処可能であるが、いくつか問題もある。
(1) ClassInterface属性にClassInterfaceType.AudoDualを設定する。
[ClassInterfaceAttribute(ClassInterfaceType.AutoDual)] public class Person |
この属性値を指定すると、そのクラスのメンバが全てVB6のコード補完から使えるようになる。
具体的にはAutoDualは全てのメンバのインターフェイスをタイプライブラリに記述する、つまり事前バインディングを可能にする指定である。
しかしこれには重大な欠点がある。そのクラスのメンバのレイアウト(メンバの順番など)を変更すると、事前バインディングのインターフェイスも変わってしまうのだ。例えばクラスのメンバAとBの間にメンバXを加えたら、それまで動いていたアプリケーションが全て動かなくなってしまう。メンバAとBの順番を変えただけでも同様の結果になる。毎回全てのアプリケーションを再コンパイルするのは、開発時は問題ないとしても、一旦リリースしてしまった場合などは困難である。
開発時のみの属性値だと思った方が良さそうである。(MSも、この属性値は「推奨しない」と言っている)
(2) ClassInterface属性にClassInterfaceType.AutoDispachを設定する。
[ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)] public class Person |
この属性値を指定してもコード補完は働かない。それもそのはず、この属性値は既定値だからである。何も指定しなければ自動的にAutoDispatchとなる。
具体的にはAutoDispatchは全てのメンバのインターフェイスをタイプライブラリに「記述しない」、つまり事前バインディングをできないようにする指定である。逆に言えば、遅延バインディング(実行時バインディング)に限定することになる。
こうする事には理由がある。(1)の説明を読めばお分かりと思うが、事前バインディングを用いるとクラスのレイアウト変更に非常に弱くなってしまうからである。事前バインディングでなく遅延バインディングを用いることで、クラスのレイアウト変更とは完全に切り離すことができる。
(3) ClassInterface属性にClassInterfaceType.Noneを設定する。
[ClassInterfaceAttribute(ClassInterfaceType.None)] public class Person : IPerson |
コード補完は使いたい、でもAutoDualは問題がある・・・このちょうど中間がNoneである。
Noneは、そのクラスが実装しているインターフェイス(ここではIPerson)のみを事前バインディング可能にする指定である。これだけならAutoDualと同じ問題が起きそうだが、Noneの良いところはクラス側のレイアウト変更が外部に影響を及ぼさない点である。IPersonのレイアウトのみが外部に影響する。
よって、Noneを用いるのは「COM公開用のインターフェイスが決まっており、今後も変更しない事が決まっている場合」
に限られる。これはCOMの思想と合致しており、あくまでもCOMとして利用するためのDLLであればこれで問題ないと思われる。
4.3 イベントをVB6から利用できない。
.NETのクラスでイベントを発生させ、VBのクライアントからそれを受け取る・・・いかにも楽しそうなこのテクニックが、単に.NETのクラスにeventを定義しただけでは行えない。
VB側でWithEventsステートメントを使っても、そもそもコード補完のリストに該当のクラスが現れないのである。
イベントの利用はなぜか面倒な手続きが必要となっている。
まず、そのイベントと同じ引数と名前のメソッドを持つインターフェイス(ここではPersonEvents)を定義し、以下のような属性を指定する。
public delegate void PersonEventHandler( string
name ); [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] public interface PersonEvents { void NameChanged( string name ); } public class Person { public event PersonEventHandler NameChanged; |
次に、ComSourceInterfaces属性を使ってクラス側にそのインターフェイス名を指定する。
public delegate void PersonEventHandler( string
name ); [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] public interface PersonEvents { void NameChanged( string name ); } [ComSourceInterfaces("DotNetCOMTest.PersonEvents, DotNetCOMTest")] public class Person { public event PersonEventHandler NameChanged; |
ComSourceIntarfaces属性値の引数は、最初がイベント用に定義したインターフェイス名、二番目がそのインターフェイスが存在するアセンブリ名である。
これでVB6からイベントを利用できるようになる。
Microsoft .NET Framework SDK クイックスタートチュートリアル
http://ja.gotdotnet.com/quickstart/default.aspx
解説 インサイド .NET Framework (@IT記事)
http://www.atmarkit.co.jp/fdotnet/technology/index/index.html
高度なCOM相互運用機能 - クラス インターフェイスの概要
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/
cpguide/html/cpconintroducingclassinterface.asp
高度なCOM相互運用機能 - COMシンクによって処理されるイベントの発生
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/
cpguide/html/cpconraisingeventshandledbycomsink.asp
著者: 株式会社アイジュピタ 新出 純壱( shinde@sev.or.jp )