まずプロジェクトの設定から
CやC++のプロジェクトをC++/CLIに変換する(プラットフォームをすべてのプラットフォームにして設定すると便利)
・プロジェクトのプロパティー
・詳細
・共通言語ランタイムサポート /cli
・ターゲットファイルの拡張子 .dll
・C/C++
・言語
・準拠モード いいえ
・基本ランタイムチェック 規定
・詳細設定
・コンパイル言語の選択 /TP
・リンカー
・出力ファイル 設定されていたら適切に修正
・ソースコードのプロパティー
・C/C++
・基本ランタイムチェック 規定
マネージドのクラスの処理はネイティブと別のソースにする。
これはCやC++の#defineが強力すぎるためマネージドのOKなどの定数が1や0などに置き換えられコンパイルが通らなくなるのを防ぐので必須です。
ネイティブ側の関数も独自にhを作り間接的に参照するようにしましょう。
マネージドのクラスはdllexportなどなくても参照できるようになるから便利
以下のようなファイルを追加し元のmain処理を修正する(ここではcustom_main)
export.cpp
#include "export.h"
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Collections::Generic;
namespace Demo {
public ref class Option
{
public:
const static int AUTO_COUNT = -1;//定数サンプル
int Count;
String^ Name;
array<unsigned char>^ Data;//C# byte[] Data
};
public ref class DemoClass
{
public:
//デフォルト引数の代わりの処理
static void DemoCall(String^ Args) {
DemoCall(Args,gcnew Option());
}
static void DemoCall(String^ Args,Option^ option) {
String^ cmmandLine = "demo.dll";
if (Args != "")
cmmandLine += " "+Args;
array<String^>^ args;
char** tmpArgs = nullptr;
NativeOption noption;
try {
//ここのサンプルは半角スペースでの簡易的な引数分解です
args = cmmandLine->Split(' ');
tmpArgs = new char* [args->Length];
for (int i = 0; i < args->Length; i++) {
tmpArgs[i] = (char*)(void*)Marshal::StringToHGlobalAnsi(args[i]);
}
//マネージドのOptionをネイティブに変換設定
noption.count = option->Count;
//文字列はネイティブで扱える形に変換 解放が必要
noption.name = (char*)(void*)Marshal::StringToHGlobalAnsi(option->Name);
//データ配列はメモリー確保してコピーするか ポインタが動かないように固定する
pin_ptr<unsigned char> p= &option->Data[0];
noption.data = p;
if(custom_main(args->Length, tmpArgs, &noption)){
throw gcnew Exception("失敗しました");
}
}
catch (char* msg) {
String^ str = gcnew String(msg);
throw gcnew Exception(str);
}
finally {
if(noption.name!=nullptr){
Marshal::FreeHGlobal(IntPtr((void*)noption.name));
}
for (int i = 0; i < args->Length; i++) {
Marshal::FreeHGlobal(IntPtr((void*)tmpArgs[i]));
}
delete[] tmpArgs;
}
}
};
}
export.h
struct nativeOption {
int count;
char *name;
unsigned char *buffer;
};
//必要なら引数ふやす
int custom_main(int argc, char* argv[],nativeOption* option);
あと必要であればネイティブの独自コード用にexport_native.cppなど追加
main関数以下の修正の注意点
abortやexitなどの処理に注意。DLLを使用したアプリが終了する。
throwで文字列などを返すように修正する。
メモリー解放がアプリ終了に依存している場合適切に開放するようにする
内部で変動するグローバル変数に依存する処理がある場合
スレッドセーフでなくなるため必要であれば引数を増やすなどで参照しないよう修正する。
C#でいうoutの引数を行う場合
[Runtime::InteropServices::Out] Option^% option
などとすればよいです。
あとはC#プロジェクトで参照すると
Demo.DemoClass.DemoCallが見えて呼べるようになってるはず