Есть некоторые вещи, о которых вы должны знать.Во-первых, если вы вызываете делегат .NET из неуправляемого кода, то если вы не будете следовать некоторым довольно узким ограничениям, вам будет больно.
В идеале вы можете создать делегат в C # и передать его вУправляемый код, поместите его в указатель на функцию, держите его так долго, как хотите, затем вызывайте его без каких-либо побочных эффектов.В документации .NET говорится так.
Я могу вам сказать, что это просто неправда.В конце концов, часть вашего делегата или его thunk будет собирать мусор, и когда вы вызываете указатель на функцию из неуправляемого кода, вы отправляетесь в забвение.Мне все равно, что говорит Microsoft, я следовал их предписанию и буквально следил за тем, как указатели на функции превращались в мусор, особенно в части кода на стороне сервера.
Учитывая это, самый эффективный способ использованияТаким образом, указатели функций таковы:
- Код C # вызывает неуправляемый код, передавая делегат.
- Неуправляемый код выполняет маршализацию делегата до указателя функции.
- Неуправляемый код выполняет некоторые действияработа, возможно вызов указателя функции.
- Неуправляемый код удаляет все ссылки на указатель функции.
- Неуправляемый код возвращается к управляемому коду.
Учитывая это, предположим, чтоу нас есть следующее в C #:
public void PerformTrick(MyManagedDelegate delegate)
{
APIGlue.CallIntoUnamangedCode(delegate);
}
, а затем в управляемом C ++ (не C ++ / CLI ):
static CallIntoUnmanagedCode(MyManagedDelegate *delegate)
{
MyManagedDelegate __pin *pinnedDelegate = delegate;
SOME_CALLBACK_PTR p = Marshal::GetFunctionPointerForDelegate(pinnedDelegate);
CallDeepIntoUnmanagedCode(p); // this will call p
}
Я не делал этого в последнее времяв C ++ / CLI - синтаксис другой - я думаю, что в итоге он выглядит так:
// This is declared in a class
static CallIntoUnamangedCode(MyManagedDelegate ^delegate)
{
pin_ptr<MyManagedDelegate ^> pinnedDelegate = &delegate;
SOME_CALLBACK_PTR p = Marshal::GetFunctionPointerForDelegate(pinnedDelegate);
CallDeepIntoUnmanagedCode(p); // This will call p
}
Когда вы выходите из этой подпрограммы, пиннинг освобождается.
Когда вы действительно, действительнонужно иметь функциюДо того, как вызывать n указателей, которые я оставил на некоторое время, я сделал в C ++ / CLI следующее:
- Сделал хеш-таблицу, которая является картой из int -> Delegate.
- Сделал регистр/ unregister подпрограммы, которые добавляют новых делегатов в хеш-таблицу, увеличивая счетчик для хеша int.
- Создали одну статическую подпрограмму неуправляемого обратного вызова, которая регистрируется в неуправляемом коде с помощью int из вызова register.Когда вызывается эта подпрограмма, она возвращает обратно в управляемый код, говоря: «найдите делегата, связанного с , и вызовите его по этим аргументам».
Что происходит, так как у делегатов нет thunksкоторые делают переходы больше, так как они подразумеваются.Они могут свободно зависать в подвешенном состоянии, перемещаясь GC по мере необходимости.Когда им позвонят, делегат будет закреплен CLR и освобожден по мере необходимости.Я также видел сбой этого метода, особенно в случае кода, который статически регистрирует обратные вызовы в начале времени и ожидает, что они останутся до конца времени.Я видел этот сбой в коде ASP.NET, а также в коде на стороне сервера для Silverlight , работающего через WCF .Это немного нервирует, но способ исправить это состоит в том, чтобы реорганизовать ваш API, чтобы разрешить позднюю (r) привязку к вызовам функций.
Чтобы дать вам пример того, когда это произойдет - предположим, у вас есть библиотека, которая включаетфункция, подобная этой:
typedef void * (*f_AllocPtr) (size_t nBytes);
typedef void *t_AllocCookie;
extern void RegisterAllocFunction(f_AllocPtr allocPtr, t_AllocCookie cookie);
, и ожидается, что при вызове API, который выделяет память, он будет перенесен в предоставленный f_AllocPtr
.Хотите верьте, хотите нет, но вы можете написать это на C #.Это мило:
public IntPtr ManagedAllocMemory(long nBytes)
{
byte[] data = new byte[nBytes];
GCHandle dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
unsafe {
fixed (byte *b = &data[0]) {
dataPtr = new IntPtr(b);
RegisterPointerHandleAndArray(dataPtr, dataHandle, data);
return dataPtr;
}
}
}
RegisterPointerHandleAndArray заполняет триплет для безопасного хранения.Таким образом, когда вызывается соответствующее свободное место, вы можете сделать это:
public void ManagedFreeMemory(IntPtr dataPointer)
{
GCHandle dataHandle;
byte[] data;
if (TryUnregister(dataPointer, out dataHandle, out data)) {
dataHandle.Free();
// do anything with data? I dunno...
}
}
И, конечно, это глупо, потому что выделенная память теперь закреплена в куче GC и фрагментирует ее в ад - но дело в том,что это выполнимо.
Но опять же, я лично видел этот сбой, если действительные указатели недолговечны.Обычно это означает обертывание вашего API, чтобы при вызове подпрограммы, выполняющей определенную задачу, она регистрировала обратные вызовы, выполняла задачу, а затем извлекала обратные вызовы.