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!
Try changing the type of ResponseMsg to a StringBuilder and make sure the capacity is enough to store the response.
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, andMarshal.Copy
to copy the resulting bytes into the allocated buffer.How about :
Here's what I did to recreate this. Maybe my experiment will help.
The C# code:
The C code (in a DLL):
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.
The C# code:
The C code (in a DLL):