2012-06-07 93 views
62

我正在MSIL分析器上工作,並遇到ManagedToUnmanagedTransition和接口的回調問題。剖析一個動態pinvoke

我想要檢索的是關於正在調用方法的信息(它駐留的名稱和模塊名稱)。

到目前爲止它工作正常。直到出現所謂的動態拼貼(詳細描述在:http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx

在這種情況下IMetaDataImport::GetPinvokeMap失敗。另外IMetaDataAssemblyImport::GetAssemblyProps返回「dynamic_pinvoke」作爲程序集的名稱。

profiler_1_0->GetTokenAndMetaDataFromFunction(function_id, IID_IMetaDataImport, (IUnknown**) &imd_import, &md_token); 
imd_import->GetPinvokeMap(md_token, &mapping, module_name, buffer_size, &chars_read, &md_module_ref); 
// here the fail occurs 

profiler_1_0->GetTokenAndMetaDataFromFunction(function_id, IID_IMetaDataAssemblyImport, (IUnknown**) &imd_assembly_import, &md_token); 
imd_assembly_import->GetAssemblyFromScope(&md_assembly); 
imd_assembly_import->GetAssemblyProps(md_assembly, 0, 0, 0, assembly_name, buffer_size, &chars_read, 0, 0); 
// assembly_name is set to "dynamic_pinvoke" 

如何獲取模塊名稱(.dll)和通過動態pinvoke pinvoked的函數名稱?

+0

很好的問題!!!你試過(當你得到「dynamic_pinvoke」)跳過GetPinvokeMap並切換到StackWalk64系列函數嗎? (http://msdn.microsoft.com/en-us/library/windows/desktop/ms680650(v=vs.85).aspx) –

+1

在所有這些調用中記錄HRESULT返回值。 –

+0

@HansPassant:所有調用都返回S_OK,但GetPinvokeMap以0x80131130結尾(CLDB_E_RECORD_NOTFOUND)。 – dud3

回答

4

分析器API正在通過DllImportAttribute返回託管代碼中指定的元數據。在使用Marshal.GetDelegateForFunctionPointer方法的「動態拼貼」的情況下,模塊和函數名稱從未被指定爲元數據並且不可用。包含所需元數據的動態拼寫聲明的替代方法可能會避免此問題。嘗試使用System.Reflection.Emit API(如TypeBuilder.DefinePInvokeMethod)作爲一個解決方案。

這是一個使用System.Reflection.Emit的示例,它可以與分析器API一起工作。

using System; 
using System.Reflection.Emit; 
using System.Runtime.InteropServices; 
using System.Reflection; 

namespace DynamicCodeCSharp 
{ 
    class Program 
    { 
     [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)] 
     private delegate int MessageBoxFunc(IntPtr hWnd, string text, string caption, int options); 

     static readonly Type[] MessageBoxArgTypes = new Type[] { typeof(IntPtr), typeof(string), typeof(string), typeof(int)}; 

     [DllImport("kernel32.dll")] 
     public static extern IntPtr LoadLibrary(string dllToLoad); 

     [DllImport("kernel32.dll")] 
     public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); 

     [DllImport("kernel32.dll")] 
     public static extern bool FreeLibrary(IntPtr hModule); 

     static MethodInfo BuildMessageBoxPInvoke(string module, string proc) 
     { 
      AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(module), AssemblyBuilderAccess.Run); 
      ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(module); 
      TypeBuilder typeBuilder = moduleBuilder.DefineType(proc); 

      typeBuilder.DefinePInvokeMethod(proc, module, proc, 
         MethodAttributes.Static | MethodAttributes.PinvokeImpl, 
         CallingConventions.Standard, typeof 
         (int), MessageBoxArgTypes, 
         CallingConvention.StdCall, CharSet.Auto); 

      Type type = typeBuilder.CreateType(); 

      return type.GetMethod(proc, BindingFlags.Static | BindingFlags.NonPublic); ; 
     } 

     static MessageBoxFunc CreateFunc() 
     { 
      MethodInfo methodInfo = BuildMessageBoxPInvoke("user32.dll", "MessageBox"); 
      return (MessageBoxFunc)Delegate.CreateDelegate(typeof(MessageBoxFunc), methodInfo); 
     } 

     static void Main(string[] args) 
     { 
      MessageBoxFunc func = CreateFunc(); 
      func(IntPtr.Zero, "Hello World", "From C#", 0); 
     } 
    } 
} 

