こんにちは。店長です
暑い日と寒い日が繰り返してますが、
皆様は体調を崩してないでしょうか?
今日は、
ネイティブコードとマネージドコードを同時に使うテクニック
を紹介致します。
例えば、
C++でゲーム用の描画ライブラリを作ったあと、
”この描画ライブラリを使ってグラフィカルなツール作りたいなぁ・・・”
と思ったことは無いでしょうか?
そして実践してみると、
・WIN32APIが使いにくい。
・もっとリッチなGUIを使いたい。
・ツール部分だけはC#使いたい。
など頭を悩ませた人も少なくないと思います
そういう時に役に立つかもしれないテクニックです
あんまり詳細なこと書くと大変量になるので、要点以外省くことをご了承ください。
例として
・Windows環境
・VisualStudioでの開発
・C++で描画ライブラリ(graphics.dll)を作成(ネイティブコード)
・C#とxamlでクライアントウィンドウ部分を作成(マネージドコード)
・描画結果は青い背景と白い四角形のポリゴン
をやってみます。
C++描画ライブラリ側の設定
1,dllを作る
2,C#から機能を使うための関数を用意する
3,モジュール定義ファイル(.def)を用意する
1,ついて
ライブラリとして出力する形式をdllにしてください。
2,について
以下のような描画ライブラリのヘッダファイルがあり、
その機能をC#側で使ってみようと思います。
/** * @brief レンダラークラス */ class CRenderer { public: // コンストラクタ CRenderer(); // デストラクタ ~CRenderer(); // 初期化 bool Initialize(); // 終了処理 void Finalize(); // 活性化 bool Activate(); // 非活性 void Deactivate(); // ウィンドウの設定 void SetWindow( HWND i_wnd ); // 描画 void Render(); private: HWND m_hwnd; // ウィンドウ HDC m_hdc; // デバイスコンテキスト HGLRC m_hglrc; };
ところが、このままではC#からC++クラス内の関数を使うことができません。
そこで、このクラスの機能を呼び出すための関数を用意いたします。
ヘッダファイル
#ifdef __cplusplus extern "C" { #endif __cplusplus // インスタンスの生成 CRenderer* NewRenderer(); // インスタンスの削除 void DeleteRenderer(CRenderer* p); // 初期化 bool InitializeRenderer(CRenderer* p); // 終了処理 void FinalizeRenderer(CRenderer* p); // ウィンドウの設定 void SetWindowRenderer(CRenderer* p,HWND i_hwnd); // 描画処理 void RenderRenderer(CRenderer* p); #ifdef __cplusplus } #endif __cplusplus
ソースファイル
CRenderer* NewRenderer(){ return new CRenderer(); } void DeleteRenderer(CRenderer* p){ delete p; } bool InitializeRenderer(CRenderer* p){ return p->Initialize(); } void FinalizeRenderer(CRenderer* p){ p->Finalize(); } void SetWindowRenderer(CRenderer* p,HWND i_hwnd) { p->SetWindow( i_hwnd ); } void RenderRenderer(CRenderer* p) { p->Render(); }
機能としてはクラスのインスタンスを渡して、
各関数を呼び出すだけです。
このようにクッションを置くことで
C#側から呼び出すことができるようになります。
3,について
dllの関数をエクスポートするためのモジュール定義ファイルを用意します。
; graphics.def : DLL のモジュール パラメータを宣言します。 LIBRARY "graphics" EXPORTS ; 明示的なエクスポートはここへ記述できます NewRenderer ;インスタンス生成 DeleteRenderer ;インスタンス破棄 InitializeRenderer ;初期化 FinalizeRenderer ;終了 SetWindowRenderer ;ウィンドウの設定 RenderRenderer ;描画
__declspecキーワードを使う方法でも同じ事ができますが、
個人的な好みで別ファイルとしております。
このファイルをプロジェクトのプロパティ→リンカー→入力→モジュール定義ファイル
に設定しておきます。拡張子は.defです。
これでビルドが完了したら、C++ライブラリ側の準備は完了です
C#クライアントウィンドウ側の準備
1,C++のCRendererの機能を呼び出す用意
2,ウィンドウのハンドルを設定
1,について
まず、graphiscs.dllの関数を呼び出せるようにします。
C#側のCRendererクラス
/** * レンダラー */ class CRenderer { [DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr NewRenderer(); [DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void DeleteRenderer(IntPtr p); [DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)] private static extern bool InitializeRenderer(IntPtr p); [DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void FinalizeRenderer(IntPtr p); [DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void SetWindowRenderer(IntPtr p, IntPtr hwnd ); [DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void RenderRenderer(IntPtr p); // コンストラクタ public CRenderer() { } // 生成 public void New() { m_renderer = NewRenderer(); } // 破棄 public void Delete() { DeleteRenderer( m_renderer ); } // 初期化 public bool Initialize() { return InitializeRenderer( m_renderer ); } // 終了 public void Finalize() { FinalizeRenderer(m_renderer); } // ウィンドウの設定 public void SetWindow(IntPtr hwnd) { SetWindowRenderer(m_renderer, hwnd); } // ウィンドウの設定 public void Render() { RenderRenderer(m_renderer); } private IntPtr m_renderer = IntPtr.Zero; }
2,について
描画を行うウィンドウのハンドルを取得し、CRendererクラスに設定します。
タイマーイベントを使っての描画の呼び出しも例として記載しておきます。
ウィンドウのサイズなどの定義はMainWindow.xamlに記載されているものとします。
// MainWindow.xaml の相互作用ロジック public partial class MainWindow : Window { private CRenderer renderer; private DispatcherTimer timer; // コンストラクタ public MainWindow() { InitializeComponent(); } // 初期化処理 private void WindowSourceInitialized(object sender, EventArgs e) { // ウィンドウハンドル取得 IntPtr hWnd = new WindowInteropHelper(this).Handle; // レンダラーの生成 renderer = new CRenderer(); renderer.New(); renderer.SetWindow(hWnd); renderer.Initialize(); //タイマー timer = new DispatcherTimer(); timer.Tick += new EventHandler(DispatcherTimerTick); timer.Interval = new TimeSpan(0, 0, 0, 0, 16); // 16msecで描画 timer.Start(); } // タイマーイベント private void DispatcherTimerTick(object sender, EventArgs e) { if (renderer != null) renderer.Render(); } // サイズ変更処理 private void WindowSizeChanged(object sender, SizeChangedEventArgs e) { // if (renderer != null) // renderer.SizeChanged(e.NewSize); } // 後片付け private void WindowClosed(object sender, EventArgs e) { renderer.Finalize(); renderer.Delete(); if (renderer != null) // { // renderer.Dispose(); renderer = null; // } } }
WindowSourceInitialized関数内で
・ウィンドウハンドルの取得。
・CRendererクラスの準備。
・タイマーの準備を行なっております。
また、描画はタイマーイベントで、
DispatcherTimerTick関数内で行なっております。
一応MainWindow.xamlも記載しておきます。
<Window x:Class="Main.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="480" Width="640" SizeChanged="WindowSizeChanged" SourceInitialized="WindowSourceInitialized" Closed="WindowClosed"> <Grid> </Grid> </Window>
今回はあまり大事な部分ではありませんが、
このファイルを編集するだけで簡単にウィンドウの設定ができます。
これでC#側も準備が整いました
実行結果は以下のようになります。
上記のような手順を踏むことで、
C++などのネイティブコードで作ったライブラリを利用しつつ、
マネージドコードの強みを生かしてGUIを作ることなどができます。
最後に、
現在でも速度が求められるコアな部分はネイティブコードが主流です。
ただ、マネージドコードの強みを利用することでツールなどの開発効率は非常に良くなります。
プログラム言語も適材適所で使っていけるようになるとことが大事ですね
上記サンプルの実行ファイルとdllファイルを置いておきますので、おためしください。
サンプル