Как передать обратный вызов из приложения .NET Core в C DLL

#c# #c #.net-core #garbage-collection #interop

#c# #c #.net-core #сбор мусора #взаимодействие

Вопрос:

Я пытаюсь передать делегат из .NET core в качестве указателя на функцию в DLL с интерфейсом C.

Функция C принимает указатель на функцию. Это будет установлено как дескриптор функции обратного вызова внутри.

При попытке вызвать функцию обратного вызова из C DLL я получаю следующую ошибку.

«Фатальная ошибка. Система.AccessViolationException: попытка чтения или записи защищенной памяти. Это часто указывает на то, что другая память повреждена «.

Примечание: Исходный код DLL также можно редактировать. DLL и приложения .NET Core должны работать на разных платформах, отсюда и требование языка C вместо C .

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

1. Никто не знает, что вы на самом деле пытались и как. Вы должны поделиться своей попыткой.

2. Не уверен, как вы ожидаете, что мы сообщим вам о проблеме в вашем коде, если мы ее не видим?

Ответ №1:

Итак, проблема в том, что у вас есть что-то вроде этого:

 [DllImport("C.dll")]
public static extern int SetCallback(Action delegate);
public static extern int PerformAction();
public static extern int ClearCallback(Action delegate);

public void PerformSomeOperation()
{
    SetCallback(HandleEvent);
    for(int i = 0; i < 5; i  ) {
        PerformAction();
    }
    ClearCallback(HandleEvent);
}

private void HandlEvent()
{
}
 

Проблема в том, что делегат собрал мусор. Исправление этого заключается в том, чтобы сохранить делегат в рабочем состоянии:

 public void PerformSomeOperation()
{
    Action handler = (Action)HandleEvent;
    SetCallback(handler);
    for(int i = 0; i < 5; i  ) {
        PerformAction();
    }
    ClearCallback(handler);
}
 

Если область действия обработчика обратного вызова является глобальной, сохраните делегат в глобальной переменной и очистите его сразу после вызова ClearCallback .

Если область действия обработчика обратного вызова является глобальной и обработчик никогда не освобождается, сохраните делегат в статической переменной и никогда не очищайте его.

Если в структуре кода используется менеджер обратного вызова, выделенный из кучи, который по-прежнему не очищает обработчик, это все равно можно сделать. Нам просто нужно поддерживать делегат в рабочем состоянии, пока мы не закончим его использовать:

 [DllImport("C.dll")]
public static extern IntPtr SetupAction(Action delegate);
public static extern int PerformAction(IntPtr ctx);
public static extern int FreeAction(IntPtr ctx);

public void PerformSomeOperation()
{
    Action handler = HandleEvent();
    IntPtr ctx = IntPtr.Zero;
    try {
        ctx = SetupAction(handler);
        for(int i = 0; i < 5; i  ) {
            PerformAction();
        }
    finally {
        if (ctx != IntPtr.Zero) FreeAction(ctx);
        GC.KeepAlive(handler);
    };
}

private void HandlEvent()
{
}
 

Версия TL; DR:

Если обработчик является локальным для работы и имеет функцию clear, поместите делегат в переменную и используйте одну и ту же переменную как в set, так и в clear .

Если обработчик является локальным для работы и не имеет четкой функции, поместите делегат в переменную и используйте GC.KeepAlive, чтобы сохранить его активным до последнего места, где его можно использовать.

Если обработчик является глобальным и имеет функцию clear, сохраните делегат в статической переменной и очистите переменную делегата после вызова функции clear .

Если обработчик является глобальным и не имеет четкой функции, сохраните делегат в статической переменной и никогда не очищайте его.