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);
}
}
}
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) –
Documenta i valori di ritorno HRESULT, su * tutti * di queste chiamate. –
@HansPassant: tutte le chiamate restituiscono S_OK ma GetPinvokeMap che termina con 0x80131130 (CLDB_E_RECORD_NOTFOUND). – dud3