他言語との連携(ネイティブから動的言語まで)
TRANSCRIPT
他言語との連携ネイティブから動的な言語まで。
(別言語からC#にWellcom)
C#の素晴らしさを語る会
自己紹介
名前石川達也
所属株式会社Codeer代表取締役(http://www.codeer.co.jp/)
技術C、C++、C++/CLI、C#、少しJava組み込みとか、Windowsアプリとか。
趣味ギター
まだまだ、ネイティブアプリは世の中にあふれている!
まずは、ネイティブとの連携
Win32アプリのメンテをしています。
そして、ある日上司から唐突に作業依頼がやってきました。
ストーリー
次の仕様追加で作る画面、WPFで作ってくれない?流行ってんでしょ?
流行ってるか・・・?
(でたよ、思い付き・・・)
でも、あなたは、答えました。
「Yes, sir」なぜなら、WPFで作るということは・・・。
大好きなC#を仕事で使えるから!
備考
でも、この思い付きは意外と良いんです。
Nativeアプリを一気に.Netで作り変えるのは大変!
その過程で、チームに.Netのノウハウが溜まるのです。
方法は複数あり、それぞれ長所短所がある。
・ネイティブをC++/CLIにする
・C++/CLIでブリッジをつくる
・COM相互運用
・P/Invoke
・ホストAPI
私はこれをよく使ってます
今回はC#とC++のみで、やってみます。
・ネイティブをC++/CLIにする
・C++/CLIでブリッジをつくる
・COM相互運用
・P/Invoke
・ホストAPI
割愛
レシピはこんなもんかな・・・
8Hほど煮込みました。
あれ?
これ、いつもやってるやつより良くね? (゚д゚ノ)ノ
なんで、今までC++/CLI使ってたの? (T-T)
ていうか、やっぱり・・・・。
C#って素晴らしい!
では、コードをどうぞ~。
弊社サイトからDwonLoad可能です。
Codeerで検索→技術メモ→ネイティブと.Netの連携http://www.codeer.co.jp/technical-notes/NativeAndNet
ManagedExporter.dll
IInterfaceTable
ManagedImporter
ObjectProxy
WpfGui.dll Win32App.exe
InputData : IInterfaceTable
InputDataDialog : IInterfaceTable
InputData : ObjectProxy
InputDataDialog : ObjectProxy
★最初にObjectProxy::Initを一回呼びます。
概略図
まとめると、
Delegateを関数ポインタにしてネイティブから使おう!
って作戦です。
ManagedExporter.dll
ManagedImporter
//関数ポインタをstaticに保持ExportInfo s_Create(path, assembly, typeFullName); void s_Free(handle);
Win32App.exe
ObjectProxy::Init(L"v4.0.30319");
ホストAPIで.Net呼び出し。関数ポインタちょうだい。
・生成関数(delegate)・解放関数(delegate)
//COMインターフェイス取得CLRCreateInstance(..., IID_PPV_ARGS(&pMetaHost));
pMetaHost->GetRuntime(..., IID_PPV_ARGS(&pRuntimeInfo));
pRuntimeInfo->GetInterface(..., IID_PPV_ARGS(&pClrRuntimeHost));
//ランタイム開始pClrRuntimeHost->Start();
//文字列指定で.Netのstaticメソッドを呼び出し。//型はint func(string args)pClrRuntimeHost->ExecuteInDefaultAppDomain(...)
IntPtr ptr = Marshal.GetFunctionPointerForDelegate(func);
注意点としてdelegateのオブジェクトをGCで回収されないようにする必要があります。方法は複数あります。
・Static変数にする・GC.KeepAlive
・GCHandle.Alloc
・ホストAPI(メンドい)
・delegateを関数ポインタに変換
初期化
ManagedExporter.dll
IInterfaceTable
ManagedImporter
Info s_Create(path, assembly, type);
WpfGui.dll Win32App.exe
InputDataDialog : IInterfaceTable InputDataDialog : ObjectProxy
InputDataDialog::InputDataDialog()
: ObjectProxy(AssemblyPathLocal,
"WpfGui.dll",
"WpfGui.InputDataDialog")
・リフレクションで生成。・ IInterfaceTableを使ってDelegateを取得。・関数名でDelegateを取得できるDelegateを作成。・ GCHandle.Allocで寿命を確保。・ネイティブに返す。
//マネージドクラス公開情報。typedef void* (__stdcall *GetInterface)(name);
struct ExportInfo
{
HANDLE handle;
GetInterface getInterface;};
オブジェクト生成
ManagedExporter.dll
IInterfaceTable
ManagedImporter
Info s_Create(path, assembly, type);
Win32App.exe
InputDataDialog : ObjectProxy
BIND_PROXY_METHOD(SetOwner);BIND_PROXY_METHOD(DoModal);BIND_PROXY_METHOD(GetInputData);
//マネージドクラス公開情報。typedef void* (__stdcall *GetInterface)(name);
struct ExportInfo
{
HANDLE handle;
GetInterface getInterface; ←これを使う};
インターフェイスマッピング
逆に.Netからネイティブのクラスを使う。P/Invoke
void DisplayInfo::SetTitle(LPCWSTR title)
{
_strTitle = title;
}
HGLOBAL DisplayInfo::GetTitle()
{
size_t size = (_strTitle.length() + 1) * 2;
HGLOBAL global = ::GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, size);
memcpy((void*)global, _strTitle.c_str(), size);
return global;
}
//Exeでも公開関数を定義することは可能です。extern "C"
{
__declspec(dllexport) void __cdecl DisplayInfoSetTitle(void* ptr, LPCWSTR title)
{
return ((DisplayInfo*)ptr)->SetTitle(title);
}
__declspec(dllexport) HGLOBAL __cdecl DisplayInfoGetTitle(void* ptr)
{
return ((DisplayInfo*)ptr)->GetTitle();
}
__declspec(dllexport) void __cdecl DisplayInfoDelete(void* ptr)
{
delete ptr;
}
}
class DisplayInfo : IDisposable
{
IntPtr _core;
public DisplayInfo(IntPtr core)
{
_core = core;
}
public string Title
{
get
{
IntPtr ptr = DisplayInfoGetTitle(_core);
string title = Marshal.PtrToStringUni(ptr);
Marshal.FreeHGlobal(ptr);
return title;
}
set
{
DisplayInfoSetTitle(_core, value);
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing && _core != IntPtr.Zero)
{
DisplayInfoDelete(_core);
_core = IntPtr.Zero;
}
}
[DllImport("Win32App.exe")]
static extern IntPtr DisplayInfoGetTitle(IntPtr core);
[DllImport("Win32App.exe", CharSet = CharSet.Unicode)]
static extern void DisplayInfoSetTitle(IntPtr core, string title);
[DllImport("Win32App.exe")]
static extern void DisplayInfoDelete(IntPtr core);}
動的言語との連携
dynamic
次の指令が来ました。
その、画面の処理、ユーザにスクリプトでカスタマイズさせたりできない?
Rubyとか。
流行ってるんでしょ?
Windowsではそうでもないと思うけど・・・
ま、Rubyには興味あったし、やってみるか!
class Formatter
def SetName(name)
@name = name
end
def SetAge(age)
@age = age
end
def SetLangauge(langauge)
@langauge = langauge
end
def Format
@name + "\r\n" + @age.to_s + "\r\n" + @langauge;
end
end
Rubyのコード
internal static string Execute(InputData data)
{
//実行var ir = IronRuby.Ruby.CreateRuntime();
ir.ExecuteFile(@"..\formatter.rb");
//クラス生成var type = ir.Globals.GetVariable("Formatter");
dynamic formatter = ir.Operations.CreateInstance(type);
//普通に使えるformatter.SetName(data.Name);
formatter.SetAge(data.Age);
formatter.SetLangauge(data.Langauge);
return (string)formatter.Format();}
C#から使う。
class RubyObject : IRubyDynamicMetaObjectProvider
dynamicで表されるRubyのクラスの正体は?
interface IRubyDynamicMetaObjectProvider : IDynamicMetaObjectProvider
IDynamicMetaObjectProviderを実装したクラスをdynamicに入れると、メソッド、プロパティー、フィールド解決時にGetMetaObjectを呼び出してくれる。なので、RubyObjectクラスがRubyに特化した解決をしてくれる。
*注)通常はIDynamicMetaObjectProviderではなく、DynamicObjectを継承して実装します。
public interface IDynamicMetaObjectProvider
{
DynamicMetaObject GetMetaObject(Expression parameter);
}
使う側は優雅!
RubyObjectが頑張ってくれている。
Dynamicは、これだけでなく、
動的なインターフェイスを持つライブラリに優雅なインターフェイスを与えてくれる!
XML操作とか
COMとかね。
あなたも、そんなライブラリ持っていませんか?
Codeeer.Friendly.Dynamic
弊社にはあります。
http://www.codeer.co.jp/AutoTest
無料
対象プロセスを
強力な操作方法(おそらく世界で唯一)でテスト用に操作するライブラリです。
軽くデモ・・・
以前は・・・
やってることは、実質変わりませんが、可読性と学習効率がUpしました。お客様に使ってもらっているのですが、サポートも減りました。
AppVar mainForm = _app[typeof(Application), "OpenForms"]()["[]"](0);
AppVar result = mainForm["MyMethod"]("100");
int value = (int)result.Core;Assert.AreEqual(101, value);
dynamicを使うと・・・
dynamic mainForm = _app.Type<Application>().OpenForms[0];
int value = mainForm.MyMethod("100");Assert.AreEqual(101, value);
ご清聴ありがとうございました。