.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 どちらでコンパイルしても、こちらから呼び出す分には、
変わらないように見える。