アンマネージ環境での.NET:アンマネージからのマネージコードの呼び出し

おそらく前回の記事で覚えているように、アンマネージコードとマネージコードの相互作用には、経験豊富な開発者であっても特定の問題があります。 この理由は、データがCLRの境界を越えるときに発生するプロセスを理解する必要があるためです。



残念ながら、多くの場合、相互運用性を確立する問題は、COMテクノロジのインとアウト、および相互運用性の.NET機能にあまり精通していない開発者に発生します。 これは正常です-世界のすべてを知ることはできません。 したがって、非管理データから管理データへ、またはその逆へのデータのマーシャリングの問題の全体的なポイントについては説明しませんが、早急に明日必要なときに役立ついくつかの作業レシピを簡単に紹介し、Inside OLEの英語版を見て理解します1日でこれを把握する方法はありません。



ただし、これが得意な人には、記事の最後に小さなボーナスがあります。これは、.NETでアウトプロセスCOMを整理する方法です。 正直なところ、.NETを使用してアウトプロセスCOMを作成することは不可能であると正直に信じていましたが、昨日は不可能であることが判明しました。 この点に関して、.NET Pipe RPCアーキテクチャについてはほとんど説明しません。非常に複雑ですが、アウトプロセスCOMは、提供するすべての機能を簡単に置き換えることができます。



一般に、Microsoftが提供する2つのメカニズム、dllエクスポートとCOMエクスポートがあります。 はい、まさにそうです-マネージコード関数のエクスポートの可能性を知っている人はほとんどいないにもかかわらず、それはまだ存在しています。 ただし、それについては説明しません。 実際、関数のエクスポートは.NETによって高レベルでサポートされていません。つまり、関数を単純に記述し、[DllExport]などの属性を掛けて生活を楽しむことはできません。ILの荒野登るか、 サードパーティのソリューションを使用する必要があります どちらの決定も満足できるものとは言えないため、これについて詳しくは説明しません。このメカニズムには実用的な利点はまったくありません。



COMエクスポートメカニズムは、COMを理解している人にはその作業が十分に透過的であるという点で優れていますが、.NET開発者にとってこれは暗い森です。 つまり、このメカニズムを使用して作業を成功させるには、COMテクノロジーとは何か、呼び出しの方法、パラメーターの転送方法などを理解する必要があります。 しかし、誰もが知識を持っているわけではないため、共通のレシピがあります。 C#でinproc-COMサーバーを作成し、VBAなどからアクセスしてみましょう。 これを行うには、Excelとその組み込みのスクリプト言語を使用します。



なぜExcelを選んだのですか? 経験が示すように、VBAはクライアントによるメモリリークに関してかなり気まぐれです-わずかなリークの場合、Excelはプロシージャの完了直後、またはdllのアンロード(つまり、プロシージャの完了後約1分)のいずれかでクラッシュします。 したがって、Excelのボラティリティは、間違ったことをしていることを理解するのに役立ちます。



始めましょう。 新しいアセンブリを作成し、通常のTestInprocCOMという名前を付けて、そこにCoClassを1つ作成します。これにより、VBAからプルするインターフェイスが実装されます。



しかし、まず最初に、インタラクションから何が必要かを決めましょう。 経験が示すように、ほとんどの場合、転送する必要があります

  1. ひも
  2. 配列
  3. 構造
  4. 他のオブジェクト




今日の私たちの仕事の冠は、線が存在する一連の構造の移転です。 実際のアプリケーションの99%にはこれで十分です。 残りの1%については、この情報はHabrの幅広い読者にとって具体的すぎるため、これに焦点を合わせません。



導入が長すぎることをおpoびします。 また、一見しただけで些細なことの多くの詳細な説明についても謝罪する必要があります。この記事を読む人々のレベルを知りませんでした。



それでは始めましょう。



インプロセスCOMの実装。





まず、呼び出しを担当するインターフェイスを定義します。 この行またはその行の目的についてのコメントは、既にかなり大きなサイズの記事が増えないように、コード内で正しく行います。



// , COM

[ComVisible( true )]

// . ,

// VBA, . ,

// C++

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

// GUID . - , .

[ Guid ( "5AC0488E-9CAC-4032-B59A-37B8B277C4EF" )]

public interface ITestInterface

{

// COM- , .

// , .NET

// BSTR.

void Hello([MarshalAs(UnmanagedType.BStr)] ref string name);



// , .

[ return : MarshalAs(UnmanagedType.BStr)]

string Say();

}




* This source code was highlighted with Source Code Highlighter .








ご覧のとおり、各パラメーターにはMarshalAs属性があります。 CLRの境界を越えるときにパラメーターをマーシャリングする方法を言うのは彼です。 デフォルトのマーシャリングが必要な動作と一致する場合、設定する必要はありません。 ただし、文字列はデフォルトでLPTStrとしてマーシャリングされるため、文字列については、BSTRとしてのマーシャリングを常に指定する必要があります。



// CoClass

[ComVisible( true )]

