【问题标题】:C# cross thread UI operation from C DLL callback来自 C DLL 回调的 C# 跨线程 UI 操作
【发布时间】:2016-01-18 04:45:09
【问题描述】:

我有 C 风格的 Dll,我必须在 C# 中使用它。我为该库创建了一个 Pinvoke 包装器。一切正常,但其中一个 C 函数接受函数指针(回调)。因此,在我的 C# 包装器中,它是一个 static 类,我添加了以下代码:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CallbackType(uint status);

/* To prevent garbage collection of delegate, need to keep a reference */
static CallbackType privCallback;

[DllImport(DLL_FILE_NAME, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
 public static extern int SetCallback(uint handle, CallbackType callback);

public static int SetMyCallback(uint handle, CallbackType callback)
{
    return SetCallback(handle, callback);
}

现在,在我的 C# Form 类中,

private void RefreshCallback(uint status)
{
    this.statusLabel.Text = "Status Code: "+ status.toString();
}

private void myBtn_Click(object sender, EventArgs e)
{
    int status, handle = 0;
    ...
    status = MyDllWrapper.SetMyCallback(handle, RefreshCallback);
    ...
}

我的回调被调用的那一刻,我得到一个错误:

Cross-thread operation not valid

我希望回调函数在 UI 线程中运行。为了实现这一点,我应该在包装器中进行哪些更改。

我想让 Form 类保持干净,并且包装类易于最终用户使用。因此,如果需要复杂的代码来处理这种情况,我想在包装器中处理它,以便最终用户可以轻松使用我的 dll。

另外,我的静态包装器中的静态委托实例是否会阻止它进行垃圾回收。

【问题讨论】:

  • 您需要与托管回调完全相同的代码才能访问主线程 Invoke/InvokeRequired 上的 UI(您显然知道) - 请说明为什么这还不够/您正在寻找什么。
  • @AlexeiLevenkov 包装内的一些事件委托系统,函数可以订阅
  • 我仍然不明白这是如何特定于本机互操作的。 RefreshCallback 如果在非 UI 线程上调用,无论是从本机代码还是托管代码调用,都会遇到完全相同的问题。您可以强制回调以 Ben Voigt suggested 的身份在 UI 线程上运行,但这会大大限制编写回调的灵活性(因为几乎每个人都已经知道如何处理在非 UI 线程上调用代码的情况 - stackoverflow.com/questions/5868783/…))
  • @AlexeiLevenkov 我有 Linux 背景(C/C++),没有太多使用 C#。不知道是不是太明显了。

标签: c# c++ c multithreading pinvoke


【解决方案1】:

您可以(大多数情况下)检测回调是否要发送到 GUI 代码,并从与 UI 代码关联的线程中自动执行。

public static int SetMyCallback(uint handle, CallbackType callback)
{
    var callbackB = callback;
    var syncTarget = callback.Target as System.Windows.Forms.Control;
    if (syncTarget != null)
        callbackB = (v) => syncTarget.Invoke(callback, new object[] { v });
    return SetCallback(handle, callbackB);
}

另一种选择是基于用于调用SetMyCallback 的线程创建SynchronizationContext,然后使用该上下文进行调用。

【讨论】:

  • 我尝试了您的解决方案,但是当调用回调时,程序崩溃并出现错误:System.Windows.Forms.dll 中发生了“System.StackOverflowException”类型的未处理异常
  • @adnankamili:是的,我没有注意到变量被提升到闭包中,并调用它自己而不是原来的callback 参数。这应该可以解决这个问题。
  • 谢谢!有效。我不明白为什么它失败了,你能解释一下吗? callbackB 的更直观的名称是什么?您可以(大部分)检测回调是否会转到 GUI 代码?这不是在所有情况下都有效吗?
  • @adnankamili:匿名委托在UI线程使用Invoke调用回调时,查找变量callback,发现,不是传入的参数,是callback在创建委托时持有,但在调用发生时 callback 的值已被匿名委托本身覆盖。
  • @adnankamili: callbackB 被引入以避免覆盖callback 的原始值。最后,callbackB 是指向回调的“安全”链接,如果它不指向 UI 对象则直接,或者通过 Invoke 包装器。也许你可以把它命名为wrappedCallback
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
相关资源
最近更新 更多