【问题标题】:Problem with C callback function in C# - how to pass value to a pointer?C#中C回调函数的问题-如何将值传递给指针?
【发布时间】:2009-08-06 20:04:27
【问题描述】:

我有一个 C 回调定义如下:

Int16 (CALLBACK *ProcessMessage)(Uint16 ServerId,
   const char PTR *RequestMsg, Uint32 RequestSize,
   char PTR **ResponseMsg, Uint32 PTR *ResponseSize,
   Int16 PTR *AppErrCode);

在 C 中使用此回调的示例:

Int16 CALLBACK ProcessMessage(Uint16 ServerId, const char PTR *RequestMsg, Uint32 RequestSize, char PTR **ResponseMsg, Uint32 PTR *ResponseSize, Int16 PTR *AppErrCode) { printf("ProcessMessage() -> ServerId=%u\n", ServerId); //**** 设置 RESPONSEMSG 的值(指针),这就是我需要在 C# 中做的事情 **** sprintf(resp,"(%05lu) 回复测试", ServerId); *ResponseMsg = resp; printf("ProcessMessage() -> atribuido %p(p) a *ResponseMsg\n", *ResponseMsg); *ResponseSize = strlen(*ResponseMsg); *AppErrCode = -1; 返回 SS_OK; }

然后我用 C# 实现了这个回调:

 [DllImport("Custom.dll", SetLastError = true)]
    static extern Int16 SS_Initialize(
        UInt16[] ServerIds,
        UInt16 ServerQty,
        [MarshalAs(UnmanagedType.LPStr)] string Binding,
        [MarshalAs(UnmanagedType.LPStr)] string LogPath,
        UInt16 LogDays,
        Int16 LogLevel,
        UInt16 MaxThreads,
        UInt16 MaxConThread,
        ProcessMessageCallback callback);  

回调定义:

public delegate Int16 ProcessMessageCallback(
        UInt16 ServerId,
        [MarshalAs(UnmanagedType.LPStr)] string RequestMsg,
        UInt32 RequestSize,
        [MarshalAs(UnmanagedType.LPStr)] ref string ResponseMsg,
        ref UInt32 ResponseSize,
        ref Int16 AppErrCode);

设置回调的方法:

public void Call_SS_Initialize(
        UInt16[] serverIds,
        string binding,
        string logPath,
        UInt16 logDays,
        Int16 logLevel,
        UInt16 maxThreads,
        UInt16 maxConThread
        )
    {
        Int16 ret;
        try
        {
            pmc = new ProcessMessageCallback(ProcessMessage);

            ret = SS_Initialize(
                serverIds,
                Convert.ToUInt16(serverIds.ToList().Count),
                binding,
                logPath,
                logDays,
                logLevel,
                maxThreads,
                maxConThread,
                pmc);
        }
    }

最后是回调方法,问题出在哪里:

public Int16 ProcessMessage(
      UInt16 ServerId,
      string RequestMsg,
      UInt32 RequestSize,
      ref string ResponseMsg,
      ref UInt32 ResponseSize,
      ref Int16 AppErrCode)
    {
       //Implement return to ResponseMsg POINTER
    }

问题是,ResponseMsg实际上是C中的POINTER。所以在C#方法ProcesMessage中,我必须设置为ResponseMsg DLL 将从中获取字符串的内存空间(指针)。

我不能简单地设置 ResponseMsg = "REPLY",因为当方法完成时,字符串已经被销毁的内存。

我该怎么做?欢迎任何建议!

谢谢!

【问题讨论】:

  • 乍一看,这看起来不错。当您在 C# 处理程序中设置 ResponseMsg 时,您会在 C 端返回什么?调试错误?垃圾?什么都没有?
  • 我认为在那里使用 ref 会给他双重间接 - 即他实际上并没有在那里传递 char*,而是 char**
  • 啊,对不起,错过了它确实是需要的事实!
  • Ben,字符串似乎被垃圾处理了,因为 C 程序在 ResponseMsg 变量上一无所获。 Pavel,我对 C 编程几乎一无所知……你能解释得更好吗?
  • 仔细看:PTR的定义是什么? ResponseMsg 被定义为char PTR ** .. 那是char *** 吗?因为那行不通。 :-)

标签: c# c memory callback pointers


【解决方案1】:

这是我为重新创建它所做的。也许我的实验会有所帮助。

