C#からネイティブC++を使うには
2009.01.17 土 16:36:55
時代はすでにJavaやC#をはじめとしたJIT方式の高度に管理されたプログラミングが主流となっています。しかし、やはり速度が必要な部分にC++を使いたいという要望は当然。
ということでC#からC++のアンマネージドなライブラリを呼び出す方法が知りたかったので調べました。
方法1:C#から直接DLLを呼び出す
方法2:C++/CLIによる薄いラップライブラリを作成
ここでは方法2を使います。
自作のネイティブライブラリをスタティックライブラリとしてビルドし、それをC++/CLIでラップしてマネージなDLLとして出力、あとはC#側で参照に追加すれば普通に使えます。
既存の(アンマネージな)DLLをラップするときも同じです。
とにかくかゆいところに手が届くC++/CLIに感動しました。
1.C++でネイティブなライブラリ(スタティック/DLL)を作成する
これについては特段の説明は不要でしょう。
Visual Studioでプロジェクトを生成するときにVisual C++からWin32のWin32 プロジェクトを選択します。ウィザードで「スタティックライブラリ」か「DLL」を選択します。
これで必要な関数をC++スタイルで(クラスのスタティックメンバとして)追加します。
これをビルドすると、DLLでもスタティックライブラリでも、*.libファイルが生成されます。これを後にラッパに使用します。
注意?: DLLとして作成した場合、ただしくエクスポートしないと*.libファイルすら生成されません。
クラス定義のほうで
__declspec(dllexport) type __stdcall func(...)
とちゃんと記述しましょう。ハマると長時間迷います(迷った!)。
注意?: ちゃんとヘッダ(*.h)とソース(*.cpp)に分けましょう。あとで使います。
例:
//func.h class TestClass { public: static void func(); }; |
//func.cpp #include "stdafx.h" #include "func.h"
void TestClass::func() { //noop } |
ソースを書いたらひとまずビルドしましょう。
なおサードパーティ製DLLで*.libが付属している場合は直接2にいけます。
付属してない場合は、一番上にある方法1を使うことになります。
2.C++/CLIによるラッパライブラリを作成
ここで新たなプロジェクトを生成します。
Visual C++のCLRのクラスライブラリを選択します。これは.NETの他言語から直接アクセスできるマネージDLLを生成します。
まず、ヘッダを追加します。
先ほどのfunc.hの内容をそっくりそのままコピーして持ってきます。
なおファイルごとCtrl+D&Dでコピーするとビルドできなくなります(ファイル実体が増えないからか)。中身をコピーしてください。
つぎに、リンクするライブラリを設定します。
ソリューションエクスプローラからラッパプロジェクトを選んで右クリック、プロジェクトのプロパティを開き、構成プロパティ→リンカ→入力のページにある追加の依存ファイルの項目に、1で作成したライブラリのlibファイルのパスを入力します。
必須ではないですが、特にstaticライブラリの場合はプロジェクト依存関係を正しく設定した方がいいです。ラッパはネイティブなライブラリに依存します。
// Wrapper.h #pragma once using namespace System;
namespace Wrapper { public ref class Wrap { public: static void func(); }; } |
//Wrapper.cpp #include "stdafx.h" #include "Wrapper.h" #include "func.h"
using namespace Wrapper; using namespace System;
void Wrap::func() { TestClass::func(); } |
これでビルドすれば、DLLが生成されます。
3.C#でのプログラムを生成
あとはこれを使うC#のプログラムを作成するのみです。
ラッパーは.NETなクラスなので、C#プロジェクトの参照に追加するだけで、IntelliSenceまでちゃんと使えるようになります。
Wrapper.Wrap.func(buf, buf.Length); |
こんなかんじ。
パフォーマンスについて
(1)ネイティブなDLLをラップしたマネージDLL経由でコール
(2)ネイティブなスタティックライブラリをラップしたマネージDLL経由でコール
(3)C#に直接書いたメソッドをコール
これらのコール速度を比較したところ、(3)はダントツではやく、(2)が(1)より若干速いながらも(3)よりは数十倍劣る感じとなりました。
もちろん絶対時間がきわめて小さく、μ秒以下のオーダーなので、何らかの処理を行うとなると状況が変わってきます。
関数ないで数千〜数万要素ほどの配列をバブルソートするプログラムを追加したところ、コールのオーバーヘッドは全くみられず、(1)、(2)が全く同等、(3)がほぼ半分の速度でした。簡単なプログラムでしたがネイティブのパワーが発揮されたと言えます。
(1)と(2)では、コールのオーバーヘッドには有意差がありましたが、実行処理速度は同等で速いです。
(1)ではDLLがネイティブ、ラッパでそれぞれ別々に存在し、つねに同じディレクトリに入れ…など煩雑となります。?はネイティブコードがラッパDLLに埋め込まれているため、DLLは一つです。
このことから、自作のネイティブライブラリをC++/CLIでラップするなら、ネイティブ側はスタティックライブラリとして作成したほうが良いと言えるように思います。
もちろん状況次第ですが。
引数を渡したい
やはりネイティブコードに処理を委託する以上、なんらかのデータを渡せなければ意味はないです。
このあたりもすでに解決したので次の記事くらいで。