跨平台 C 代码和防止垃圾回收
Cross-platform C code and preventing garbage collection
我有一组 C 函数需要在 ARM 目标、C++ 和 C# 中使用。我可以成功地将 C 封装到一个 C++ DLL 中,然后再封装到一个 C# DLL 中,并使用我已成功绑定的所有 C 函数。但是,我有一个调试功能,我希望能够打印到 C# GUI 并且它使用的委托正在被垃圾收集,而不是在持续时间内留在原地。
Managed Debugging Assistant 'CallbackOnCollectedDelegate' has detected a
problem in 'C:\utm\pc\utm_win32_app\bin\Debug\utm_win32_app.vshost.exe'.
Additional Information: A callback was made on a garbage collected delegate of
type
'utm_dll_wrapper_cs!MessageCodec.MessageCodec_dll+guiPrintToConsoleCallback::
Invoke'. This may cause application crashes, corruption and data loss. When
passing delegates to unmanaged code, they must be kept alive by the managed
application until it is guaranteed that they will never be called.
这是使用和设置回调的 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
}
这是 C++ 代码,与 C 代码一起内置到 DLL 中,可以从 C# 调用并传入函数指针 printToConsole
:
void msInitDll (void (*printToConsole) (const char *))
{
initDll (printToConsole);
}
这是来自调用 msInitDll()
、传入 guiPrintToConsole()
并定义委托 onConsoleTrace
的 C# DLL 的代码片段:
[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);
}
我看了这里的各种答案,最有希望的似乎是在 C# 代码中创建一个静态变量:
static GCHandle gch;
...然后使用它在 C# bindDll()
函数中引用 onConsoleTrace
:
gch = GCHandle.Alloc(onConsoleTrace);
然而,这对我没有任何好处。我已经尝试过其他一些尝试将事物声明为静态,但似乎没有什么能让我到达我想去的地方。谁能建议另一种解决问题的方法?我有一个需要修复的错误,但事实证明缺少任何调试非常烦人。
罗布
下一行使用了一些语法糖:
msInitDll(guiPrintToConsole);
完整语法为:
msInitDll(new guiPrintToConsoleCallback(guiPrintToConsole));
希望你现在明白为什么委托可以被垃圾回收了。
一个简单的解决方法:
var callback = new guiPrintToConsoleCallback(guiPrintToConsole);
msInitDll(callback);
// ... some other code
GC.KeepAlive(callback);
现在可以保证委托 在 GC.KeepAlive
调用前 。
但您很可能需要委托人存活更长时间。正如错误消息所说,只需保留对它的引用。如果您在整个 C# 应用程序生命周期内都需要它,请将本地 callback
转换为 class 中的静态字段。静态字段被视为 GC 根,因为它们的值始终可以访问。
答案是,在 C# DLL 代码中,添加静态变量:
public static guiPrintToConsoleCallback debugCallback;
...然后,在 C# bindDLL()
中,更改:
msInitDll(guiPrintToConsole);
...到
debugCallback = new guiPrintToConsoleCallback(guiPrintToConsole);
msInitDll(debugCallback);
知道怎么做就很简单。
我有一组 C 函数需要在 ARM 目标、C++ 和 C# 中使用。我可以成功地将 C 封装到一个 C++ DLL 中,然后再封装到一个 C# DLL 中,并使用我已成功绑定的所有 C 函数。但是,我有一个调试功能,我希望能够打印到 C# GUI 并且它使用的委托正在被垃圾收集,而不是在持续时间内留在原地。
Managed Debugging Assistant 'CallbackOnCollectedDelegate' has detected a problem in 'C:\utm\pc\utm_win32_app\bin\Debug\utm_win32_app.vshost.exe'.
Additional Information: A callback was made on a garbage collected delegate of type 'utm_dll_wrapper_cs!MessageCodec.MessageCodec_dll+guiPrintToConsoleCallback:: Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.
这是使用和设置回调的 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
}
这是 C++ 代码,与 C 代码一起内置到 DLL 中,可以从 C# 调用并传入函数指针 printToConsole
:
void msInitDll (void (*printToConsole) (const char *))
{
initDll (printToConsole);
}
这是来自调用 msInitDll()
、传入 guiPrintToConsole()
并定义委托 onConsoleTrace
的 C# DLL 的代码片段:
[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);
}
我看了这里的各种答案,最有希望的似乎是在 C# 代码中创建一个静态变量:
static GCHandle gch;
...然后使用它在 C# bindDll()
函数中引用 onConsoleTrace
:
gch = GCHandle.Alloc(onConsoleTrace);
然而,这对我没有任何好处。我已经尝试过其他一些尝试将事物声明为静态,但似乎没有什么能让我到达我想去的地方。谁能建议另一种解决问题的方法?我有一个需要修复的错误,但事实证明缺少任何调试非常烦人。
罗布
下一行使用了一些语法糖:
msInitDll(guiPrintToConsole);
完整语法为:
msInitDll(new guiPrintToConsoleCallback(guiPrintToConsole));
希望你现在明白为什么委托可以被垃圾回收了。
一个简单的解决方法:
var callback = new guiPrintToConsoleCallback(guiPrintToConsole);
msInitDll(callback);
// ... some other code
GC.KeepAlive(callback);
现在可以保证委托 在 GC.KeepAlive
调用前 。
但您很可能需要委托人存活更长时间。正如错误消息所说,只需保留对它的引用。如果您在整个 C# 应用程序生命周期内都需要它,请将本地 callback
转换为 class 中的静态字段。静态字段被视为 GC 根,因为它们的值始终可以访问。
答案是,在 C# DLL 代码中,添加静态变量:
public static guiPrintToConsoleCallback debugCallback;
...然后,在 C# bindDLL()
中,更改:
msInitDll(guiPrintToConsole);
...到
debugCallback = new guiPrintToConsoleCallback(guiPrintToConsole);
msInitDll(debugCallback);
知道怎么做就很简单。