舉幾個例子來說明當前方法的問題。

[DllImport("user32.dll", CharSet = CharSet.Unicode)] 
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options); 

static void Main(string[] args) 
{ 
    MessageBox(IntPtr.Zero, "Hello World", "From C#", 0); 
} 

從user32.dll中沒有導出MessageBox函數。它只包含MessageBoxA和MessageBoxW。由於我們沒有在DllImport屬性中指定ExactSpelling=false,並且我們的CharSet是Unicode,因此.Net還會搜索user32.dll,以便爲我們的入口添加W.這意味着MessageBoxW實際上是我們調用的本機函數。但是,GetPinvokeMap返回'MessageBox'作爲函數名稱(代碼中的module_name變量)。

現在讓我們通過序號而不是名稱來調用函數。使用Windows SDK中的dumpbin程序:

dumpbin /exports C:\Windows\SysWOW64\user32.dll 

... 
2046 215 0006FD3F MessageBoxW 
... 

2046是MessageBoxW的序號。調整我們的DllImport聲明使用入口點領域,我們得到:

[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "#2046")] 
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options); 

這次GetPInvokeMap返回「#2046」。我們可以看到,剖析器對被調用的本地函數的「名稱」一無所知。

更進一步,被調用的本地代碼可能甚至沒有名稱。在以下示例中,運行時在可執行內存中創建了「添加」功能。沒有函數名稱或庫曾經與正在執行的本機代碼相關聯。

using System; 
using System.Runtime.InteropServices; 

namespace DynamicCodeCSharp 
{ 
    class Program 
    { 
     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     private delegate int AddFunc(int a, int b); 

     [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)] 
     private static extern IntPtr VirtualAlloc(IntPtr addr, IntPtr size, int allocType, int protectType); 

     const int MEM_COMMIT = 0x1000; 
     const int MEM_RESERVE = 0x2000; 

     const int PAGE_EXECUTE_READWRITE = 0x40; 

     static readonly byte[] buf = 
      { 
       // push ebp 
       0x55, 
       // mov ebp, esp 
       0x8b, 0xec, 
       // mov eax, [ebp + 8] 
       0x8b, 0x45, 0x08, 
       // add eax, [ebp + 8] 
       0x03, 0x45, 0x0c, 
       // pop ebp 
       0x5d, 
       // ret 
       0xc3 
      }; 

     static AddFunc CreateFunc() 
     { 
      // allocate some executable memory 
      IntPtr code = VirtualAlloc(IntPtr.Zero, (IntPtr)buf.Length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 
      // copy our add function implementation into the memory 
      Marshal.Copy(buf, 0, code, buf.Length); 
      // create a delegate to this executable memory 
      return (AddFunc)Marshal.GetDelegateForFunctionPointer(code, typeof(AddFunc)); 
     } 

     static void Main(string[] args) 
     { 
      AddFunc func = CreateFunc(); 
      int value = func(10, 20); 
      Console.WriteLine(value); 
     } 
    } 
} 
+0

真的很有意思的答案。儘管使用'AssemblyBuilderAccess.RunAndCollect'來避免內存泄漏是有意義的。 – svick

+0

對於更長時間運行的應用程序而言,這是正確的。理想情況下,一次也會創建多個pinvoke。也許全部用於單個模塊構建器或類型構建器中的給定庫。 – joncham