具有自定义结构的PInvoke访问冲突异常

时间:2014-02-02 18:29:34

标签: c# c++ pinvoke access-violation

我正在尝试使用P / Invoke来填充POD结构。 C ++中的POD结构如下所示:

struct GraphicsAdapterDesc {
    const wchar_t* AdapterName;
    int32_t AdapterIndex;
    const wchar_t* HardwareHash;

    int64_t DedicatedVMEM;
    int64_t DedicatedSMEM;
    int64_t SharedSMEM;

    int32_t NumOutputs;
};

我试着要明确指定所有字段的宽度。 C#中的“镜像”结构定义如下:

[StructLayout(LayoutKind.Sequential)]
public struct GraphicsAdapterDesc {
    public WCharStringPtr AdapterName;
    public int AdapterIndex;
    public WCharStringPtr HardwareHash;

    public long DedicatedVMEM;
    public long DedicatedSMEM;
    public long SharedSMEM;

    public int NumOutputs;
};

WCharStringPtr看起来像这样:

public struct WCharStringPtr {
    internal IntPtr charArrayPtr;

    private string asString;
    public string AsString {
        get {
            return asString ?? (asString = Marshal.PtrToStringUni(charArrayPtr));
        }
    }

    public static implicit operator string(WCharStringPtr operand) {
        return operand.AsString;
    }

    public override string ToString() {
        return AsString;
    }
}

我有一个在C ++中定义的方法:

extern "C" __declspec(dllexport) bool GetGraphicsAdapter(int32_t adapterIndex, GraphicsAdapterDesc& outAdapterDesc) {
        outAdapterDesc = RENDER_COMPONENT.GetGraphicsAdapter(adapterIndex);
    return true;
}

P / Invoke extern方法声明如下:

[DllImport(InteropUtils.RUNTIME_DLL, EntryPoint = "GetGraphicsAdapter", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool _GetGraphicsAdapter(int adapterIndex, out GraphicsAdapterDesc adapterDesc);

每当我调用_GetGraphicsAdapter时,都会收到访问冲突错误(不是AccessViolationException)。当我从外部C ++方法中破解程序时,一切似乎都是格式良好的;但是一旦从该方法返回,就会发生访问冲突。所以,我猜测一旦方法存在就会清理一些内存,但我看不清楚是什么,或者为什么。

然后,我是P / Invoke的新手,也许这与我处理字符串有关;我不确定我所做的事情是否正确。

提前谢谢。

1 个答案:

答案 0 :(得分:2)

您的C#struct WCharStringPtr包含两个数据成员。这些都被编组为指针。另一方面,您将其映射到的C ++字段的类型为wchar_t*,它只是一个指针。这是一个明显的不匹配。

不清楚的是如何处理这些字符串的内存分配。调用者是分配内存还是被调用者?

假设被调用者分配内存。在这种情况下,GraphicsAdapterDesc结构应该声明如下:

public struct GraphicsAdapterDesc {
    public IntPtr AdapterName;
    public int AdapterIndex;
    public IntPtr HardwareHash;

    public long DedicatedVMEM;
    public long DedicatedSMEM;
    public long SharedSMEM;

    public int NumOutputs;
};

一旦函数返回,您就会调用Marshal.PtrToStringUni()AdapterNameHardwareHash转换为字符串。至于解除分配,这超出了问题的范围。

这似乎是最合理的选择。似乎很清楚,由于缓冲区的长度未在接口中传递,因此不能期望被调用者为两个字符串分配内存。当然,字符串被声明为const wchar_t*,这是进一步的证据。