C# получает правильный IntPtr, указывающий на уже объявленную переменную
Я пытаюсь записать / прочитать многобайтовый массив непосредственно в / из файла, и было предложено использовать PInvoke WriteFile/ReadFile.
В основном мой код чтения теперь выглядит так:
[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 как ref int, а не IntPtr.
Однако везде, где я нахожу ответ на вопрос "как получить IntPtr для int", он выглядит так:
int x = 0;
IntPtr ptrtox = new IntPtr(x)
Итак, что я делаю не так? Почему нарушение доступа?
4 ответа
Причина, по которой вы получаете нарушение прав доступа, заключается в том, что новый IntPtr(x) создает указатель, адрес которого является содержимым x. поэтому вы создали нулевой указатель, когда x = 0.
Конструктор IntPtr не получает адрес своего аргумента. Это не эквивалентно оператору & в C/C++.
Вы хотите использовать аргумент ref для прочитанных байтов; это правильный путь. Кроме того, вы всегда хотите использовать GCHandle для получения адреса управляемого объекта, поэтому используйте его в своем массиве mb, а не в фиксированном. Просто не держите ручку надолго и не забудьте освободить ее.
-reilly.
Если вы находитесь в небезопасном контексте, вы можете получить указатель на blittable тип, такой как int, так же, как в C или C++. В вашем случае &bytesread. Тем не менее, для простых параметров указателя вы всегда должны использовать ключевые слова ref или out.
Это легко. Посмотрите на этот маленький кусочек, который вы делаете:
new IntPtr(bytesread)
Это не делает то, что вы думаете, что делает. Вы думаете, что он создает новый указатель, который указывает на вашу переменную bytesread. Это не так. Он создает указатель, который указывает на адрес со значением bytesread, которое равно 0. Неуправляемый код читает, затем пытается записать число в память, на которую указывает нулевой указатель, что не удается.
Другая версия работает, потому что аргумент объявлен как ref int
что заставит маршаллера передать фактический указатель на bytesread вместо значения.
Я думаю, что нарушение доступа связано с тем, что 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)