C# Interop:从带有 out 参数的非托管代码返回结构会产生奇怪的结果

C# Interop : Returning struct from unmanaged code with out parameter gives strange results

我正在尝试做一些基本的 C#\C 互操作,结果 returns 奇怪。这些是C端的定义:

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

实施简单:

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

现在,我的 C# 定义如下:

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

当我在托管端设置这样的代码时:

Unmanaged.INPUT_DATA input = new Unmanaged.INPUT_DATA();
input.message = "Test";

Unmanaged.RESULT result;

Unmanaged.execute_out(input, out result);

我收到 RESULT 结构中定义的 err 变量的空数据,而且我的成功标志设置不正确(结果为 false)。谁能告诉我我在这里缺少什么?
另外,与这种情况类似的最佳做法是什么:
调用者(托管代码)应该为 RESULT 结构分配内存然后释放它,还是应该有另一个调用来释放非托管端分配的内存?

根据@HansPassant 和@Olaf 的建议,这是适合我的实际代码:

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 已注释,因为它替换了从托管代码传递的原始指针。

在托管方面,函数需要这样声明:

[DllImport("unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int execute_out(ref INPUT_DATA input, [Out] out RESULT result);

注意 ref 关键字在 RESULT 指针之前正确传递 INPUT_DATA 指针和 [Out] 属性以告诉编组器我们需要一些回报。

最后,托管端的完整调用如下所示:

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

希望有人会觉得这有用。