C# 来自C的本机调用尝试读取无效内存

C# 来自C的本机调用尝试读取无效内存,c#,c,marshalling,C#,C,Marshalling,我正在从托管代码中调用本机代码,并且在弄清楚如何正确封送我的代码时遇到了一些麻烦。在C中,我有以下内容: struct cHandle { unsigned long handleLo; unsigned long handleHi; } struct cBuffer { unsigned long bufferSize; unsigned long bufferType; __field_bcount(bufferSize) void *buffe

我正在从托管代码中调用本机代码,并且在弄清楚如何正确封送我的代码时遇到了一些麻烦。在C中,我有以下内容:

struct cHandle {
     unsigned long handleLo;
     unsigned long handleHi;
}

struct cBuffer {
    unsigned long bufferSize;
    unsigned long bufferType;
    __field_bcount(bufferSize) void *bufferPtr;
}

struct cBufferDesc {
    unsigned long bufferVersion;
    unsigned long bufferCount;
    _field_ecount(bufferCount) cBuffer *buffers;
 }

uint __stdcall CMethod(
    __in_opt cHandle* handle1,
    __in_opt cHandle* handle2,
    __in_opt wchar_t* wstr,
    __in unsigned long long1,
    __in unsigned long resevered1, // Reserved, always 0
    __in unsigned long long2,
    __in_opt cBufferDesc* inputBufferPtr, 
    __in unsigned long reserved2, // Reserved, always 0
    __inout_opt cHandle* outHandle,
    __inout_opt cBufferDesc* outputBufferPtr,
    __out unsigned long * outLong,
    __out_opt TimeStampStruct* timeStamp);
CMethod的行为如下所示。outputBufferPtr将始终输出一个值。如果inputBufferPtr为NULL,handle2也应为NULL,CMethod应遵循不同的逻辑以给出初始输出缓冲区,如果不是,CMethod应根据输入缓冲区中的数据计算输出缓冲区。我第一次打电话上班时遇到了麻烦。此外,我不关心时间戳,因此我不会详细说明该结构,也不会制作C等价物。我在C中尝试了以下编组:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Handle {
    private IntPtr HandleLo;
    private IntPtr HandleHi;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Buffer {
    public uint Size; // Possibly unknown
    public uint Type; // Always set.
    public IntPtr Buffer; // Possibly unknown
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BufferDesc {
    public uint Count; // Always 1 for my purposes
    public uint Version; // Always set

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]        
    public Buffer[] BufferArray; // Will always be a size 1 array.
}

// Used for calling when we have an existing input buffer
[DllImport("mylib.dll", ExactSpelling = "true", CharSet = CharSet.Unicode, SetLastError = true)]
uint CMethod(
    [In] ref Handle handle1,
    [In] ref Handle handle2,
    [In] IntPtr wstr,
    [In] uint long1, // C# uint == C ulong
    [In] uint reserved1,
    [In] uint long2,
    [In] ref BufferDesc inputBufferPtr,
    [In] uint reserved2,
    [In, Out] ref Handle outHandle,
    [In, Out] ref BufferDesc outputBufferPtr,
    [Out] out IntPtr outLong,
    [Out] out IntPtr timestamp);

// Used for calling when we do not have an existing input buffer
// Here IntPtr.Zero will be passed in for handle2 and inputBufferPtr
[DllImport("mylib.dll", ExactSpelling = "true", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint CMethod(
    [In] ref Handle handle1,
    [In] IntPtr handle2,
    [In] IntPtr wstr,
    [In] uint long1, // C# uint == C ulong
    [In] uint reserved1,
    [In] uint long2,
    [In] IntPtr inputBufferPtr,
    [In] uint reserved2,
    [In, Out] ref Handle outHandle,
    [In, Out] ref BufferDesc outputBufferPtr,
    [Out] out IntPtr outLong,
    [Out] out IntPtr timestamp);

public static void WrapperMethod(
    ref Handle handle1,
    ref Handle handle2, 
    string wstr,
    byte[] inputBuffer,
    ref Handle outHandle,
    out byte[] outputBuffer)
 {
     BufferDesc inputBufferDesc;
     BufferDesc outputBufferDesc;

     outputBufferDesc.Count = 1;
     outputBufferDesc.Version = 0; // Real data not shown
     outputBufferDesc.BufferArray = new Buffer[0];

     outputBufferDesc.BufferArray[0].Count = 0;
     outputBufferDesc.BufferArray[0].Type = 2; // Real data not shown
     outputBufferDesc.BufferArray[0].Buffer = IntPtr.Zero;        

     IntPtr wstrPtr = Marshal.StringToCoTaskMemUni(wstr);

     IntPtr ignoredOutLong;
     IntPtr ignoredTimestamp;

     if (null != inputBuffer)
     {
         inputBufferDesc.Count = 1;
         inputBufferDesc.Version = 0; // Real data not shown
         inputBufferDesc.BufferArray = new Buffer[1];

         inputBufferDesc.BufferArray[0].Size = inputBuffer.Length;
         inputBufferDesc.BufferArray[0].Type = 2; // Real data not shown
         inputBufferDesc.BufferArray[0].Buffer = GCHandle.Alloc(inputBuffer, GCHandleType.Pinned).AddrOfPinnedObject();

         CMethod(
             ref handle1, 
             ref handle2,
             wstrPtr,
             0, // Real data not shown
             0,
             0, // Real data not shown
             ref inputBufferDesc,
             0,
             ref outHandle,
             ref outputBufferDesc,
             out ignoreOutLong,
             out ignoreTimestamp);
     }
     else
     {   ///////////////////////////////////////////////////////////////////////
         // This is the call I am taking and also where the code is crashing. //
         CMethod(                                                             //
             ref handle1,                                                     // 
             IntPtr.Zero,                                                     //
             wstrPtr,                                                         //
             0, // Real data not shown                                        //
             0,                                                               //
             0, // Real data not shown                                        //
             IntPtr.Zero,                                                     //
             0,                                                               //
             ref outHandle,                                                   //
             ref outputBufferDesc,                                            //
             out ignoreOutLong,                                               //
             out ignoreTimestamp);                                            //
         ///////////////////////////////////////////////////////////////////////                                                     
     }

     // Do Cleanup. Not reached at this point.

 }

我得到的错误是,我试图访问读或写受保护的内存。如果您能看到我的编组方式有任何明显错误,或者我锁定错误,或者只是没有锁定我应该在的位置,或者如果您能看到任何其他问题,请告诉我。

问题在于我的输出缓冲区。我没有将大小为1的空数组分配给outputBufferDesc.Buffers,而本机代码试图写入未为此目的分配的内存。我也无法将其作为byvalue数组封送。相反,我的结构如下所示:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct BufferDesc
{
    public uint Version;
    public uint Count;
    public IntPtr Buffers;
}

我锁定一个大小为1的空SecurityBuffer数组,并将地址提供给缓冲区。

是否有可能尝试在64位操作系统上使用32位dll,并将visual studio中的目标平台设置为任何CPU?如果是这样,intptr的大小将是8,而不是4,并且将失败。但是您应该会遇到一个错误,即无法在64位进程中加载32位dll。不,所讨论的dll是windows dll,因此它们的32位和64位名称相同,但我可以使用以前的本机调用调用相同的dll,所以我一定是编错了什么。事实上,我确信这一定是缓冲区或BufferDesc结构有问题,因为我以前正确地使用了句柄,自动正确地字符串marhsal,我看不出uint或IntPtr是如何导致问题的。