.NET C# での ActiveX 用 DLL の作り方 ■ 環境変数 コンパイルと登録のコマンドを実行する時に必要な PATH の追加。 set Path=~~~;%Path%[Enter] 1つは、"%ProgramFiles%" の、Visual Studio のインストールされたフォルダの奥にある、 bin フォルダ。 sn(厳密名ツール) や gacutil(GACツール) などを含む。 VS2008(Express..)では少し違って、"%ProgramFiles%\Microsoft SDKs\..." にあった。 もう1つは、"%windir%\Microsoft.NET\Framework" の .NET のバージョン毎のフォルダで、 C# コンパイラや Regasm(アセンブリ登録ツール)などがある。 バージョンアップした場合は、前のバージョンのフォルダも加えないとならないようで ある。(例えば Regasm は、前のバージョンを使っていたりする。) VC++ の コマンド プロンプト(スタートメニューにある)は、最後に、 %VSxxCOMNTOOLS%\ (xxは数字)の vsvars32.bat を呼び出して環境変数を設定している。 VC++ に必要な環境変数は、PATH 以外に幾つかあり、コンパイラの場所も異なるが、 上2~3の場所も設定されるので、流用できる。 ■ コンパイル sn -k XXX.snk アセンブリに厳密名で署名する為のキーを生成、それを格納したファイルを作成する。 次のコンパイルで、/keyfile: オプションに指定する。 ソース中で、[assembly: AssemblyKeyFileAttribute("XXX.snk")] といった属性の指定は、出来なくなっているらしい。 ( Express Edition の IDE(統合開発環境) (vcsexpress.exe) を使ってのコンパイル (ビルド)は、/keyfile:XXX.snk の指定方法が判らない。この為、作成したアセンブリは、 GAC へインストールできない。) csc.exe /keyfile:XXX.snk /target:library /out:XXX.dll XXX.cs C# のコンパイル。(上で、/out:XXX.dll は省略可。) ( そこにある全ての cs を指定なら /out:XXX.dll *.cs .dll は、/out 省略時、最初のソースの名が使われるとある。) ( /target:~ は、/t:~ でも可。) ■ インストールと登録 次の2つをしないとならない。(順序は関係ないらしい。) gacutil /i XXX.dll グローバル アセンブリ キャッシュ(GAC)に、インストールする。 この キャッシュ フォルダ は、%windir%\assembly\ であり、 エクスプローラで開き、dll を、ドラッグ&ドロップしても、インストールできる。 regasm XXX.dll レジストリへの登録。(GAC にある事が前提で、DLLがそこにある事が登録されるのではない。) /tlb:XXX.tlb のオプションは、不要。これは、タイプ ライブラリを生成し、 それを登録する。 ソースを変更し、コンパイルし直したら、gacutil /i のみ行えば良いようである、 (タイプライブラリを登録した場合は違うだろうけれど。) ■ アンインストール(登録解除) gacutil /u XXX グローバル アセンブリ キャッシュ(GAC)から、削除する。 DLLファイルのパス・名前(XXX.dll)ではなく、登録された(コンポーネントの)名前を指定する。 regasm /u XXX.dll レジストリの登録を削除する。 /tlb:XXX.tlb オプションを使った場合は、regasm /u /tlb:XXX.tlb XXX.dll ■ ソース ▼ メソッドは、 -------------------------------------------------------------------- namespace XXX { public class Test { public string methodX (string str) { return str; } } } -------------------------------------------------------------------- これだけの記述で、コンパイルし、上の登録作業をすれば、public メンバーが 自動的に COM に公開されるらしく、こちらから、次のように呼び出す事が出来る。 ○ = new ActiveXObject("XXX.Test") 戻り値 = ○.methodX("~~~") クラスやメソッドの直前の [ ] 内の属性(Attribute)の記述は、特に必要ない。 ProgId は、"名前空間名.クラス名" ▼ プロパティは、 -------------------------------------------------------------------- int X = 0; public int propX { get { return X; } set { X = value;//value は、代入された値が入っている暗黙の // パラメータ変数(コンテキスト・キーワード)。 } } -------------------------------------------------------------------- これで、 値 = ○.propX; ○.propX = 値; と出来る。 ▼ イベント(コールバック)は、やや面倒だが、 -------------------------------------------------------------------- using System.Runtime.InteropServices;//[属性(~)]の指定に必要。 namespace XXX { [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface ITestEvt { [DispId(1)] void farFuncX(int num); // [DispId(数)] を付けないとだめらしく、実行時に、 // 「メソッドが、実装されてない。」というようなエラーが起きる。 } [ComSourceInterfaces(typeof(ITestEvt))] public class Test { public delegate void TDelegate1(int num);//デリゲート型の宣言 public event TDelegate1 farFuncX;//イベント(メソッド)用のポインタ変数?の宣言 public void evtTest(){ farFuncX( 123 ); } } } // [ComSourceInterfaces(インターフェイス型)] を、クラスに適用すると、 // イベント受信側(シンク)インターフェイスを、クラスに接続し、 // インターフェイスのメソッドを、クラスのイベントメンバに割り当てる、、 ------------------------------------------------------------------ これで、コントロール側から、使用側のメソッドを呼び出せる。 function ○::farFuncX( val ){ //向こうからの呼び出し } ( ○.evtTest() は、こちらから呼び出せる。) ▼ 上をまとめると、 ------------------------------------------------------------------ using System; using System.Runtime.InteropServices; namespace XXX { [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface ITestEvt {//イベント用の interface [DispId(1)] void farFuncX(int num); } [ComSourceInterfaces(typeof(ITestEvt))] public class Test { public delegate void TDelegate1(int num); public event TDelegate1 farFuncX; public void evtTest( num ){ farFuncX( num );//イベント(コールバック メソッド)の呼び出し } public string methodX (string str) {//メソッド return str; } int X = 0; public int propX {//プロパティ get { return X; } set { X = value; } } } } ------------------------------------------------------------------ ■ 続き ▼ 公開するメソッドとプロパティを明示的に指定するには、それを記述した interface を実装するようにし、また自動的に クラス インターフェイスを 生成しないように、ClassInterfaceAttribute(属性)を、この class に指定する。 ------------------------------------------------------------------ namespace XXX { public interface ITest { public string methodX (string str); public int propX { get; set; } } [ClassInterface(ClassInterfaceType.None)] public class Test : ITest { //~~~ } } ------------------------------------------------------------------ ▼ その他 ------------------------------------------------------------------ using System; using System.Runtime.InteropServices; namespace XXX { [Guid("~~~")] [ProgId("~~~")] public class Test { //.... ------------------------------------------------------------------ HTML の object タグの classid="clsid:~" に指定する CLSID を自動生成させず、 こちらで決めるには、class に、GuidAttribute を指定する。 ProgId (new ActiveXObject()の引数に指定する)を、明示的に指定するには、 同様に ProgIdAttribute を指定する。 Version の指定は、 [assembly: AssemblyVersion("1.0.0.0")] 指定無しは、0.0.0.0 ※ [assembly: ~] には、 using System.Reflection; が必要。 厳密名のキーファイルの指定の、 [assembly: AssemblyKeyFileAttribute("XXX.snk")] これは、コンパイルできない。コンパイルできるバージョンで作成した DLL も、 登録できず、使えないようである。 ■ イベントのこちら側の記述 function ○::イベント( ... ){ ... } これが、オブジェクトの取得 var ○ = new ActiveXObject("~"); と同じスコープの レベルにあると、取得より後に置かれていても、構文チェックで、 「 ○ は、宣言されていない」と、記述が認められない。 これを避けるには、関数の中に隠して置き、オブジェクトの取得後に、その関数を 実行する事で定義されるようにする。オブジェクトに属するので、関数の中に 書いても見えなくなる事はない。 object 要素で指定した場合は、上を、裸のまま置いても、コンポーネントが存在するなら、 エラーにはならない。 また、script 要素を、イベントハンドラにする事も出来る。 <script language="JScript" for="○" event="イベント" > //受信時の記述。引数は、arguments[0] ~ </script> ■ ActiveX コントロール (フォーム(GUI)を埋め込むもの) クライアント側の窓の中に、表示を埋め込むコントロール。 こちら(HTML)で、OBJECT 要素に、ClassID を指定して取り込むもの。 これは、class に Form ではなく、UserControl (System.Windows.Forms.UserControl)を 継承し、フォームの作成と同様にすると、可能になる。 コンパイル時に キーを指定し、DLL を GAC へ入れ、RegAsm すれば、 上と同様、public にしたものが、公開される。 Application.Run は不要。 ■ 独自の窓を使用するコントロール Window は、class に Form を継承し、Main メソッド(static)で、 class のインスタンス(Window)を作成し、Application.Run メソッドにそれを渡す、 といったソースを、/target:winexe でコンパイルし、出来た .exe を実行すると、 表示される。 それを、GAC へインストール(但しコンパイル時にキーも必要)し、レジストリに登録し、 こちらから呼び出してみると、Main メソッドは呼び出されないので、窓は 表示されないが、class のインスタンスは、自動的に作成されている。 であるので (new の必要はなく)、Application.Run(this) を実行するメソッドを作って、 こちらから呼び出すようにすると、Window が表示されるが、この呼び出しは、 窓を閉じるまで戻ってこない。これをスレッドで解決しようとすると、実行時に 作業の内容によっては、「Single Thread Apartment (STA) モードでなければならない」 というエラーが起きる。( 解決法が判らない。) /t:library 、/t:winexe どちらでコンパイルしても、こちらから呼び出す分には、 変わらないように見える。