加速C#原生调用pinvoke

Speed up C# native call pinvoke

mod 注意:我不认为这是重复的,因为我已经尝试了下面描述的几种提到的解决方案。

有什么办法可以加快速度吗?我已经在这方面遵循了 Microsoft 指南,这是我所做的:

  1. 添加了 SuppressUnmanagedCodeSecurity
  2. 将其放在名为 UnsafeNativeMethods.cs
  3. 的文件中
  4. 为方法存根定义特定类型

方法如下:

        [DllImport("kernel32.dll")]
        [SuppressUnmanagedCodeSecurity]
        public static extern bool DeviceIoControl(
            IntPtr hDevice,
            uint IoControlCode,
            [In] MemoryManager.MemOperation InBuffer,
            int nInBufferSize,
            [Out] byte[] OutBuffer,
            uint nOutBufferSize,
            ref int pBytesReturned,
            IntPtr Overlapped
        );

这是 MemOperation 的内容(我猜是必须编组的输入缓冲区):

        public struct MemOperation
        {
            public int Pid;
            public int UserPid;
            public int Size;
            public int protection_mode;
            public int allocation_type;
            public IntPtr Addr;
            public IntPtr WriteBuffer;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string module_selection;
        }

这是一个用法示例:

        public UnsafeNativeMethods.MEMORY_BASIC_INFORMATION QueryVirtualMemory(IntPtr address) {
            var memOperation = new MemOperation();
            byte[] buffer = new byte[48]; // 8 + 8 + 4 + 8 + 4 + 4 + 4 MEMORY_BASIC_INFORMATION
            memOperation.Pid = this.Pid;
            memOperation.Addr = address;
            int bytes = 0;
            bool res = UnsafeNativeMethods.DeviceIoControl(this.Handle, CtlCode(0x00000022, this.IOCTL_QUERY, 2, 0), memOperation, Marshal.SizeOf(memOperation), buffer, (uint)buffer.Length, ref bytes, IntPtr.Zero);
            return GetStructure<UnsafeNativeMethods.MEMORY_BASIC_INFORMATION>(buffer);
        }

在探查器中,我的热路径是 pinvoke。我的应用程序运行得非常快,我认为它可以在 C# 中运行。然而,由于应用程序正在执行多少内存操作,几乎所有执行时间的三分之一都花在了调用上。我想以任何可能的方式缩短这段时间,包括不安全的方式。

我看到您可以改为编写 DeviceIoControl 包装器并从 C++ dll 导入它,但这对我来说没有任何改变,它的功能似乎完全相同。这是来源:

devicecontrol.cpp

#include <iostream>
#include "DeviceControl.h"

bool __cdecl DeviceIoRequestWrapper(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlappedk)
{
    return DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlappedk);
}

devicecontrol.h

#pragma once

#include <Windows.h>

extern "C" {
    __declspec(dllexport) bool __cdecl DeviceIoRequestWrapper(
        HANDLE       hDevice,
        DWORD        dwIoControlCode,
        LPVOID       lpInBuffer,
        DWORD        nInBufferSize,
        LPVOID       lpOutBuffer,
        DWORD        nOutBufferSize,
        LPDWORD      lpBytesReturned,
        LPOVERLAPPED lpOverlappedk);
}

我正在使用 .net 6.0 和最新版本的 C#。


建议更改输出缓冲区:

        public unsafe UnsafeNativeMethods.MEMORY_BASIC_INFORMATION QueryVirtualMemory(IntPtr address) {
            var memOperation = new MemOperation();
            byte* buffer = stackalloc byte[48];
            memOperation.Pid = this.Pid;
            memOperation.Addr = address;
            int bytes = 0;
            bool res = UnsafeNativeMethods.DeviceIoControl(this.Handle, CtlCode(0x00000022, this.IOCTL_QUERY, 2, 0), memOperation, Marshal.SizeOf(memOperation), (IntPtr)buffer, 48, ref bytes, IntPtr.Zero);
            return GetStructure<UnsafeNativeMethods.MEMORY_BASIC_INFORMATION>(buffer);
        }

GetStructure 现在在哪里:

        public static unsafe T GetStructure<T>(byte* bytes) where T: unmanaged {
            T structure = *(T*)bytes;
            return structure;
        }

以下是最有帮助的内容,我会在几个小时后接受它作为我自己的主题的答案。

除了我提到的上述步骤外,我现在还做了以下工作:

  1. 已将 MemOperation 转换为完全 blittable。字符串“module_selection”在每次程序启动时只使用过一次,所以我现在添加了一个完全 blittable 版本的 MemOperation。谢谢@Flydog57
  2. 将缓冲区更改为快速分配(非清零),并传递 PTR,而不是分配然后将字节 [] 编组到 void/char*(我认为这可能发生在你传递给它一个 byte[] 对象)。谢谢@Charlieface

运行 visual studio 中的分析器,它似乎已经消除了热路径。现在总 CPU % 正确位于 kernel32.dll 内(对设备的 deviceiocontrol 调用)。这是否会带来切实的性能优势,我不知道,没有为此设置完整基准的简单方法。