Reputation: 459
I'm trying to do some basic C#\C interop which returns strange results. These are definitions on the C side :
typedef struct _RESULT {
BOOL success;
char *err;
} RESULT;
typedef struct _INPUT_DATA {
char *message;
} INPUT_DATA;
API int execute_out(IN INPUT_DATA *input, OUT RESULT *result);
With simple implementation:
API int execute_out(INPUT_DATA *input, RESULT *result){
result = (RESULT*)malloc(sizeof RESULT);
result->err = (char*)malloc(sizeof 128);
strcpy(result->err, "Result");
result->success = TRUE;
return 0;
}
Now, my C# definitions are as follows :
[StructLayout(LayoutKind.Sequential)]
public struct INPUT_DATA
{
[MarshalAs(UnmanagedType.LPStr)]
public string message;
}
[StructLayout(LayoutKind.Sequential)]
public struct RESULT
{
public bool success;
public IntPtr err;
}
[DllImport("unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int execute_out(INPUT_DATA input, out RESULT result);
When I setup code like this on the managed side :
Unmanaged.INPUT_DATA input = new Unmanaged.INPUT_DATA();
input.message = "Test";
Unmanaged.RESULT result;
Unmanaged.execute_out(input, out result);
I receive empty data for err variable defined in RESULT struct and also my success flag is not set correctly (yields false). Can anyone tell me what I'm missing here?
Also, what would be the best practice similar to this case :
Should caller (managed code) allocate memory for the RESULT struct and later free it, or should be there another call to free the allocated memory on the unmanaged side?
Upvotes: 0
Views: 1083
Reputation: 459
Based on the suggestions from @HansPassant and @Olaf, this is the actual code that works for me :
API int execute_out(INPUT_DATA *input, RESULT *result){
//result = (RESULT*)malloc(sizeof RESULT); //this malloc is redundat which effectively replaces original pointer
result->err = (char*)malloc(sizeof 128);
strcpy(result->err, "Result");
result->success = TRUE;
return 0;
}
Malloc is commented, as it replaces original pointer passed from the managed code.
On the managed side, functions need to be declared like this :
[DllImport("unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int execute_out(ref INPUT_DATA input, [Out] out RESULT result);
Notice the ref keyword to properly pass INPUT_DATA pointer and [Out] attribute before RESULT pointer to tell the marshaller we need something back.
In the end, complete call from managed side looks like this :
Unmanaged.INPUT_DATA input = new Unmanaged.INPUT_DATA();
input.message = "Test";
Unmanaged.RESULT result;
Unmanaged.execute_out(ref input, out result);
string error = Marshal.PtrToStringAnsi(result.err);
Hope someone will find this useful.
Upvotes: 1