views:

1269

answers:

4

Hi,

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!

A: 

How about :

IntPtr p = Marshal.GetFunctionPointerForDelegate(pmc);
R Ubben
R Ubben, in what point in the code should I used it, and how??
Rodrigo
A: 

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.

Pavel Minaev
Memory allocation by the P/Invoke layer is fairly well-documented. In this case, it uses the default OLE task memory allocator--so CoTaskMemFree should be used to free it on the C side.
Ben M
Pavel, thanks for your comments. But I never worked with these guys you're talking about (AllocHGlobal, Marshal.Copy etc). Could you please help me to put it all togheter?? Txs!!!
Rodrigo
Ben, out of curiosity, can you give a link where it describes that? In any case, I doubt the C code is going to `CoTaskMemFree` it, and it seems that it is what it is (i.e. cannot be corrected).
Pavel Minaev
That's it, the C code is from a third-part company. @Pavel, can you give me an example of using Marshal.AllocHGlobal and Copy, please??? Thank you!!
Rodrigo
Here's a decent article on the subject. http://msdn.microsoft.com/en-us/magazine/cc164193.aspx
Ben M
A: 

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.

Ben M
A: 

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

hjb417