2012-12-13 36 views
0

我想直接写入/读取多字节数组到/从文件,并建议使用PInvoke WriteFile/ReadFile。C#得到正确的IntPtr指向已经声明的变量

基本上我阅读的代码看起来像现在这样:

[DllImport("kernel32.dll", SetLastError = true)] 
static extern unsafe int ReadFile(IntPtr handle, IntPtr bytes, uint numBytesToRead, 
    IntPtr numBytesRead, System.Threading.NativeOverlapped* overlapped); 

..<cut>.. 

byte[,,] mb = new byte[1024,1024,1024]; 
fixed(byte * fb = mb) 
{ 
    FileStream fs = new FileStream(@"E:\SHARED\TEMP", FileMode.Open); 
    int bytesread = 0; 
    ReadFile(fs.SafeFileHandle.DangerousGetHandle(), (IntPtr)fb, Convert.ToUInt32(mb.Length), new IntPtr(bytesread), null); 
    fs.Close(); 
} 

此代码引发AccessViolationException。 然而,下面的代码不会:

[DllImport("kernel32.dll", SetLastError = true)] 
static extern unsafe int ReadFile(IntPtr handle, IntPtr bytes, uint numBytesToRead, 
    ref int numBytesRead, System.Threading.NativeOverlapped* overlapped); 

..<cut>.. 

byte[,,] mb = new byte[1024,1024,1024]; 
fixed(byte * fb = mb) 
{ 
    FileStream fs = new FileStream(@"E:\SHARED\TEMP", FileMode.Open); 
    int bytesread = 0; 
    ReadFile(fs.SafeFileHandle.DangerousGetHandle(), (IntPtr)fb, Convert.ToUInt32(mb.Length), ref bytesread, null); 
    fs.Close(); 
} 

不同的是,我宣布numBytesRead是裁判INT而不是IntPtr的。

然而,无处不在那里我找到了问题的答案“如何获得的IntPtr为int”,它是这样:

int x = 0; 
IntPtr ptrtox = new IntPtr(x) 

那么,我究竟做错了什么?为什么访问违规?

回答

2

你会得到一个访问冲突的原因是新的IntPtr(X)创建了一个指针,其地址为x的内容。因此您在x = 0时创建了一个NULL指针。

IntPtr构造函数未获取其参数的地址。它不等同于C/C++中的&运算符。

您希望使用ref参数来读取字节;这是正确的方法。此外,您总是希望使用GCHandle来获取管理对象的地址,因此请在您的MB阵列上使用它,而不是固定的。不要长时间保持手柄,不要忘记释放它。

-reilly。

+0

我正在使用IntPtr读取字节,因为我想读入多维数组。任何多维数组。这样,我可以为所有情况制作一个简单的虚拟包装器,并且将byte [,]或byte [,,]或其他任何内容传递给相同的函数,并且它将工作,因为“fixed(byte * fixedByteArray = buffer)”不关心缓冲区有多少维度。但是,如果我宣布参考字节[,]我不能通过字节[,,]例如 – Istrebitel

+0

你误会了我。我不主张使用ref byte [];我主张为numBytesRead使用ref int。我告诉你摆脱阵列上的固定,并在读取操作期间使用GCHandle将其固定在内存中。 – Reilly

+0

他为什么要摆脱固定?固定更适合本地化使用(只要您可以使用不安全的上下文)。 GCHandle更适合在固定块不能使用的情况下持久使用。 –

0

我认为访问冲突是因为bytesread被管理,因此GC可能会移动它,使得您传递的指针无效。

以下工作?

int bytesread = 0; 
var pin = GCHandle.Alloc(bytesread, GCHandleType.Pinned) 
ReadFile(fs.SafeFileHandle.DangerousGetHandle(), (IntPtr)fb, Convert.ToUInt32(mb.Length), pin.AddrOfPinnedObject(), null); 

[编辑] 我忘了下一行:

pin.Free(); 

[双编辑] 哦,亲爱的!我完全没有理解棒的错误结局。我所说的更适用于处理安全代码中来自堆的托管数据。

@plinth是完全正确的,代码:

int x = 0; 
IntPtr ptrtox = new IntPtr(x) 

创建指针与x的值,而不是指向X。在你原来的代码只是通过:

new IntPtr(&bytesread) 

(IntPtr)(&bytesread) 
+0

Semms工作。所以,我必须这样做的一切,我想作为IntPtr传递到pinvoke方法?我必须以某种方式释放“引脚”?我可以直接传递GCHandle.Alloc(myVarName,GCHandleType.Pinned)AddrOfPinnedObject()以避免额外的代码行吗? – Istrebitel

+0

如果我是你,我会坚持使用ref声明,函数是一样的。但是,我忘了说你需要解锁!所以你不能在一行中插入,打电话,取消固定。 –

+0

好吧,但一般来说,我如何制作指向某个东西的指针,而不用像这样分配它?只有通过“固定”关键字? – Istrebitel

0

这很简单。看到你正在做的这件小事:

new IntPtr(bytesread) 

这并不是你所想的那样。你认为它会产生一个指向你的变量字节数的新指针。它没有。它会生成一个指针,指向 bytesread,它是0.非托管代码读取,然后尝试向空指针指向的内存中写入一个数字,该数字失败。

另一个版本的工作原理是因为参数被声明为ref int,这将使得编组器将实际的指针传递给bytesread而不是值。

+0

好吧,那么指向bytesread的正确方法是使用Peter Wishart提出的“pin”构造? – Istrebitel

0

如果您处于不安全的环境中,您可以使用C或C++中的相同方式获取指向blittable类型(如int)的指针。在你的案例& bytesread。这就是说,对于简单的指针参数,您应该始终使用ref或out关键字。

+0

嗯。我尝试将字节读作为参数传递给它,期望我有IntPtr,并且它抱怨无法将int转换为IntPtr。我做错了吗(我明白我应该使用ref,我会在最终产品中,但我也想学习如何做到这一点) – Istrebitel

+0

当你使用地址运算符时,你会得到一个与变量类型相同的指针。在这种情况下,你有一个int *,所以你要么改变你的声明来获取一个int *或者将它转换为IntPtr(即(IntPtr)&bytesread)或者创建新的IntPtr(&bytesread)。 –