[ Guid ( "B06CC21C-BCBB-4dde-8ED3-BFBD9A31AD6E" )]

// ProgId , , GUID

[ProgId( "TestInprocCOM.TestCoClass" )]

// , CoClass- .

// , , COM

// - ,

// , .

[ClassInterface(ClassInterfaceType.None)]

// default interface. VBA, ++ .

[ComDefaultInterface( typeof (ITestInterface))]

public class TestCoClass : ITestInterface

{

public void Hello( ref string name)

{

name = "Hello, " + name;

}



public string Say()

{

return "OK" ;

}



}




* This source code was highlighted with Source Code Highlighter .








オブジェクトが作成されました。 コンパイルしてから、 regasm /tlb:testinproccom.tlb testinproccom.dll /codebase.



を使用してシステムに登録しregasm /tlb:testinproccom.tlb testinproccom.dll /codebase.







登録済みのオブジェクトがあり、そのためのタイプライブラリ(tlbファイル)があり、クライアントを続行できます。 これを行うには、Excelを開き、Visual Basicエディターに移動し、[ツール]-> [参照設定]でタイプライブラリを接続し、次のように記述します。



Dim a As TestCoClass

Dim s As String



Sub main()

Set a = New TestCoClass

s = "mike"

a.Hello s

MsgBox s

msgbox a.Say()

End Sub




* This source code was highlighted with Source Code Highlighter .








最初の「Hello、mike」と2番目の「OK」という2つのメッセージボックスが表示されます。 おめでとうございます、私たちはすべてを正しく行いました。



ただし、1行だけでうんざりすることはありません。 構造を伝えてみましょう。 このために定義します。



[ComVisible( true )]

// , ,

// unmanaged.

[StructLayout(LayoutKind.Sequential)]

[ Guid ( "E1AB60D5-F8F5-41fe-BD0D-AE2AC94237DD" )]

public struct MyStruct

{

//

[MarshalAs(UnmanagedType.BStr)]

public string wow;

// . SafeArray,

// , VBA JavaScript. , C++ SafeArray

// , . -

// .

[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)]

public int [] gig;

// IDispatch. .NET

// object.

[MarshalAs(UnmanagedType.IDispatch)]

public object self;

}




* This source code was highlighted with Source Code Highlighter .








次に、インターフェースを展開します。 そこにもう1つの関数を追加しましょう。



// out ref -

// .

void GetStruct([MarshalAs(UnmanagedType.Struct)] ref MyStruct ms);




* This source code was highlighted with Source Code Highlighter .








そして、それをCoClassに実装します。



public void GetStruct( ref MyStruct ms)

{

ms.gig = new int [] { 1, 2, 3, 4, 5 };

ms.self = this ;

ms.wow = "wow" ;

}




* This source code was highlighted with Source Code Highlighter .








ご覧のとおり、thisパラメーターをselfに配置します。つまり、VBAでこの関数を呼び出した後、このフィールドにオブジェクトへのリンクがあります。 オブジェクトの操作性を確認します-再コンパイル、再登録、Excelを再度開き、次のコードを記述します。



Dim a As TestCoClass

Dim s As String

Dim b As MyStruct



Sub main()

Set a = New TestCoClass

s = "mike"

a.Hello s

MsgBox s

b.wow = "baaa"

a.GetStruct b

MsgBox b.gig(3)

MsgBox b.self.Say()

MsgBox b.wow

Set a = Nothing

End Sub




* This source code was highlighted with Source Code Highlighter .








すべてがうまくいけば、次のメッセージボックスがあります

  1. こんにちはマイク
  2. 4
  3. わかった
  4. うわー




b.self.Say()



注意してb.self.Say()



。 遅延バインディングを使用して、オブジェクトのSay関数を呼び出します。 このメカニズムは、インターフェイスをデュアルに設定する(つまり、vtableとIDispatchを同時に使用できる)ために提供されます。 したがって、インターフェイスを常にデュアルに設定することをお勧めします-これには作業は不要ですが、多くの利点があります。



ところで、すべてが正しく機能しているという間接的な証拠は、VBA環境のヒントです。



そして最後に、クラウン番号。 文字列を含む構造体の配列。 構造を定義します。



[ComVisible( true )]

[StructLayout(LayoutKind.Sequential)]

[ Guid ( "1D40391F-C9CF-42ed-9C9E-4991B3F22907" )]

public struct MyStruct2

{

[MarshalAs(UnmanagedType.BStr)]

public string param;



public MyStruct2( string par)

: this ()

{

param = par;

}

}




* This source code was highlighted with Source Code Highlighter .








インターフェイスで関数を定義する



// safearray custom- - .

void SetMyStruct2([MarshalAs(UnmanagedType.SafeArray, SafeArrayUserDefinedSubType = typeof (MyStruct2))] out MyStruct2[] structs);




* This source code was highlighted with Source Code Highlighter .








ただ? そして、誰もそれが難しいとは言いませんでした。 マーシャルを設定するときに必要なのは、クライアントがパラメーターをどのように処理するかを想像することだけです。



