2012-06-07 20 views
62

Sto lavorando su profilatore MSIL e ho riscontrato problemi con ManagedToUnmanagedTransition e UnmanagedToManagedTransition callbacks dell'interfaccia ICorProfilerCallback.Creazione di profili di una dinamica pinvoke

Quello che voglio recuperare è un'informazione sul metodo chiamato (nome e nome del modulo in cui risiede).

Finora funzionava bene. Fino a quando non si è verificato il cosiddetto pinvoke dinamico (descritto in dettaglio a: http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx)

In questo scenario IMetaDataImport::GetPinvokeMap non riesce. Anche IMetaDataAssemblyImport::GetAssemblyProps restituisce "dynamic_pinvoke" come nome dell'assembly.

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" 

Come ottenere un nome di modulo (.dll) e un nome di funzione che pinvoked tramite PInvoke dinamica?

+0

Ottima domanda !!! Hai provato (quando ottieni "dynamic_pinvoke") per saltare GetPinvokeMap e passare alle funzioni della famiglia StackWalk64? (http://msdn.microsoft.com/en-us/library/windows/desktop/ms680650(v=vs.85).aspx) –

+1

Documenta i valori di ritorno HRESULT, su * tutti * di queste chiamate. –

+0

@HansPassant: tutte le chiamate restituiscono S_OK ma GetPinvokeMap che termina con 0x80131130 (CLDB_E_RECORD_NOTFOUND). – dud3

risposta

4

Le API del profiler restituiscono i metadati specificati nel codice gestito, normalmente tramite DllImportAttribute. Nel caso del "dynamic pinvoke" che utilizza il metodo Marshal.GetDelegateForFunctionPointer, i nomi dei moduli e delle funzioni non sono mai stati specificati come metadati e non sono disponibili. Un approccio alternativo alle dichiarazioni dinamiche di pinvoke che include i metadati richiesti probabilmente eviterà questo problema. Prova ad utilizzare API System.Reflection.Emit come TypeBuilder.DefinePInvokeMethod come un'unica soluzione.

Ecco un esempio di System.Reflection.Emit che funziona con le API del profiler.

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); 
     } 
    } 
} 

Alcuni esempi per dimostrare i problemi con l'approccio attuale.

[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); 
} 

Non esiste alcuna funzione MessageBox esportata da user32.dll. Contiene solo MessageBoxA e MessageBoxW. Poiché non abbiamo specificato ExactSpelling=false nell'attributo DllImport e il nostro CharSet è Unicode, .Net cercherà anche user32.dll per il nostro punto di ingresso aggiunto a W. Ciò significa che MessageBoxW è in effetti la funzione nativa che stiamo chiamando. Tuttavia, GetPinvokeMap restituisce 'MessageBox' come nome della funzione (variabile module_name nel codice).

Ora consente invece di richiamare la funzione tramite numero ordinale anziché nome. Utilizzo del programma dumpbin nell'SDK di Windows:

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

... 
2046 215 0006FD3F MessageBoxW 
... 

2046 è il numero ordinale di MessageBoxW. Regolazione nostra dichiarazione DllImport utilizzare il campo EntryPoint otteniamo:

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

Questa volta GetPInvokeMap restituisce "# 2046". Possiamo vedere che il profiler non sa nulla del "nome" della funzione nativa invocata.

Andando ancora oltre, il codice nativo chiamato potrebbe non avere nemmeno un nome. Nell'esempio seguente, una funzione 'Aggiungi' viene creata nella memoria eseguibile in fase di esecuzione. Nessun nome di funzione o libreria è mai stato associato al codice nativo in esecuzione.

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

Risposta davvero interessante. Anche se potrebbe avere senso usare 'AssemblyBuilderAccess.RunAndCollect' per evitare perdite di memoria. – svick

+0

Questo vale per le applicazioni con esecuzione più lunga. Idealmente, si creerebbe anche più di un pinvoke alla volta. Forse tutto per una determinata libreria in un singolo ModuleBuilder o TypeBuilder. – joncham

Problemi correlati