Страницы

Поиск по вопросам

пятница, 12 июля 2019 г.

Правильная реализация Dispose вместе с SafeHandleZeroOrMinusOneIsInvalid

В сети есть много примеров реализации паттерна 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(procAddress);
_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. По возможности весь грязный интероп лучше держать в одном месте.

Комментариев нет:

Отправить комментарий