跨平台C代码并防止垃圾收集

Rob*_*Rob 2 c# delegates cross-platform

我有一组C函数,我需要在ARM目标,C++和C#中使用.我可以成功地将C包装成C++ DLL,然后成为C#DLL并使用我已成功绑定的所有C函数.但是,我有一个调试功能,我希望能够打印到C#GUI,它使用的委托是垃圾收集而不是留在原地.

托管调试助手'CallbackOnCollectedDelegate'在'C:\ utm\pc\utm_win32_app\bin\Debug\utm_win32_app.vshost.exe'中检测到问题.

附加信息:对类型为'utm_dll_wrapper_cs!MessageCodec.MessageCodec_dll + guiPrintToConsoleCallback :: Invoke'的垃圾回收委托进行了回调.这可能会导致应用程序崩溃,损坏和数据丢失.将委托传递给非托管代码时,托管应用程序必须将它们保持活动状态,直到确保它们永远不会被调用.

以下是使用和设置回调的C代码片段mp_guiPrintToConsole:

#ifdef WIN32
static void (* mp_guiPrintToConsole) (const char*) = NULL;

void logMsg (const char * pFormat, ...)
{
    char buffer[MAX_DEBUG_MESSAGE_LEN];

    va_list args;
    va_start (args, pFormat);
    vsnprintf (buffer, sizeof (buffer), pFormat, args);
    va_end (args);
#ifdef WIN32
    if (mp_guiPrintToConsole)
    {
        (*mp_guiPrintToConsole) (buffer);
    }
#else
    // Must be on ARM
    printf (buffer);
#endif
}

void  initDll (void (*guiPrintToConsole) (const char *))
{
#ifdef WIN32
    mp_guiPrintToConsole = guiPrintToConsole; 
    // This is the signal to the GUI that we're done with initialisation
    logMsg ("ready.\r\n");
#endif
}
Run Code Online (Sandbox Code Playgroud)

这是C++代码,它与C代码一起构建在DLL中,可以从C#调用并传入函数指针printToConsole:

void msInitDll (void (*printToConsole) (const char *))
{
    initDll (printToConsole);
}
Run Code Online (Sandbox Code Playgroud)

这是来自C#DLL的代码片段,它调用msInitDll(),传入guiPrintToConsole()和定义委托onConsoleTrace,我猜这是消失的东西:

[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
public delegate void _msInitDll([MarshalAs (UnmanagedType.FunctionPtr)] guiPrintToConsoleCallback callbackPointer);
public _msInitDll msInitDll;

public delegate void ConsoleTrace(string data);
public event ConsoleTrace onConsoleTrace;

public void guiPrintToConsole(StringBuilder data)
{
    if (onConsoleTrace != null)
    {
        onConsoleTrace (data.ToString ());
    }
}

public void bindDll(string dllLocation)
{
    IntPtr ptrDll = LoadLibrary (dllLocation);

    if (ptrDll == IntPtr.Zero) throw new Exception (String.Format ("Cannot find {0}", dllLocation));

    //...
    // All the other DLL function bindings are here
    //...

    msInitDll = (_msInitDll)bindItem(ptrDll, "msInitDll", typeof(_msInitDll));
    msInitDll(guiPrintToConsole);
}
Run Code Online (Sandbox Code Playgroud)

我在这里看了各种答案,最有希望的似乎是在C#代码中创建一个静态变量:

static GCHandle gch;

...然后用它来引用onConsoleTraceC#bindDll()函数:

gch = GCHandle.Alloc(onConsoleTrace);

但是,这对我没有任何好处.我尝试了一些其他尝试静态的尝试,但似乎没有什么能让我想到的地方.任何人都可以建议另一种方法来解决问题吗?我有一个我需要修复的错误,缺少任何调试都证明非常烦人.

Luc*_*ski 5

以下行使用一些语法糖:

msInitDll(guiPrintToConsole);
Run Code Online (Sandbox Code Playgroud)

完整语法是:

msInitDll(new guiPrintToConsoleCallback(guiPrintToConsole));
Run Code Online (Sandbox Code Playgroud)

希望你现在明白为什么代表可以收集垃圾.

一个简单的解决方法:

var callback = new guiPrintToConsoleCallback(guiPrintToConsole);
msInitDll(callback);
// ... some other code
GC.KeepAlive(callback);
Run Code Online (Sandbox Code Playgroud)

现在委托保证是活GC.KeepAlive呼叫.

但你很可能需要代表才能保持活力更长时间.正如错误消息所示,只需保留对它的引用即可.如果您需要完整的C#应用​​程序生命周期,请将callback本地语言转换为类中的静态字段.静态字段被视为GC根,因为它们的值始终可以访问.