.NETにおけるDllImportではないネイティブライブラリの使用法

画像処理なんかのように高速演算が求められる場面では、GUI部分で開発効率に優れたC#を使い、高速化が必要な部分でだけネイティブC/C++を使いたいなぁと思うことがしばしばあります。しかし、DllImportだとDLL作るのは若干面倒*1ですし、この時代にあえて非.NETな過去バージョンのDLLを作るというのもなんだか気持ち悪いものです。

3つのプロジェクト

で、実はこういう場合、「(A)ネイティブC/C++コード」を「(B)C++/CLIコード」でラップし「(C)C#コード」から呼び出す、という形をとることができます。具体的なやり方については↓を参照してください。

ソリューション内に(A)(B)(C)それぞれをプロジェクトとして作るわけですが、(A)はネイティブC/C++で書かれてるので肝心のパフォーマンスや実装の自由度も当然ネイティブに匹敵します*2
ただ、このやり方だと、たとえちょっとした処理の切り出しであっても3つのプロジェクトを作らなきゃいけないのが単純に面倒なのと、プロジェクト(A)(B)で「*.h」や「*.cpp」に同じことを何度も書かされる感じ(しかも互いが異種C++なので余計にややこしい)がして個人的に好きじゃありません。
ここがなんとかならんもんかと試行錯誤していたら↓な記事を発見。さすが川俣さんw。

なんと!「#pragma unmanaged/managed」命令によってC++/CLIとネイティブC/C++は同一ソースに混在できるそうな(なんという仕様・・・)。じゃあこれ使えば万々歳ですねぇ。・・・が、肝心のパフォーマンスはどうなるんでしょう??。
ソースがプロジェクト絡みのものなので貼れないのが申し訳ないのですが、結果は↓こんな感じでした。環境によっても変わると思いますので参考程度に。

対象処理をC#の関数で記述 141[ms]
対象処理をC++/CLIの関数で記述 122[ms]
対象処理をネイティブC/C++の関数で記述(C++/CLIでラップ) 91[ms]
対象処理をネイティブC/C++の関数で記述(#pragmaで混在) 90[ms]

C#よりC++/CLIのほうが速いのかい・・・とちょっと思ってしまいました。・・・あ、それは本題じゃないですw。意外にも新旧混在コードでも速度は変わりませんでした。こうなれば(A)のコードを(B)に突っ込んでプロジェクトを二つにした方が絶対便利じゃん、という話ですね。

注意点

ただ、いろいろ試行錯誤する中でちょっと気になった点がありましたので挙げておきます。
一つ目がコンパイルオプションの変更。C++/CLIでDLLプロジェクトを作成すると、Releaseモードのデフォルトの最適化オプションが「Custom」*3となっていて、この設定だと少し遅いです(113[ms])。これを「/O2」に変更することで上記の結果が得られました。
二つ目が、C++/CLIとネイティブC/C++の混在による可読性の低下の防止。自分がとった方法は「native.hpp」「cli.cpp」を次のように分類しました。native.hppのほうは「#pragma unmanaged」以外は既存のC/C++と同じですから導入しやすいと思います。

//native.hpp
#pragma once
#pragma unmanaged
#include <cstdio>
using namespace std;

void process(){
    printf("Native C++ での処理");
}
//cli.cpp
#include "native.hpp"
#pragma managed
using namespace System;

public ref class Warpper{
public:
    static void Process(){ //C#から呼び出し可能なメソッド
        process(); //ネイティブ処理を呼び出す
    }
};

おわりに

このエントリ書いてる間に↓早速検証までしていただいたようです*4。噂の張本人はたぶんオレ(笑)。

Intelコンパイラに加えて、CUDAコンパイラなんかも別プロジェクトで切り替えないと苦しい例の一つな気がします(マトモにやったことないので想像ですけど)。「C++/CLI with pin_ptr」がネイティブに匹敵というのは興味深いですね〜。慣れた人はこれのほうが純.NETなので良いかもです。逆に慣れてない方や過去の資産がある方は、C++/CLIは結構な別言語(人間と人造人間くらい違う)ですので、そのままネイティブコードをコピペして使用するほうが楽だと思います。あとSSEイントリンシック命令とかも使いやすそうとか(実は今からやってみますw)。
ではでは♪ご参考に!。

*1:ついでに言うと、あまりプログラミングに詳しくない友人などに「DLLの形で作ってちょうだい」と言いにくい、という事情もある。

*2:引数の渡し方がややこしいですが、画像処理に限ればプリミティブ変数・配列・ポインタの場合がほとんどですのですぐ慣れます。

*3:英語版VS2008を使っているので日本語版での表記が分かりません。ご勘弁をば。

*4:高専5年でこのプログラミング力は大したもんですねぇ。ウチの高専時代の後輩じゃ普通に負けるだろうなぁw。