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);
}
}
}
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) –
Documente los valores de retorno HRESULT en * todas * estas llamadas. –
@HansPassant: todas las llamadas devuelven S_OK pero GetPinvokeMap que termina con 0x80131130 (CLDB_E_RECORD_NOTFOUND). – dud3