Problem with C callback function in C# - how to pa

2019-08-27 12:41发布

I've a C callback defined as follows:

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

An exemple of using this callback in 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);

    //**** SET THE VALUE FOR RESPONSEMSG (POINTER), THAT'S WHAT I NEED TO DO IN C# ****       
    sprintf(resp,"(%05lu) REPLY TEST", ServerId);
    *ResponseMsg = resp;

    printf("ProcessMessage() -> atribuido %p(p) a *ResponseMsg\n", *ResponseMsg);
    *ResponseSize = strlen(*ResponseMsg);
    *AppErrCode = -1; 
    return SS_OK;
}

Then I've this callback implemented in 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);  

Callback definition:

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);

Method that sets the callback:

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);
        }
    }

And finally the Callback method, where THE PROBLEM IS:

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

The problem is, ResponseMsg is actually a POINTER in C. So in the C# method ProcesMessage, I've to set to ResponseMsg a space in memory (pointer) from where the DLL will get the string from.

I can't simply set ResponseMsg = "REPLY", because when the method finishes the memory where the string was is already destroyed.

How can I do that?? Please any advise is welcome!!

Thanks!

5条回答
淡お忘
2楼-- · 2019-08-27 13:19

Try changing the type of ResponseMsg to a StringBuilder and make sure the capacity is enough to store the response.

查看更多
何必那么认真
3楼-- · 2019-08-27 13:24

The reason why this confuses P/Invoke is unusual memory management semantics implied by this. You effectively return a string, allocated by unknown means, to the caller who then doesn't free it (at least so far as I can see). This is rather problematic design (not thread-safe, and possibly not reentrant), and definitely not typical.

There isn't really anything you can do here with the string, because the C code doesn't actually receive a pointer directly to your string data. Since C# strings are Unicode, and you requested the string to be marshalled as ANSI, a "copy" (with result of Unicode-to-ANSI conversion) will be made and returned. I've no idea about the lifetime of this, nor how to control this, and I don't see any guarantees in the documentation.

So, looks like your best bet is to manage this yourself, using Marshal.AllocHGlobal to allocate the buffer (probably just once for all calls, the same as your C code does), Encoding.GetBytes to convert strings to byte arrays, and Marshal.Copy to copy the resulting bytes into the allocated buffer.

查看更多
forever°为你锁心
4楼-- · 2019-08-27 13:26

How about :

IntPtr p = Marshal.GetFunctionPointerForDelegate(pmc);
查看更多
三岁会撩人
5楼-- · 2019-08-27 13:38

Here's what I did to recreate this. Maybe my experiment will help.

The C# code:

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";
    }
}

The C code (in a 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!
}

This successfully prints out "hi there".

Note: CoTaskMemFree should be used to free memory allocated by the P/Invoke layer. If you want to keep the returned string around longer than the method that calls your C# callback, consider copying it into another location before freeing the returned memory.

查看更多
迷人小祖宗
6楼-- · 2019-08-27 13:38

The C# code:

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");
    }
}

The C code (in a 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);
}
查看更多
登录 后发表回答