🐷
C# から C の DLL を呼ぶサンプル(SafeHandle 利用)
C# から C の DLL を呼ぶサンプル(SafeHandle 利用)
シナリオ
以下のようなシナリオでの典型的な実装例を示します。
- C++ のクラスライブラリを C# で使いたい
- C# からは C++ は使うのが困難なので C の DLL を呼ぶのが普通
- そのため、 C++ クラスをラップした C の DLL を作成
- その C の DLL を C# から呼び出したい
C# のプログラム
└ C の DLL(Hoge_C.dll)
└ C++ のクラスライブラリ
実装例
C# コード
Program.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace CDllFromCSharp
{
internal class Program
{
static void Main(string[] args)
{
using (HogeHoge hoge = new HogeHoge())
{
string inputFilePath = @"C:\TEMP\sample.hoge";
var isOpened = hoge.open(inputFilePath);
Debug.WriteLine("open : [" + inputFilePath + "] -> " + isOpened);
if (isOpened)
{
string saveFilePath = @"C:\TEMP\page_1.jpg";
var isSaveOk = hoge.saveAsImage(0, saveFilePath);
Debug.WriteLine("saveAsImage : [" + saveFilePath + "] -> " + isSaveOk);
hoge.close();
}
else
{
Debug.WriteLine("open 失敗> " + inputFilePath);
}
}
}
}
/// <summary>
/// HogeLibrary ラップするクラス
/// </summary>
public class HogeHoge : IDisposable
{
private HogeHandle _handle;
private bool _disposedValue = false; // Dipsose パターン用
public HogeHoge()
{
this._handle = HogeHandle.create();
if (this._handle.IsInvalid)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "create");
}
}
protected virtual void Dispose(bool disposing)
{
if (!this._disposedValue)
{
if (disposing)
{
// マネージド状態を破棄します (マネージド オブジェクト)
// IDisposable を継承するものは マネージド オブジェクト です。
if (this._handle != null && !this._handle.IsInvalid)
{
// Free the handle
this._handle.Dispose();
}
}
// アンマネージド リソース (アンマネージド オブジェクト) を解放し、ファイナライザーをオーバーライドします
// 大きなフィールドを null に設定します
this._disposedValue = true;
}
}
// 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
//~HogeHoge()
//{
// // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
// Dispose(disposing: false);
//}
public void Dispose()
{
// このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public bool open(string filePath_)
{
if (this._handle.IsInvalid) // Is the handle disposed?
{
throw new ObjectDisposedException("handle was destroyed.");
}
bool ret = false;
int openRet = HogeLibrary.hoge_open(this._handle, filePath_);
if (0 == openRet)
{
ret = true;
}
return ret;
}
public void close()
{
if (this._handle.IsInvalid) // Is the handle disposed?
{
throw new ObjectDisposedException("handle was destroyed.");
}
HogeLibrary.hoge_close(this._handle);
}
public bool isOpened()
{
if (this._handle.IsInvalid) // Is the handle disposed?
{
throw new ObjectDisposedException("handle was destroyed.");
}
return HogeLibrary.hoge_isOpened(this._handle);
}
public bool saveAsImage(int pageIndex_, string saveFilePath_)
{
if (this._handle.IsInvalid) // Is the handle disposed?
{
throw new ObjectDisposedException("handle was destroyed.");
}
bool ret = false;
int saveRet = HogeLibrary.hoge_saveAsImage(this._handle, pageIndex_, saveFilePath_);
if (0 == saveRet)
{
ret = true;
}
return ret;
}
}
/// <summary>
/// C の DLL API hoge_ 用の独自 unmanaged handle のラッパー
/// unmanaged handle は SafeHandle でラップすることが推奨されています。
/// cf. https://docs.microsoft.com/ja-jp/dotnet/standard/garbage-collection/implementing-dispose#implement-the-dispose-pattern-with-safe-handles
/// </summary>
internal sealed class HogeHandle : SafeHandle
{
public override bool IsInvalid => IntPtr.Zero == handle; // handle の値が 0 の場合、無効として扱う
// Create a SafeHandle, informing the base class
// that this SafeHandle instance "owns" the handle,
// and therefore SafeHandle should call
// our ReleaseHandle method when the SafeHandle
// is no longer in use.
private HogeHandle(IntPtr handle_) : base(IntPtr.Zero, ownsHandle: true)
{
this.SetHandle(handle_);
}
public static HogeHandle create()
{
return new HogeHandle(HogeLibrary.hoge_create());
}
protected override bool ReleaseHandle()
{
// 呼び出し遷移は以下のようになります。
// System.Runtime.InteropServices.SafeHandle.Finalize()
// └ System.Runtime.InteropServices.SafeHandle.InternalFinalize()
// └ HogeHandle.ReleaseHandle()
HogeLibrary.hoge_destory(this.handle);
return true;
}
}
/// <summary>
/// C 言語 DLL 呼び出し用のクラス
/// </summary>
internal static class HogeLibrary
{
[DllImport("Hoge_C.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static IntPtr hoge_create();
[DllImport("Hoge_C.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static void hoge_destory(IntPtr handle_);
[DllImport("Hoge_C.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static int hoge_open(HogeHandle handle_, string filePath_);
[DllImport("Hoge_C.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static void hoge_close(HogeHandle handle_);
[DllImport("Hoge_C.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.I1)]
public extern static bool hoge_isOpened(HogeHandle handle_);
[DllImport("Hoge_C.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static int hoge_saveAsImage(HogeHandle handle_, int pageIndex_, string saveFilePath_);
}
}
コードの説明
Program.Main
C の DLL を呼び出して動かすためのデモアプリ。
C# から使いやすいように作られた class HogeHoge の使い方を示します。
class HogeLibrary
Hoge_C.dll をラップするクラス。
ここでは C++ で Hoge クラスが実装されており、それをラップして C# で使えるように C の DLL 化したもの。C++ Hoge クラスインスタンスを new / delete するのが create / destory で、それ以外は Hoge クラスのメソッドを呼び出すラッパーです。
C の DLL でハンドルと言っているものの実体は C++ クラスのインスタンス(new Hoge)です。
【着目点】HogeHandle が使われていることに注目してください。
class HogeHandle : SafeHandle
HogeLibrary で利用される C でいうところの HANDLE (C# だと IntPtr)をラップするクラス。
C# だと unmanage の独自ハンドルは SafeHandle を継承したクラスを使うことが推奨されているため。
class HogeHoge : IDisposable
HogeLibrary をラップする C# のクラス。C# から C の DLL を呼びやすくするために、クラス化しています。
HogeHandle(マネージド オブジェクト) を内包しているので、Disposeパターンを用いています。
Discussion