2012-06-07 14 views
62

Estoy trabajando en el generador de perfiles MSIL y encontré problemas con ManagedToUnmanagedTransition y UnmanagedToManagedTransition de interfaz ICorProfilerCallback.Creación de perfiles de un pinvoke dinámico

Lo que quiero recuperar es una información sobre el método que se llama (nombre y nombre del módulo en el que reside).

Hasta ahora funcionaba bien. Hasta que se produjo el denominado pinvoke dinámico (descrito en detalle en: http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx)

En este escenario, IMetaDataImport::GetPinvokeMap falla. También IMetaDataAssemblyImport::GetAssemblyProps devuelve "dynamic_pinvoke" como nombre del ensamblado.

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" 

Cómo obtener un nombre de módulo (.dll) y un nombre de función que se está pinvoked través PInvoke dinámico?

+0

Muy buena pregunta !!! ¿Intentó (cuando obtiene "dynamic_pinvoke") omitir GetPinvokeMap y cambiar a las funciones de la familia StackWalk64? (http://msdn.microsoft.com/en-us/library/windows/desktop/ms680650(v=vs.85).aspx) –

+1

Documente los valores de retorno HRESULT en * todas * estas llamadas. –

+0

@HansPassant: todas las llamadas devuelven S_OK pero GetPinvokeMap que termina con 0x80131130 (CLDB_E_RECORD_NOTFOUND). – dud3

Respuesta

4

Las API de perfil devuelven los metadatos especificados en el código administrado, normalmente a través del DllImportAttribute. En el caso del "pinvoke dinámico" que usa el método Marshal.GetDelegateForFunctionPointer, los nombres del módulo y la función nunca se especificaron como metadatos y no estaban disponibles. Un enfoque alternativo a las declaraciones dinámicas de pinvoke que incluya los metadatos requeridos probablemente evitará este problema. Intente utilizar System.Reflection.Emit API como TypeBuilder.DefinePInvokeMethod como una solución.

Aquí hay un ejemplo de uso de System.Reflection.Emit que funciona con las API del perfilador.

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

Algunos ejemplos para demostrar los problemas con el enfoque actual.

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

No se exportó la función MessageBox de user32.dll. Solo contiene MessageBoxA y MessageBoxW. Como no especificamos el ExactSpelling=false en el atributo DllImport y nuestro CharSet es Unicode, .Net también buscará user32.dll para nuestro punto de entrada con una W. Esto significa que MessageBoxW es, de hecho, la función nativa que estamos llamando. Sin embargo, GetPinvokeMap devuelve 'MessageBox' como el nombre de la función (variable module_name en su código).

Ahora, en su lugar, invoquemos la función a través del número ordinal en lugar del nombre. Usando el programa dumpbin en el SDK de Windows:

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

... 
2046 215 0006FD3F MessageBoxW 
... 

2046 es el número ordinal de MessageBoxW. Ajuste de nuestra declaración DllImport utilizar el campo EntryPoint obtenemos:

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

Esta vez vuelve GetPInvokeMap "# 2046". Podemos ver que el generador de perfiles no sabe nada sobre el 'nombre' de la función nativa que se invoca.

Yendo aún más lejos, el código nativo que se está llamando puede que ni siquiera tenga un nombre. En el siguiente ejemplo, se crea una función 'Agregar' en la memoria ejecutable en tiempo de ejecución. Ningún nombre de función o biblioteca ha sido asociado con el código nativo que se está ejecutando.

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

Respuesta realmente interesante. Aunque podría tener sentido usar 'AssemblyBuilderAccess.RunAndCollect' para evitar pérdidas de memoria. – svick

+0

Eso es cierto para aplicaciones de ejecución más larga. Idealmente, uno también crearía más de un pinvoke a la vez. Quizás todo para una biblioteca dada en un solo ModuleBuilder o TypeBuilder. – joncham

Cuestiones relacionadas