C# 代码:

public delegate void ProcessMessageCallback(
        [MarshalAs(UnmanagedType.LPStr)] ref string ResponseMsg);

static class test
{
    [DllImport("test.dll")]
    static extern void TestCallback(ProcessMessageCallback callback);

    static public void Main(string[] args)
    {
        TestCallback(MyCallback);
    }

    static void MyCallback(ref string ResponseMsg)
    {
        ResponseMsg = "hi there";
    }
}

C 代码(在 DLL 中):

#include <windows.h>
#include "objbase.h"

__declspec(dllexport) void TestCallback(
    void (* CALLBACK managedCallback(char **)))
{
    char *test = NULL;
    managedCallback(&test);
    printf(test);
    CoTaskMemFree(test); // NB!
}

这会成功打印出“hi there”。

注意:CoTaskMemFree 应该用于释放 P/Invoke 层分配的内存。如果您希望返回的字符串比调用 C# 回调的方法更长,请考虑在释放返回的内存之前将其复制到另一个位置。

【讨论】:

    【解决方案2】:

    这使 P/Invoke 感到困惑的原因是它隐含的不寻常的内存管理语义。您有效地将一个以未知方式分配的字符串返回给调用者,然后调用者不释放它(至少据我所知)。这是相当有问题的设计(不是线程安全的,也可能不是可重入的),而且绝对不是典型的。

    您实际上无法对字符串执行任何操作,因为 C 代码实际上并没有直接接收指向您的字符串数据的指针。由于 C# 字符串是 Unicode,并且您请求将字符串编组为 ANSI,因此将制作并返回一个“副本”(带有 Unicode 到 ANSI 转换的结果)。我不知道它的生命周期,也不知道如何控制它,而且我在文档中看不到任何保证。

    所以,看起来你最好的办法是自己管理这个,使用Marshal.AllocHGlobal 分配缓冲区(可能只为所有调用分配一次,与你的 C 代码一样),Encoding.GetBytes 将字符串转换为字节数组, 和 Marshal.Copy 将生成的字节复制到分配的缓冲区中。

    【讨论】:

    • P/Invoke 层的内存分配是相当有据可查的。在这种情况下,它使用默认的 OLE 任务内存分配器——因此应该使用 CoTaskMemFree 在 C 端释放它。
    • Pavel,感谢您的 cmets。但我从未与你所说的这些人合作过(AllocHGlobal、Marshal.Copy 等)。你能帮我把它们放在一起吗?发送!!!
    • Ben,出于好奇,您能否提供一个描述该内容的链接?无论如何,我怀疑 C 代码是否会转到 CoTaskMemFree 它,而且它似乎就是它的样子(即无法更正)。
    • 就是这样,C代码来自第三方公司。 @Pavel,你能给我一个使用 Marshal.AllocHGlobal 和 Copy 的例子吗?谢谢!!
    • 这是一篇关于这个主题的不错的文章。 msdn.microsoft.com/en-us/magazine/cc164193.aspx
    【解决方案3】:

    尝试将 ResponseMsg 的类型更改为 StringBuilder 并确保容量足以存储响应。

    【讨论】:

      【解决方案4】:

      C# 代码:

      public delegate void ProcessMessageCallback(StringBuilder ResponseMsg);
      
      static class test
      {
          [DllImport("test.dll")]
          static extern void TestCallback(ProcessMessageCallback callback);
      
          static public void Main(string[] args)
          {
              TestCallback(MyCallback);
          }
      
          static void MyCallback(StringBuilder ResponseMsg)
          {
              ResponseMsg.Append("hi there");
          }
      }
      

      C 代码(在 DLL 中):

      typedef int(__stdcall * Callback)(char *message);
      
      __declspec(dllexport) void TestCallback(Callback callback) 
      {
          char* test = (char*)malloc(10000);
          test[0] = '\0';
          callback(test);
          printf(test);
          free(test);
      }
      

      【讨论】:

        【解决方案5】:

        怎么样:

        IntPtr p = Marshal.GetFunctionPointerForDelegate(pmc);
        

        【讨论】:

        • R Ubben,我应该在代码中的哪个位置使用它,以及如何使用它??
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-04-25
        • 2019-03-12
        • 2017-08-30
        • 2011-05-19
        • 1970-01-01
        相关资源
        最近更新 更多