我从您的代码中猜测是您正尝试使用 this technique 之类的东西手动将 DLL 或 EXE 加载到内存中 - 对吗?我将在最后解决这个问题(双关语),但首先快速解释为什么 VirtualAllocEx 失败。
为什么VirtualAllocEx 会出现这个错误?
在特定地址分配内存的问题在于,该地址需要有足够的空间来分配您请求的内存大小。这就是为什么一般来说,当你请求内存时,你让操作系统决定把它放在哪里。 (另外,让 OS / malloc 库决定可以带来其他好处,例如减少碎片等 - 超出了此答案的范围。)
您遇到的问题不是 VirtualAllocEx 无法分配 64MB 而不是 4MB。 VirtualAllocEx 可以(几乎)分配尽可能多的内存。问题是在您指定的地址处,在您的进程中,没有 64MB 的未分配内存。
考虑假设的地址 0-15 (0x0 - 0xF),其中- 标记为空内存,x 标记分配的内存:
0 1 2 3 4 5 6 7 8 9 A B C D E F
x x - x - - - - x - - - - - - -
这是您的进程的内存空间。现在,您想在地址 0x4 分配 4 个字节。简单 - 0x4 到 0x7 是免费的,因此您可以分配并获取(新分配标有 X):
0 1 2 3 4 5 6 7 8 9 A B C D E F
x x - x X X X X x - - - - - - -
太棒了。但是现在假设您想要分配 6 个字节。地址 0x4 没有六个空闲字节:0x8 处有一些内存正在使用:
0 1 2 3 4 5 6 7 8 9 A B C D E F
x x - x - - - - x - - - - - - -
1 2 3 4 bang!
你做不到。问题不在于内存分配器无法处理分配 6 个字节的问题,而是内存没有空闲供它分配。最有可能的是,它也不能改变内存——在普通的非 GC 程序中,你不能移动内存来腾出空间,因为你可能会留下不知道内存内容的悬空指针。指向已更改地址。唯一要做的就是要么失败,根本不分配内存,要么分配有空闲空间的地方,比如 0x9 或 0xA。
您可能想知道为什么VirtualAllocEx 会以ERROR_INVALID_ADDRESS 而不是NULL 失败:很可能是因为您指定了无法分配的地址;因此,即使该地址(可能)有 一些 空闲内存,但内存不足且地址无效。 the documentation 中暗示了这一点:
尝试通过指定 MEM_COMMIT 来提交特定地址范围
没有 MEM_RESERVE 和非 NULL lpAddress 失败,除非整个
范围已被保留。产生的错误代码是
ERROR_INVALID_ADDRESS。
这不是你的情况:你同时指定了两个标志,但如果方法不能保留,那么它实际上会陷入这种情况。它不能保留整个范围在那个地址,所以它给出错误代码ERROR_INVALID_ADDRESS。
加载 DLL 或 EXE 图像
那么,您应该如何处理您的问题,我从您的问题猜测并且代码正在将 DLL 或 EXE 图像加载到内存中?
在这里,您需要一些关于 EXE 文件中图像位置的背景知识。通常,EXE 会在进程的虚拟地址位置 0x400000 处加载到内存中。它是可选的:你的链接器可以要求它放在任何地方,但是this value is common。同样,DLL 有一个共同的默认位置:0x10000000。所以,对于一个 EXE 和一个 DLL,你没问题:图像加载器几乎可以肯定地在它们请求的位置加载它们。
当您有两个 DLL,都要求位于 0x10000000 时会发生什么?
答案是图像变基。图像位置是可选的,不是必需的。依赖于在特定地址加载的图像中的代码可以由图像加载器调整,因此第二个 DLL 可能不是在 0x10000000 加载,而是在其他地方加载 - 例如 0x1080000。这是 0x80000 的地址差异,因此加载程序实际上修补了 DLL 中的一堆地址和代码,因此所有认为它们应该引用 0x10000000 的位现在都引用了 0x10800000。
这非常非常普遍,每次加载 EXE 时都会对多个 DLL 执行此操作。这是很常见的,微软有一个叫做rebase的小优化工具,(用于“rebase”,即调整基地址),当你用它分发你的EXE和你自己的DLL时,你可以用它来确保每个 DLL 都有一个不同的基地址,每个基地址都位于这样,当 Windows 加载您的 EXE 和 DLL 时,它们已经有了正确的地址,并且不太可能必须对它们中的任何一个进行 rebase(执行上述操作)。对于某些应用程序,这可以显着缩短启动时间。 (在现代版本的 Windows 中,有时 DLL 无论如何都会移动 - 这是针对 address space layout randomization 的一种安全技术,可故意确保代码在每次运行时不在同一个地址。)
(另一件事是一些 DLL 和 EXE 压缩工具会删除用于此重定位的数据。这很好,因为它使 EXE 更小......直到它需要重新定位,并且因为数据丢失它不能,因此根本无法加载。或者你可以build with a fixed base, and it will magically work right until it doesn't.不要对你的 EXE 或 DLL 这样做。)
那么,当您尝试手动将 DLL 加载到内存中,并且在它要求加载的地址处没有足够的空间供它使用时,您应该怎么做?简单——这不是致命错误,只需将其加载到其他地方,然后自己执行变基。我建议如果您有问题可以提出一个新的 SO 问题,但是为了给您一个起点,您可以使用 RebaseImage 函数,或者如果您不能使用它或想自己做,我找到了 this code which from a quick overview seems to perform this manually .不保证其正确性。
TLDR
您的进程地址空间在您指定的地址处没有 64MB 的空白空间,因此您不能在那里分配 64MB 的内存。相反,将其分配到其他地方并修补/重新设置加载的 DLL 或 EXE 映像以匹配新地址。