В сети есть много примеров реализации паттерна Dispose применительно к оберткам для C++ DLL, но все они немного отличаются, такое ощущение, что многие просто перепечатывают паттерн из любимого источника и все.
Есть DLL, из которой вызывается одна единственная функция, код следующий:
public class U2KApi:IDisposable
{
private const string FunctionName = "fname";
private const string B5 = "param1";
private const int B6 = param2;
public delegate int MainFuncDelegate(int b1, int b2, int b3, int b4, string b5, int b6);
private bool _disposedValue;
private SafeLibraryHandle _safeLibraryHandle;
private MainFuncDelegate _mainFuncDelegate;
public U2KApi()
{
var fileName = "dllname.dll";
_safeLibraryHandle = Kernel32Dll.LoadLibrary(fileName);
if (_safeLibraryHandle.IsInvalid)
{
var hrForLastWin32Error = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(hrForLastWin32Error);
}
var procAddress = Kernel32Dll.GetProcAddress(_safeLibraryHandle, FunctionName);
if (procAddress == IntPtr.Zero)
{
_safeLibraryHandle.Close();
throw new ArgumentException();
}
var delegateForFunctionPointer = Marshal.GetDelegateForFunctionPointer
_mainFuncDelegate = delegateForFunctionPointer;
}
public int MainFunc(int b1, int b2, int b3, int b4)
{
return _mainFuncDelegate(b1, b2, b3, b4, B5, B6);
}
private void Dispose(bool disposing)
{
if (_disposedValue) return;
if (disposing)
{
if (_safeLibraryHandle != null)
{
if (!_safeLibraryHandle.IsClosed)
{
_safeLibraryHandle.Close();
}
}
}
_safeLibraryHandle = null;
_mainFuncDelegate = null;
_disposedValue = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~U2KApi()
{
Dispose(false);
}
}
static class Kernel32Dll
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeLibraryHandle LoadLibrary(string fileName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, string procname);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FreeLibrary(IntPtr hModule);
}
class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeLibraryHandle() : base(true)
{
}
protected override bool ReleaseHandle()
{
return Kernel32Dll.FreeLibrary(handle);
}
}
Вопросы примерно следующие:
Нужно ли закрывать SafeHandle внутри if (disposing) - видел примеры обратного, когда проверка на disposing опускалась, и, честно, не понимаю, как правильно. Зачем тогда городить огород из Dispose(bool)?
Нужно ли присваивать null _safeLibraryHandle и _mainFuncDelegate - памяти много не съедят же?
Если нет, можно ли всё упростить?
UPD. Почитал ответы VladD, пытался думать. Насколько я понял, так как handle обернут в SafeHandleZeroOrMinusOneIsInvalid, то всё можно свести к простому освобождению _safeLibraryHandle в обычном Dispose(), выкинув финализатор и перегруженный Dispose(bool)
Ответ
Когда вы используете SafeHandle, классический паттерн Dispose помножается на ноль, и вне наследников SafeHandle все методы Dispose должны быть простыми методами, которые вызывают Dispose на приватных членах, реализующих IDisposable. Никаких финализаторов в коде быть не должно.
С точки зрения инкапсуляции ваш код для загрузки библиотеки и проверки ошибок я бы переместил в SafeLibraryHandle. По возможности весь грязный интероп лучше держать в одном месте.
Комментариев нет:
Отправить комментарий