C#からC++のDLLを使うには2通りあり、DllImport属性を使った動的リンクと、LoadLibrary関数を使った動的ロードがあります。
予めDLLのパスがわかっている場合は動的リンクで事足りますが、今回はプログラム起動後にDLLパスを指定するためにLoadLibraryを使った動的ロードを行います。
DLLの動的ロード処理
C++のDLLを用意
C++側に簡単な足し算処理のソースを作成しました。
extern "C" __declspec(dllexport) int Sum(int a, int b) {
return a + b;
}
ビルドして出力先ディレクトリの.dllファイルができあがるのを確認しておきます。
C#側でロード処理の用意
先ずDLLをロードする際に使用する基底クラスを作成します。
using System.Runtime.InteropServices;
public abstract class BaseDllClass : IDisposable
{
[DllImport("kernel32")]
private static extern IntPtr LoadLibrary(string dllFilePath);
[DllImport("kernel32")]
private static extern IntPtr GetProcAddress(IntPtr module, string functionName);
[DllImport("kernel32")]
private static extern bool FreeLibrary(IntPtr module);
private IntPtr _dllPtr;
public BaseDllClass(string dllFilePath)
{
if (!File.Exists(dllFilePath))
throw new Exception("Not found dll.");
// DLLのロード
_dllPtr = LoadLibrary(dllFilePath);
if (_dllPtr == IntPtr.Zero)
throw new Exception("Failed load dll.");
}
protected T LoadFunction<T>(string functionName)
{
// 関数ポインタを取得
IntPtr functionPtr = GetProcAddress(_dllPtr, functionName);
if (functionPtr == IntPtr.Zero)
throw new Exception("function not found.");
// 関数ポインタをdelegateに変換する
return (T)(object)Marshal.GetDelegateForFunctionPointer(functionPtr, typeof(T));
}
public void Dispose()
{
FreeLibrary(_dllPtr);
}
}
次にBaseDllClassを継承した、DLLのラッパークラスを作成します。
using System.Runtime.InteropServices;
public class MyMath : BaseDllClass
{
// ここで定義したdelegateにあとで関数ポインタをマッピングする
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int Sum_Internal(int a, int b);
private Sum_Internal _sum;
public MyMath(string dllFilePath): base(dllFilePath)
{
// コンストラクタで関数をロードしておく
_sum = LoadFunction<Sum_Internal>("Sum");
}
// ラッパー関数
public int Sum(int a, int b)
{
return _sum(a, b);
}
}
C++DLLをC#側の実行ディレクトリコピー
C#側で参照できるようにdllファイルを移動しておいてください。
ここまでの手順で以下のような構成になったかと思います。
いざ呼び出し
using var myMath = new MyMath("myMath.dll");
Console.WriteLine(myMath.Sum(1, 1));
無事に足し算の結果が表示されました。
ここまででDLLの動的ロードと、関数の呼び出し方は把握できたかと思います。
ただ、実際にはこんな単純な関数ではなく、もっと複雑なデータ構造のやり取りがあると思うので、次回の記事では構造体、配列、コールバックなどもDLLとやり取りしてみようと思います。
次回記事