ここで、関数をCoClassに実装します。



public void SetMyStruct2( out MyStruct2[] structs)

{

structs = new MyStruct2[] { new MyStruct2( "bla1" ), new MyStruct2( "bla2" ), new MyStruct2( "bla3" ) };

}



* This source code was highlighted with Source Code Highlighter .








コンパイル、登録、および-すべてが順調に進んだことを示すテストケース。



Dim a As TestCoClass

Dim sa() As MyStruct2



Sub main()

Set a = New TestCoClass



a.SetMyStruct2 sa

For i = 0 To 2

MsgBox sa(i).param

Next i



Set a = Nothing

End Sub




* This source code was highlighted with Source Code Highlighter .








そして、すべてがうまくいった場合、次のメッセージを受け取るはずです。



  1. bla1
  2. bla2
  3. bla3




私たちに万歳!



Outproc COM。





ただし、インプロセスCOMアーキテクチャは、必ずしも私たちに適しているわけではありません-たとえば、シングルトンを作成する場合。 dllはプロセスのメモリフィールドに投影されるため、異なるプロセスはクラスの同じインスタンスを生成できます。このインスタンスは各プロセスに固有であり、他のインスタンスについては何も知りません。 これは常に必要なものではありません。



COM仕様によると、outprocサーバーの実装は次のとおりです。 クライアントが適切なパラメーターを使用してCoGetClassObject



を呼び出すと、COMはまず内部クラスファクトリテーブルを調べ、クライアントが指定したCLSIDを探します。 クラスファクトリがテーブルにない場合、COMはレジストリにアクセスし、対応するEXEモジュールを起動します。 後者のタスクは、できるだけ早くクラスファクトリを登録して、COMがそれらを見つけられるようにすることです。 EXEクラスのファクトリを登録するには、COM関数CoRegisterClassObject.





使用しCoRegisterClassObject.





CoRegisterClassObject.







したがって、私たちのタスクはこれを行うことです。 これを行うには、新しいプロジェクト(コンソールEXEアプリケーション)を作成し、そこにインターフェイス、構造、およびクラスのコードを転送します。 次に、Main関数で必要な登録コードを定義します。



COM仕様によると、outprocサーバーは/ registerスイッチを使用した自己登録をサポートする必要があります。 これは次のように行われます。



static private void RegisterManagedType(Type type)

{

String strClsId = "{" + Marshal.GenerateGuidForType(type).ToString().ToUpper(CultureInfo.InvariantCulture) + "}" ;



// Create the HKEY_CLASS_ROOT\CLSID key.

using (RegistryKey ClsIdRootKey = Registry.ClassesRoot.CreateSubKey( "CLSID" ))

{

// Create the HKEY_CLASS_ROOT\CLSID\<CLSID> key.

using (RegistryKey ClsIdKey = ClsIdRootKey.CreateSubKey(strClsId))

{

ClsIdKey.SetValue( "" , type.FullName);



// Create the HKEY_CLASS_ROOT\CLSID\<CLSID>\LocalServer32 key.

using (RegistryKey LocalServerKey = ClsIdKey.CreateSubKey( "LocalServer32" ))

{

LocalServerKey.SetValue( "" , ( new Uri ( Assembly .GetExecutingAssembly().CodeBase)).LocalPath);

}

}

}

}




* This source code was highlighted with Source Code Highlighter .








そして最後に、メイン関数。



[MTAThread]

static void Main( string [] args)

{



if ((args.Length == 1) && (args[0] == "register" ))

{

RegisterManagedType( typeof (TestCoClass));



Console .WriteLine( "Server registered, press any key to exit" );



Console .ReadKey();



return ;

}



// ?



}




* This source code was highlighted with Source Code Highlighter .








そして今、問題がありますCoRegisterClassObject



を呼び出してクラスファクトリを登録します。 簡単な解決策は、この機能にプラットフォーム呼び出しを使用して、人生を楽しむことです。 ただし、何もうまくいかない-Microfostは、クラスファクトリ登録関数に対して明示的にp / invokeを禁止します。



ソリューションは、RSDNフォーラムから突然提供されました。 .NETには、この機能に類似したクラスがあり、この目的のために特別に機能し、 RegistrationServices



と呼ばれています。 CoRegisterClassObject.



メソッドから必要なものとまったく同じことを行うRegisterTypeForComClients



メソッドがありCoRegisterClassObject.







RegistrationServices rs = new RegistrationServices();



rs.RegisterTypeForComClients(

typeof (TestCoClass),

RegistrationClassContext.RemoteServer | RegistrationClassContext.LocalServer | RegistrationClassContext.InProcessServer,

RegistrationConnectionType.MultipleUse);



Console .WriteLine( "Server started, press any key to exit" );



Console .ReadKey();




* This source code was highlighted with Source Code Highlighter .








コンパイル、登録、テストを行い、最初に登録インプロセスCOM dllを削除することを忘れないでください。 私たちに万歳!






All Articles