Question

I am trying to write/read multibyte array directly to/from file, and was suggested using PInvoke WriteFile/ReadFile.

Basically my reading code looks like this now:

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

This code throws an AccessViolationException. However, the following code does not:

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

The difference is that I declare numBytesRead to be ref int rather than IntPtr.

However, everywhere where I find an answer to a question "how to get IntPtr to an int", it goes like:

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

So, what am I doing wrong? Why access violation?

Was it helpful?

Solution

The reason you get an Access Violation is because new IntPtr(x) creates a pointer whose address is the contents of x. so you have created a NULL pointer when x = 0.

The IntPtr constructor does not get the address of its argument. It isn't equivalent to the & operator in C/C++.

You want to use the ref argument for bytes read; that is the proper way. Furthermore, you always want to use a GCHandle to get the address of a managed object, so use it on your mb array, not fixed. Just don't keep the handle for long, and don't forget to free it.

-reilly.

OTHER TIPS

That's easy. See this little piece you're doing:

new IntPtr(bytesread)

That doesn't do what you think it does. You think that it makes a new pointer that points to your variable bytesread. It doesn't. It makes a pointer that points to an address with the value of bytesread, which is 0. The unmanaged code reads, then tries to write a number into the memory pointed to by the null pointer, which fails.

The other version works because the argument is declared as ref int which will make the marshaller pass an actual pointer to bytesread instead of the value.

If you are in an unsafe context you can get a pointer to a blittable type, such as int, the same way you can in C or C++. In your case &bytesread. That said for simple pointer parameters you should always use the ref or out keywords.

I think the access violation is because bytesread is managed, therefore the GC may move it, making the pointer you passed invalid.

Does the following work?

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

[edit] I forgot the next line:

pin.Free();

[double edit] Oh dear! I got the wrong end of the stick completely. What I'm saying applies more to handling managed data from the heap in safe code.

@plinth is exactly right, the code:

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

Creates a pointer with the value of x, not pointing to x. In your original code just pass:

new IntPtr(&bytesread)

or

(IntPtr)(&bytesread)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top