1

我们有一个用于相当大的 C API 的 .NET (c#) 包装器。在这个包装器中,用户可以提供从本机代码重复调用的回调。

回调如下所示:

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle);

用户可以通过以下方式输入回调:

public void SetLoggingCallback(LogCallBack lcallback,
                               IntPtr      handle)
{
  SlmReturn ret = (SlmReturn)Native.SlmSetLoggingCallbackW(ModelPtr,
                                                           lcallback,
                                                           handle);

  if( ret != SlmReturn.SlmRetOk )
  {
    throw new SlmException(ret,ret.ToString());
  }
}

最终调用:

[DllImport("sulum20.dll",CallingConvention = CallingConvention.StdCall , CharSet = CharSet.Unicode)]
public static extern int SlmSetLoggingCallbackW(IntPtr      ModelPtr,
                                                LogCallBack lcallback,
                                                IntPtr      handle);

一位用户通过以下方式(简化)调用了回调例程:

string temp;
    SetLoggingCallback((str, wtsr, handle) => { temp = wtsr; Console.WriteLine(temp);  }, IntPtr.Zero);

这会导致应用程序在某些平台上崩溃,而在其他平台上则不会。

所以我的问题仍然存在,这有效吗?

来自 C/C++ 世界,一件事让我感到困惑:

访问在回调范围之外创建的类的实例是否有效代码(即“字符串临时”)?我的意思是我的猜测是编组需要将它们作为输入/输出参数才能控制它。我考虑过使用句柄参数自己尝试编组,但不确定这是否过大。

更新 1:

也许这就是我需要的 GCHandle

更新 2:

回调是从本机代码 ala 调用的:

      if( logcallback_ != NULL )
      {
        (logcallback_)(cstream_,chbuf_,logcallbackhandle_);
      }

更新 3:

  string temp;
               LogCallBack logCallback = (str, wtsr, handle) =>
                                             {
                                                 temp = "Hello";
                                             };
               smodel.SetLoggingCallback(logCallback, IntPtr.Zero);

它给出了同样的崩溃。

更新 4:

typedef void (ISLMCALL *SlmLogCallBackW)(enum SlmStream,const wchar_t*, void *handle);

更新 5

也尝试过但失败了:

 var logCallback = new LogCallBack(TargetMethod);
               smodel.SetLoggingCallback(logCallback, IntPtr.Zero);

    private string _test;

   private void TargetMethod(SlmStream str, IntPtr wtsr, IntPtr handle)
   {
       _test = "Hello";
   }

解决方案 :

使用 GCHandler 使委托保持活动状态,这样它就不会被垃圾收集。

4

1 回答 1

1

让我们看看你的代表:

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle);

首先,我不知道它是什么SlmStream,所以我无法评论它是如何编组的。这当然是一个可能的失败向量。

IntPtr手柄没有问题。大概在本机方面,它是某种指针,也许void*.

代码最明显的问题是字符串参数,wtsr. 编组器假定您将传递一个指向以空字符结尾的宽字符数组的指针。用本地术语来说就是wchar_t*。但是,编组器也负责销毁本机内存。它假定内存是从 COM 堆分配的,因此调用CoTaskMemFree

我认为您的本机代码可能没有在 COM 堆上分配空终止字符数组。这肯定会解释某些平台上的崩溃,但不能解释其他平台上的崩溃。

以下是解决问题的几种方法:

  1. 获取本机代码以从 COM 堆中分配,并让托管代码释放它。
  2. 如果本机代码分配和解除分配,则在您的委托中声明参数为并在您的委托中IntPtr调用Marshal.PtrToStringUni以转换为托管字符串。

您的评论告诉我选项 2 是正确的解决方案。您的代表应该是:

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate void LogCallBack(SlmStream str, IntPtr wtsr, IntPtr handle);

并像这样实现它:

(str, wtsr, handle) => { Console.WriteLine(Marshal.PtrToStringUni(wtsr));  }

您显然必须做的另一件事是确保委托保持活动状态,以便在本机代码调用它时它仍然存在。

于 2013-06-18T14:15:08.387 回答