带有非导出函数的 DllImport。为什么有效?

Vas*_*sya 6 .net c# winapi marshalling dllimport

我刚刚在 C# 中遇到了 DllImport 的奇怪行为,我无法解释。我想知道它是如何可能的,以及我可以在哪里阅读它。案例是通过 DllImport 可以调用不真正导出表单 dll 的函数。就我而言,它是 kernel32.dll 和函数 ZeroMemory(但具有复制/移动/填充内存这样的行为)。所以,我的代码:

[DllImport("kernel32", EntryPoint = "LoadLibraryW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr LoadLibrary(string libName);

[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr module, string procName);

//WTF???
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ZeroMemory(IntPtr address, int size);

static void TestMemory()
{
    IntPtr mem = Marshal.AllocHGlobal(100); //Allocate memory block of 100 bytes size
    Marshal.WriteByte(mem, 55);            //Write some value in the first byte
    ZeroMemory(mem, 100);                   //Clearing block of memory
    byte firstByte = Marshal.ReadByte(mem); //Read the first byte of memory block
    Console.WriteLine(firstByte);           //Output 0 (not 55) - ZeroMemory is working

    //Getting address of ZeroMemory from kernel32.dll
    IntPtr kernelHandle = LoadLibrary("kernel32.dll");
    IntPtr address = GetProcAddress(kernelHandle, "ZeroMemory");
    Console.WriteLine(address.ToString("X"));   //Output 0 - Library kernel32.dll DOESN'T export function ZeroMemory!!!

    //Testing GetProcAddress via getting address of some exported function
    Console.WriteLine(GetProcAddress(kernelHandle, "AllocConsole").ToString("X"));  //Output some address value - all is OK.
}
Run Code Online (Sandbox Code Playgroud)

没有抛出 EntryPointNotFoundException - 代码工作正常。如果将 ZeroMemory 的名称更改为 ZeroMemory1 或类似的名称 - 将引发异常。但是在kernel32.dll的导出表中我们看到:

没有零内存!!!

根本没有 ZeroMemory 功能!如果我们查看msdn,我们会发现 ZeroMemory 只是 C++ 的 WinBase.h 头文件中的一个宏。在里面我们看到:

#define RtlMoveMemory memmove
#define RtlCopyMemory memcpy
#define RtlFillMemory(d,l,f) memset((d), (f), (l))
#define RtlZeroMemory(d,l) RtlFillMemory((d),(l),0)
#define MoveMemory RtlMoveMemory
#define CopyMemory RtlCopyMemory
#define FillMemory RtlFillMemory
#define ZeroMemory RtlZeroMemory
Run Code Online (Sandbox Code Playgroud)

显然,在 C++ ZeroMemory 中,它实际上是通过 ntdll.dll 中的 RtlFillMemory 工作的。但它是在 C++ 中的!!!为什么它在 C# 中工作?在此处的DllImport 属性的官方文档中,我们可以阅读以下内容:

作为最低要求,您必须提供包含入口点的 DLL 的名称

但在这种情况下,kernel32.dll 无法连接 ZeroMemory 的入口点。到底是怎么回事??请帮忙。

dxi*_*xiv 4

OP 是正确的,因为kernel32.dll没有导出ZeroMemory导出,但 C#DllImport却以某种方式成功地神奇地解析了针对框架(但不是核心)的 .NET 应用程序中ZeroMemory对正确导出的引用。RtlZeroMemory

事实证明,框架代码专门检查了一些记录为内联/宏 ( MoveMemoryCopyMemory FillMemory、 ) 的 Win32 API,并在内部重新路由到正确的导出。ZeroMemory虽然没有正式记录,但 MS 批准的.NET 运行时问题下的评论中承认了这一点。

仅供参考,.NET Framework 中有一些特殊大小写的 P/Invoke 名称:MoveMemoryCopyMemoryFillMemoryZeroMemory。当指向 .NET Framework 上的 kernel32 时,所有这些都将起作用,但在 .NET Core 上将失败。请使用属性EntryPoint中的属性DllImport来获得所需的行为。可以使用dumpbin /exports kernel32.dllVisual Studio 命令提示符找到正确的导出名称。

上面建议添加显式声明EntryPoint以在 Framework 和 Core 中工作,例如:

[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
public static extern void ZeroMemory(IntPtr address, IntPtr count);
Run Code Online (Sandbox Code Playgroud)

神奇的名称重新映射发生在开源 .NET 代码之外,但可以在Simon Mourier在评论中提供的反汇编中清楚地看到。

在此输入图像描述