I'm working on a high-performance app that makes a pInvoke dll call to call winapi DeviceIoControl. DeviceIoControl requires a byte buffer.
public byte[] Read(IntPtr address, int length) {
var info = new MemOperation();
var buf = new byte[length];
info.Pid = Pid;
if (address == H.pBaseCtxAddress) {
address = (IntPtr) ((ulong) address + (ulong) KeReadModuleBase());
}
info.Addr = address;
info.WriteBuffer = IntPtr.Zero;
info.Size = buf.Length;
var bytes = 0;
if (address != IntPtr.Zero && (long) address > 0 && (long) address != 0 &&
(long) address < 0x7FFFFFFFFFFF) {
var res = Win32.DeviceIoControl(Handle, CtlCode(0x00000022, IOCTL_READ_MEM, 2, 0), info,
Marshal.SizeOf(info), buf, (uint) length, ref bytes, IntPtr.Zero);
}
return buf;
}
When running Visual Studio 2019's profiler, it lists the byte array's declaration as a hotspot.
var buf = new byte[length];
I am not constrained to safe or managed code, so I am open for unsafe solutions for this. I have debugged the actual pInvoke call, and it's as performant as it can be. The profiler doesn't list it as a hotspot either, only the byte array allocation.
I assume this is happening due to how C# is creating the new byte array. It's my understanding C# zero's out every byte when allocating the array. Is there a way instead that I can simply select a block of free memory of n length? The Driver that this IOCTL is calling is always going to return the specified length, so having dirty memory (non-zero'd) shouldn't be an issue.
This is the DllImport I have for DeviceIoControl:
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[SuppressUnmanagedCodeSecurity]
public static extern bool DeviceIoControl(
IntPtr hDevice,
uint IoControlCode,
[MarshalAs(UnmanagedType.AsAny)][In] object InBuffer,
int nInBufferSize,
[MarshalAs(UnmanagedType.AsAny)][Out] object OutBuffer,
uint nOutBufferSize,
ref int pBytesReturned,
IntPtr Overlapped
);
Here's the full call-path for this (so you understand how I'm using the results):
Multiple threads are calling Read<T>
which is in turn calling the function Read
that contains the byte array allocation that's posted at the beginning of the thread. The profiler lists this allocation as a hotspot. The final GetStructure converts that byte array to a struct.
public T Read<T>(IntPtr address) where T: unmanaged {
var size = Marshal.SizeOf(typeof(T));
var data = Read(address, size);
return GetStructure<T>(data);
}
public unsafe static T GetStructure<T>(byte[] bytes) where T: unmanaged {
fixed (byte* p = bytes)
{
T structure = *(T*)p;
return structure;
}
}
User contributions licensed under CC BY-SA 3.0