Маршалинг SafeHandles от неуправляемого к управляемому
Автор вопроса: Tom DaviesОтветов: 3
В собственной оболочке dll, которую я пишу, я только что заменил все варианты использования IntPtr (для маршалинга дескрипторов) на SafeHandles. У меня сложилось впечатление, что правильно написанный тип SafeHandle взаимозаменяем с IntPtr таким образом.
Однако мои вызовы Marshal.GetFunctionPointerForDelegate теперь вызывают исключение:
Cannot marshal 'parameter #n': SafeHandles cannot be marshaled from unmanaged to managed.
Обратный вызов содержит дескриптор в списке аргументов, поэтому вместо него делегат содержит SafeHandle (вместо IntPtr, как раньше). Так я могу просто не делать этого? Если да, то каковы мои варианты использования SafeHandles, учитывая, что мне нужно маршалировать обратные вызовы?
Вот отредактированный пример собственного заголовка dll:
struct aType aType;
typedef void (*CallBackType)(aType*, int);
aType* create(); // Must be released
void release(aType* instance);
int doSomething(aType* instance, int argumnet);
void setCallback(CallbackType func);
Бит, который вызывает у меня проблемы, - это обратный вызов. Сторона C# выглядела так:
delegate void CallBackType(IntPtr instance, int argument);
Потом:
var funcPtr = Marshal.GetFunctionPointerForDelegate(del = new CallbackType(somefunc)):
NativeFunction.setCallback(funcPtr)
Это отлично работает, и всегда так было. Однако я хотел перейти от IntPtr для управления дескриптором к safehandle и прочитал, что это замена. Однако замена IntPtr подклассом SafeHandle в приведенном выше коде C# вызывает сообщение об исключении:
delegate void CallBackType(MySafeHandle instance, int argument);
Разве SafeHandle не является концепцией стороны .NET (в отличие от родной)? Как выглядят ваши объявления взаимодействия? 500 - Internal Server Error 25.01.2013
SafeHandle действительно является концепцией .NETside (как и IntPtr). Добавлю больше деталей к моему первоначальному вопросу. Tom Davies 25.01.2013
SafeHandle - это абстрактный класс. Нет никакого способа, которым маршаллер pinvoke мог бы выбрать правильный тип производного класса из объявления, все, что у него есть, это IntPtr. Hans Passant 25.01.2013
Извините, я должен написать MySafeHandle. Очевидно, мне пришлось реализовать свой собственный SafeHandle с соответствующим переопределением Release. Я отредактирую свой вопрос, чтобы сделать его более понятным. Tom Davies 25.01.2013
Сообщение об ошибке вводит в заблуждение. 100% возможно маршалировать безопасные дескрипторы из неуправляемых в управляемые, потому что именно так SafeHandles должны создаваться. Например, посмотрите, как определяется CreateFile:
Причина, по которой компилятор генерирует сообщения об ошибках, заключается в том, как вы объявляете делегата. Я сделал ту же ошибку, что и вы, и попытался использовать тип MySafeHandle в качестве параметра делегата, когда я объявил свой делегат обратного вызова (здесь неуправляемый код будет вызывать ваш управляемый код):
Для чего я получил точно такое же сообщение об ошибке, как и у вас. Однако, как только я изменил свою подпись делегата на IntPtr, ошибка исчезла, поэтому мы видим, что наша наивная интуиция неверна...
Видите, вуаля, ошибка исчезла! Теперь нам просто нужно решить, как использовать IntPtr, который входит в делегат, для поиска правильного объекта MySafeHandle...!
Как только я выясню, какое изменение устранило ошибку, я также смогу придумать теорию относительно того, почему оно устраняет ошибку.
Теория: (непроверенная)
Причина, по которой вы должны использовать IntPtr в подписи делегата, заключается в том, что SafeHandles являются особыми. Всякий раз, когда вы маршалируете как SafeHandle, маршаллер CLR автоматически преобразует непрозрачный дескриптор IntPtr в новый объект CLR SafeHandle, которому владеет рассматриваемый HANDLE. (Обратите внимание, что SafeHandles - это объекты, а не структуры!)
Если бы вы создавали новый объект-владелец для OS HANDLE каждый раз, когда вызывается делегат, у вас скоро были бы очень большие проблемы, потому что ваш объект будет собирать мусор, как только вы вернетесь из делегата!
Так что я предполагаю, что, возможно, компилятор просто пытается спасти нас от этой ошибки - в своей запутанной формулировке?
Ответ 2 · рейтинг 3 · JerKimball
Хм ... просто мысли вслух, но я думаю, вам придется применить какую-то промежуточную обертку; SafeHandle работает с реализацией P/invoke во время базовой сортировки, но не с "ручной сортировкой", как вы здесь... попробуйте что-нибудь подобное, может быть?
internal delegate void InnerCallbackType(IntPtr instance, int argument);
public delegate void MyCallBackType(MySafeHandle instance, int argument);
public void SetCallback(Action<MySafeHandle, int> someFunc)
{
InnerCallbackType innerFunc = (rawHandle, rawArg) =>
{
someFunc(new MySafeHandle(rawHandle, true), rawArg);
};
var funcPtr = Marshal.GetFunctionPointerForDelegate(innerFunc);
NativeFunction.setCallback(funcPtr);
}
Таким образом, вы все равно сохраните свою "безопасность типов" по отношению к использованию SafeHandle, позволяя вам обрабатывать сортировку так, как вы хотите...
Ответ 3 · рейтинг 1 · Eric Liu
Возможно с некоторыми дополнительными шагами превратить IntPtr в MySafeHandleбез прокси-передачи делегата в другого с помощью ICustomMarshaler.
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] // depends on unmanaged implementation, but for the example sake
delegate void CallBackType([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyCustomMarshaler))] MySafeHandle instance, int argument);
и, наконец, реализация ICustomMarshaler:
public sealed class MyCustomMarshaler : ICustomMarshaler
{
private static MyCustomMarshaler _instance = new MyCustomMarshaler();
public void CleanUpManagedData(object o)
{
}
public void CleanUpNativeData(IntPtr ptr)
{
}
public int GetNativeDataSize()
{
return IntPtr.Size;
}
public IntPtr MarshalManagedToNative(object o)
{
return IntPtr.Zero;
}
public object MarshalNativeToManaged(IntPtr ptr)
{
return new MySafeHandle()
{
handle = ptr
};
}
public static ICustomMarshaler GetInstance(string s)
{
return _instance;
}
}
Я опубликовал ответ, потому что ничего не нашел об этом в Интернете (вне зависимости от того, безопасно это или нет, или почему маршалинг не автоматически превращает IntPtr в SafeHandle).