带有非导出函数的 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.
}

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

There is NO ZeroMemory!!!

根本没有 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

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

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

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

解决方法

OP 是正确的,因为 kernel32.dll 没有导出 ZeroMemory 导出,但是 C# DllImport 以某种方式成功地神奇地将 ZeroMemory 引用解析为正确的 {{1 }} 在面向框架的 .NET 应用程序中导出(但在核心中不是)。

事实证明,少数记录为内联/宏(RtlZeroMemoryMoveMemory CopyMemoryFillMemory)的 Win32 API 由框架代码专门检查并在内部重新路由到正确的出口。虽然没有正式记录,但这已在 MS 批准的 comment 下的 .NET Runtime 问题中得到承认。

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

以上建议为声明添加显式 dumpbin /exports kernel32.dll 以在 Framework 和 Core 中工作,例如:

EntryPoint

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

enter image description here