2013-01-04 100 views
2

我意識到一點點C#移植this application,允許從內存/流加載庫,而不是使用通過文件系統工作的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異常。任何關於這個的線索?^_^

+1

如果不能完全移植它,它會不會更容易(也用於維護),但是在原始項目的頂部提供了一個小的C++/CLI層? – stijn

+3

不要在這裏發佈你所有的代碼..只發布相關的部分,如果你有一個單一的方法所有的邏輯..然後我會建議重構它..我沒有時間看你的任何代碼if它超過了10-15行.. – MethodMan

+0

你的DLLMain做了什麼,它作爲結果返回了什麼?它是否被調用(例如,是否可以在DLLMain中執行OutoutDebugString並查看輸出)? –

回答

9

此代碼只是Windows加載程序加載DLL的一階近似。它只能用於最簡單的DLL,從C到C#代碼的翻譯也極有可能導致您正在處理的堆棧不平衡問題。我看到的主要問題:

  • 它沒有做任何事情來確保DLL之前沒有加載過。當您嘗試加載kernel32.dll和user32.dll時,這幾乎保證會成爲麻煩的來源,這些DLL在託管代碼開始執行之前已經加載。他們不會善意地再次裝載。

  • 它並沒有做任何明顯的事情來確保依賴的DLL也被加載,並且它們的DllMain()入口點以正確的順序被調用並嚴格地序列化。

  • 它沒有做任何事情來妥善處理託管代碼加載程序存根,MSCoree.dll,這使得它非常不可能正確加載任何包含混合模式代碼的DLL。

  • 它沒有做任何事情來確保Windows加載程序知道這些模塊,這使得任何後續的DLL請求都很可能失敗。這種失敗是無法想象的。

您能夠正確解決這些問題的可能性非常低。