首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >具有非导出函数的DllImport。为什么起作用了?

具有非导出函数的DllImport。为什么起作用了?
EN

Stack Overflow用户
提问于 2021-03-12 08:22:11
回答 1查看 540关注 0票数 7

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

代码语言:javascript
运行
复制
[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或类似的-异常将被抛出。但在内核出口表中,我们可以看到:

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

代码语言:javascript
运行
复制
#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属性这里的正式文档,我们可以阅读下面的内容:

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

但在这种情况下,kernel32.dll不能满足ZeroMemory的入口点。怎么一回事??救命求你了。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-03-13 07:19:53

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

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

作为一个FYI,.NET框架中有一些特殊的大小写P/Invoke名称:MoveMemoryCopyMemoryFillMemoryZeroMemory。当指向kernel32 on .NET框架时,所有这些都可以工作,但在.NET核心上将失败。请使用EntryPoint属性中的DllImport属性来获得所需的行为。可以使用Visual命令提示符中的dumpbin /exports kernel32.dll找到正确的导出名称。

以上建议为声明添加一个显式EntryPoint,以便在框架和核心中都工作,例如:

代码语言:javascript
运行
复制
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
public static extern void ZeroMemory(IntPtr address, IntPtr count);

神奇的名称重新映射发生在开源.NET代码之外,但是可以在西蒙·穆里尔在注释中提供的反汇编中清楚地看到。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66596448

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档