从.NET向C代码传递回调,代码是否有效?

从.NET向C代码传递回调,代码是否有效?,.net,callback,pinvoke,marshalling,.net,Callback,Pinvoke,Marshalling,对于一个相当大的c API,我们有一个.NET(c#)包装器。在这个包装器中,用户可以提供从本机代码重复调用的回调 回调如下所示: [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)] public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle); 用户可以通过以下方式输入回调: public vo

对于一个相当大的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);
一个用户以以下方式调用回调例程(简化):

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

所以我的问题是,这是有效的吗

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

访问在回调范围外创建的类的实例(即“string temp”)的代码是否有效?我的意思是,我的猜测是编组需要将它们作为输入/输出参数来控制它。我考虑过使用handle参数进行尝试,但不确定这是否是过度使用

更新1:

也许这就是我需要的

更新2:

从本机代码ala调用回调:

      if( logcallback_ != NULL )
      {
        (logcallback_)(cstream_,chbuf_,logcallbackhandle_);
      }
更新3:

它给出了同样的崩溃

更新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使委托保持活动状态,这样它就不会被垃圾收集。

让我们看看您的委托:

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle);
首先,我不知道什么是
SlmStream
,所以我不能评论它是如何封送的。这当然是一个可能的失败向量

IntPtr
句柄不显示任何问题。可能在本机端是某种类型的指针,可能是
void*

代码最明显的问题是字符串参数,
wtsr
。封送拆收器假定您将向以null结尾的宽字符数组传递指针。用本机术语,即
wchar\u t*
。但是,封送员也负责销毁本机内存。它假设内存是从COM堆分配的,因此调用
CoTaskMemFree

我认为您的本机代码可能没有在COM堆上分配以null结尾的字符数组。这当然可以解释某些平台上的崩溃,但不能解释其他平台上的崩溃

以下是一些解决问题的方法:

  • 获取要从COM堆中分配的本机代码,并让托管代码释放它
  • 如果本机代码进行分配和解除分配,则将委托中的参数声明为
    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));  }
    


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

    让我们看看您的委托:

    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle);
    
    首先,我不知道什么是
    SlmStream
    ,所以我不能评论它是如何封送的。这当然是一个可能的失败向量

    IntPtr
    句柄不显示任何问题。可能在本机端是某种类型的指针,可能是
    void*

    代码最明显的问题是字符串参数,
    wtsr
    。封送拆收器假定您将向以null结尾的宽字符数组传递指针。用本机术语,即
    wchar\u t*
    。但是,封送员也负责销毁本机内存。它假设内存是从COM堆分配的,因此调用
    CoTaskMemFree

    我认为您的本机代码可能没有在COM堆上分配以null结尾的字符数组。这当然可以解释某些平台上的崩溃,但不能解释其他平台上的崩溃

    以下是一些解决问题的方法:

  • 获取要从COM堆中分配的本机代码,并让托管代码释放它
  • 如果本机代码进行分配和解除分配,则将委托中的参数声明为
    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));  }
    


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

    谢谢。我不知道封送员实际上也会释放“字符串wtsr”,这当然不是我想要的。这是一个WCHAR数组,但在本机中分配并在那里释放。顺便说一句:SlmStream只是一个简单的枚举,没有危险。谢谢,非常感谢!显示
    logcallback\的本机声明
    。此外,我看不到任何证据表明您更换了您的代表。你能不能用一个实函数而不是lambda来证明你真的做到了我的要求。更新了问题以包含声明。我不能改变lambda,直到在几个小时内,因为它的共同工作与另一个,谁现在不在这里。谢谢你的帮助,我稍后会回来的。谢谢。我不知道封送员实际上也会释放“字符串wtsr”,这当然不是我想要的。这是一个WCHAR数组,但在本机中分配并在那里释放。顺便说一句:SlmStream只是一个简单的枚举,没有危险。谢谢,非常感谢!显示
    logcallback\的本机声明
    。此外,我看不到任何证据表明您更换了您的代表。你能不能用一个实函数而不是lambda来证明你真的做到了我的要求。更新了问题以包含声明。我不能改变lambda,直到在几个小时内,因为它的共同工作与另一个,谁现在不在这里。谢谢你的帮助,我稍后会回来的。