はじめに
みなさん、OpenVINO使っていますか?
OpenVINOはIntel製CPUやGPU、VPU等の上で高速に推論をするエンジンなのですが、C#のインターフェースは提供されていません。
C/C++かPythonで書く必要がありますが、私は C# を使いたいのです!
ということで、SwigでWrapperを作ろうと決意したわけですが、作ったことがないので調べてみました。
私と同じように思っている方がどの程度いるか不明ですが、その方の助けの一部になればと思います。
この記事は以下5つのパートに分かれます。
お先に結論
SwigでWrapperを作りたいのですが、現時点では失敗しています。
Wrapper経由でC#から呼び出そうとすると例外を吐きます。
詳細は結論に記載していますが、詳しい方からアドバイスいただけるかもしれないと思い、この状態で投稿しております。
もし原因わかる方、これじゃないの?と思う方、コメントいただけると幸いです。
[2020.11.23追記]
原因が判明しましたので記事とソースを修正しました!
環境
今回dllやWrapperを作成した環境は以下です。
- Windows10 Pro(x64)
- Visual Studio 2019 Professional
- Swig 4.0.2
ソースコード
ソースコードはここに格納しています。
プロジェクトとしては以下3つになります。
- HellDll
C/C++で作成するdllのプロジェクトです。 - HellpCpp
作成したdllをC++から呼び出すことが出来るのかテスト用のプロジェクトです。 - HelloCSharp
作成したdllをC#から呼び出すことが出来るのかテスト用のプロジェクトです。
C++でdll作成
今回はClassifierという名前のクラスとそのクラスを生成する関数をdllで提供しますので、そのためのプロジェクトをHelloDllという名前で作成していきます。
作成したヘッダ(Classifier.h)の中身は以下です。
#pragma once
#ifdef DLL_EXPORT
#define DLL __declspec(dllexport)
#else
#define DLL __declspec(dllimport)
#endif
class Classifier
{
public:
virtual bool Train(int val) = 0;
virtual bool Fit() = 0;
};
extern "C" DLL Classifier * GetInstance(void);
dllを作成する部分の詳細はC++で作成したDLLでAPIではなくクラスを提供する方法のやり方を踏襲していますので、そちらを参照してください。
C++からdll呼び出し
C++で作成するコンソールプログラムから作成したdllが呼び出せるか確認します。
プロジェクト名はHelloCppです。
呼び出す部分は以下となっています。
HMODULE hHandle = LoadLibrary(L"HelloDll.dll");
FUNC func = (FUNC)GetProcAddress(hHandle, "GetInstance");
Classifier* instance = func();
instance->Train(10);
こちらも、C/C++で作成したdllをC++から呼び出す部分もC++で作成したDLLでAPIではなくクラスを提供する方法のやり方を踏襲していますので、そちらを参照してください。
Wrapper作成
Wrapperを作成する方法はいくつかあるようなのですが、すべて手作業でするのも面倒なのでSwigを使ってみました!
SwigでWrapperを作るのは簡単で、インターフェースファイルと呼ばれる.iファイルを作成するだけです。ただ、これが奥深く、理解するのが大変というか、まだ私自身理解できていないという・・・
とりあえず、Swigを使ってWrapperを作っていきましょう。
Swigのインストール
ここからサクッと落としましょう
2020年11月21日にこの記事を記載していますが、現時点での最新は4.0.2となっています。
私の環境はwindowsなのでswigwin-4.0.2をダウンロードすることになります。
お使いの環境に合わせてダウンロードしてください。
ダウンロード後は、適当な場所に展開しパスを通すなりしてください。
これでインストールは完了です。
インターフェースファイルの作成
ようやくSwigの肝であるインターフェースファイルの作成です。
マニュアルはここにありますので、まずは熟読しましょう。
で、とりあえず必要最低限の内容をカバーしたインターフェースファイルは以下のようになります。
/* File : Swig.i */
/* windows.iをincludeしないと、なぜかエラー */
%include <windows.i>
%module HelloDll
%{
#include "Classifier.h"
%}
/* Let's just grab the original header file here */
%include "Classifier.h"
ポイントは以下です。
- %moduleの後ろにdll名
- %{}の中に提供するヘッダ名を記載
- %includeの後ろに提供するヘッダ名を記載
- エラー対策にwindows.iを最初にinclude
最後のwindows.iをincludeする部分ですが、広大なネットの海を探しているときにそのような情報を見つけたのですが、今となってはその記述を見つけられず。。。
とりあえず効果があるので記載していますが、どのような理由でエラーが回避できるのかなどは不明です。
インターフェースファイルのビルド
コマンドプロンプトで毎回ビルドしても良いのですが、Visual Studioでdllをビルドした際に一緒にビルドするようにしましょう。
手順は以下です。
- 作成したインターフェースファイル(今回はSwig.i)をdllのプロジェクト(今回はHelloDll)に加える
- 1.で加えたSwig.iをVisual Studio上で右クリックしプロパティを開く
- 項目の種類をカスタムビルドツールに変更
- 3.を実行すると左側にカスタムビルドツールが表示されるようになるので、コマンドラインに以下を記載
"C:\Program Files\swigwin-4.0.2\swig.exe" -c++ -csharp -cppext cpp "%(FullPath)"
5.出力ファイルに以下を記載
%(Filename)_wrap.cpp; %(Outouts)
1.~5.が完了後、HelloDllプロジェクトをリビルドして、以下のファイルが作成されれば成功です。
- Classifier.cs
- HelloDll.cs
- HelloDllPINVOKE.cs
- Swig_wrap.cpp
[2020.11.23 追記]
Swig.exeが作成した上記4つのファイルのうち、Swig_wrap.cppをHelloDllプロジェクトのソースファイルフォルダに追加します。これが必要でした!
C#からWrapper経由でのdll呼び出し
C#で作成するコンソールプログラムから作成したdllが呼び出せるか確認します。
プロジェクト名はHelloCSharpです。
することは2つだけです。
- Swig.exeが作成した.csファイルをプロジェクトに加える
- dllが提供する関数を呼び出す
1.は特にわからない部分はないと思いますので割愛します。
2.ですが、呼び出す部分は以下となります。
Classifier classifier= HelloDll.GetInstance();
classifier.Train(10);
では実際に動作するか見てみましょう!
。
。
。
はい、動きませんね。
私にもわかりません(汗)
dll自体はC++側で呼び出せているので問題ないはずなので、やはりSwigの使い方だと思うのですが、謎です。。。
[2020.11.23 追記]
問題なければ以下が表示されるはずです。
Hello World!
GetInstance()
ClassifierImpl
Train : val = 10
結論
Swigを使ってWrapperを作り、C++のdllをC#から呼び出したかったのですが、C#側で呼び出した途端、以下の例外を吐いて失敗します。
TypeInitializationException: The type initializer for 'SWIGExceptionHelper' threw an exception.
EntryPointNotFoundException: Unable to find an entry point named 'SWIGRegisterExceptionCallbacks_HelloDll' in DLL 'HelloDll'.
解決策を探している途中なのですが、にっちもさっちもいかないので、アドバイスいただけたらと思いQiitaに投稿しました。
もし原因わかる方、これじゃないの?と思う方、コメントいただけると幸いです。
何かしら情報にアップデートあれば本記事を更新いたします。
[2020.11.23 追記]
無事、C++で作成したdllをSwigでWrapperを作成してC#から呼び出すことが出来ました。
Wrapper開発の工数削減を目的にSwigを使いましたが、インターフェースファイル(.iファイル)を用意するだけなので、インターフェースファイルさえ問題なく作ることが出来れば目的は達せられるという印象を受けました。
今回は簡単にするために引数や戻り値を簡単なものにしましたが、どうやら文字列や構造体、クラスやポインタなど色々難しそうですが、少しずつ調べていきたいと思います。