我实现了this application的一些C#移植,允许从内存/流加载库,而不是使用通过文件系统工作的LoadLibrary API函数。 用指针和匹配结果弄乱了一点......最后我有一些按预期工作的东西。我唯一的问题是对DLLMain的调用总是失败(我尝试使用Kernel32.dll和User32.dll)。我无法理解为什么,我不知道如何调试这个问题。
这是我的项目(一个简单的32位控制台应用程序)的主要功能,它读取库,将其分配到内存并手动加载:
public static UInt32 Load(String libraryName)
{
if (libraries.ContainsKey(libraryName))
return libraries[libraryName];
String libraryPath = Path.Combine(Environment.SystemDirectory, libraryName);
Byte[] libraryBytes = File.ReadAllBytes(libraryPath);
fixed (Byte* libraryPointer = libraryBytes)
{
HeaderDOS* headerDOS = (HeaderDOS*)libraryPointer;
if ((UInt16)((headerDOS->Magic << 8) | (headerDOS->Magic >> 8)) != IMAGE_DOS_SIGNATURE)
return 0;
HeadersNT* headerNT = (HeadersNT*)(libraryPointer + headerDOS->LFANEW);
UInt32 addressLibrary = VirtualAlloc(headerNT->OptionalHeader.ImageBase, headerNT->OptionalHeader.SizeOfImage, AllocationType.RESERVE, MemoryProtection.READWRITE);
if (addressLibrary == 0)
addressLibrary = VirtualAlloc(0, headerNT->OptionalHeader.SizeOfImage, AllocationType.RESERVE, MemoryProtection.READWRITE);
if (addressLibrary == 0)
return 0;
Library* library = (Library*)Marshal.AllocHGlobal(sizeof(Library));
library->Address = (Byte*)addressLibrary;
library->ModulesCount = 0;
library->Modules = null;
library->Initialized = false;
VirtualAlloc(addressLibrary, headerNT->OptionalHeader.SizeOfImage, AllocationType.COMMIT, MemoryProtection.READWRITE);
UInt32 addressHeaders = VirtualAlloc(addressLibrary, headerNT->OptionalHeader.SizeOfHeaders, AllocationType.COMMIT, MemoryProtection.READWRITE);
MemoryCopy((Byte*)headerDOS, (Byte*)addressHeaders, (headerDOS->LFANEW + headerNT->OptionalHeader.SizeOfHeaders));
library->Headers = (HeadersNT*)((Byte*)addressHeaders + headerDOS->LFANEW);
library->Headers->OptionalHeader.ImageBase = addressLibrary;
CopySections(library, headerNT, libraryPointer);
UInt32 locationDelta = addressLibrary - headerNT->OptionalHeader.ImageBase;
if (locationDelta != 0)
PerformBaseRelocation(library, locationDelta);
UInt32 libraryHandle = (UInt32)library;
if (!BuildImportTable(library))
{
Free(libraryName);
return 0;
}
FinalizeSections(library);
if (library->Headers->OptionalHeader.AddressOfEntryPoint == 0)
{
Free(libraryName);
return 0;
}
UInt32 libraryEntryPoint = addressLibrary + library->Headers->OptionalHeader.AddressOfEntryPoint;
if (libraryEntryPoint == 0)
{
Free(libraryName);
return 0;
}
LibraryMain main = (LibraryMain)Marshal.GetDelegateForFunctionPointer(new IntPtr(libraryEntryPoint), typeof(LibraryMain));
UInt32 result = main(addressLibrary, DLL_PROCESS_ATTACH, 0);
if (result == 0)
{
Free(libraryName);
return 0;
}
library->Initialized = true;
libraries[libraryName] = libraryHandle;
return libraryHandle;
}
}
以下是如何使用它的示例:
private const Byte VK_Z_BREAK = 0x5A;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void KeyboardEventDelegate(Byte key, Byte scan, KeyboardFlags flags, Int32 extra);
[Flags]
private enum KeyboardFlags : uint
{
EXTENDEDKEY = 0x0001,
KEYUP = 0x0002,
}
public static void Main()
{
UInt32 libraryHandle = LibraryLoader.Load("User32.dll");
if (libraryHandle != 0)
{
UInt32 functionHandle = LibraryLoader.GetFunctionAddress("User32.dll", "keybd_event");
if (functionHandle != 0)
{
KeyboardEventDelegate s_KeyboardEvent = (KeyboardEventDelegate)Marshal.GetDelegateForFunctionPointer(new IntPtr(functionHandle), typeof(KeyboardEventDelegate));
while (true)
{
s_KeyboardEvent(VK_Z_BREAK, VK_Z_SCANCODE, 0, 0);
s_KeyboardEvent(VK_Z_BREAK, VK_Z_SCANCODE, KeyboardFlags.KEYUP, 0);
}
}
}
Console.ReadLine();
}
如果您想快速尝试,可以从this link下载项目。
[编辑]经过几次尝试,在调用DllMain之后使用Marshal.GetLastWin32Error(),我发现正在生成错误代码14,它对应于ERROR_OUTOFMEMORY。如果我在DllMain调用失败后继续,并且我获得库函数的地址,尝试使用委托调用它会产生PInvokeStackImbalance异常。关于这个的任何线索? ^ _ ^
答案 0 :(得分:9)
此代码只是Windows加载程序加载DLL所做的一阶近似。它只能用于最简单的DLL,从C到C#代码的转换也很可能导致像你正在处理的堆栈不平衡问题一样的麻烦。我看到的主要问题是:
它没有做任何事情来确保之前没有加载DLL。当您尝试加载kernel32.dll和user32.dll时,这几乎可以防止出现问题,这些DLL在托管代码开始执行之前已经加载。他们不会好心地再次装满。
它没有做任何明显的事情来确保依赖DLL也被加载,并且它们的DllMain()入口点以正确的顺序被调用并严格序列化。
它无法正确处理托管代码加载器存根MSCoree.dll,这使得您无法正确加载任何包含混合模式代码的DLL。
它没有做任何事情来确保Windows加载程序知道这些模块,这使得任何后续的DLL请求都很可能失败。这种失败是无法确定的。
您能够正确解决这些问题的可能性非常低。