Les API du profileur renvoient les métadonnées spécifiées dans le code géré, normalement via la fonction DllImportAttribute . Dans le cas de la "pinvoke dynamique", qui utilise l'approche de la Marshal.GetDelegateForFunctionPointer les noms des modules et des fonctions n'ont jamais été spécifiés comme métadonnées et ne sont pas disponibles. Une autre approche des déclarations dynamiques de pinvoke qui inclut les métadonnées requises permettra probablement d'éviter ce problème. Essayez d'utiliser des API System.Reflection.Emit telles que TypeBuilder.DefinePInvokeMethod (Constructeur de type) comme une solution.
Voici un exemple utilisant System.Reflection.Emit qui fonctionne avec les API du profileur.
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);
}
}
}
Quelques exemples pour démontrer les problèmes de l'approche actuelle.
[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);
}
Il n'y a pas de fonction MessageBox exportée par user32.dll. Elle ne contient que MessageBoxA et MessageBoxW. Comme nous n'avons pas spécifié la fonction Orthographe exacte=false dans l'attribut DllImport et que notre CharSet est Unicode, .Net recherchera également dans user32.dll notre point d'entrée accompagné d'un W. Cela signifie que MessageBoxW est en fait la fonction native que nous appelons. Cependant, GetPinvokeMap renvoie 'MessageBox' comme nom de fonction (variable module_name dans votre code).
Maintenant, invoquons la fonction par le numéro ordinal plutôt que par le nom. En utilisant le programme dumpbin du SDK de Windows :
dumpbin /exports C:\Windows\SysWOW64\user32.dll
...
2046 215 0006FD3F MessageBoxW
...
2046 est le numéro ordinal de MessageBoxW. En ajustant notre déclaration DllImport pour utiliser le champ EntryPoint, nous obtenons :
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "#2046")]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);
Cette fois, GetPInvokeMap renvoie "#2046". Nous pouvons voir que le profileur ne sait rien du "nom" de la fonction native invoquée.
En allant encore plus loin, le code natif appelé peut même ne pas avoir de nom. Dans l'exemple suivant, une fonction "Add" est créée dans la mémoire exécutable au moment de l'exécution. Aucun nom de fonction ou de bibliothèque n'a jamais été associé au code natif exécuté.
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 votes
Très bonne question ! !! Avez-vous essayé (lorsque vous obtenez "dynamic_pinvoke") de sauter GetPinvokeMap et de passer aux fonctions de la famille StackWalk64 ? ( msdn.microsoft.com/fr/us/library/Windows/desktop/ )
1 votes
Documenter les valeurs de retour HRESULT, sur tous de ces appels.
0 votes
@HansPassant : tous les appels renvoient S_OK sauf GetPinvokeMap qui aboutit à 0x80131130 (CLDB_E_RECORD_NOTFOUND).
0 votes
Je ne suis pas familier avec les interfaces de profilage, mais j'ai une idée. Prenez le
FunctionId
que vous obtenez dans le callback, passez-le àICorProfilerInfo::GetCodeInfo
pour obtenir le point d'entrée de la fonction (je pense...). Avec cette adresse, utiliserVirtualQuery
pour obtenir le handle du module dans lequel il se trouve puis utiliserGetModuleFileName
pour obtenir le nom du module.0 votes
Je ne connais pas d'API permettant d'obtenir un nom d'exportation à partir d'un pointeur de fonction, mais vous pourriez énumérer les exportations du module et construire votre propre table de consultation inversée. Encore une fois, je ne suis pas familier avec ces API ; c'est juste une idée qui m'est venue à l'esprit en lisant votre question. Faites-moi savoir si cela vous a aidé et je pourrai le publier comme réponse (ou, si cela ne vous aide pas, j'aimerais le savoir aussi :-D).
0 votes
Cette erreur est-elle associée à
GetPinvokeMap
se passe-t-il avec toutes les fonctions pour les DLLs qui sont appelées ? Si oui, êtes-vous sûr d'utiliser les bonnes conventions d'appel dans le code ? Pour ma part, j'aiderais le profileur en ajoutant une méthode d'aide pour stocker dynamiquement le nom du module pour chaque fonction afin qu'il soit facilement accessible ou extrait du profileur.0 votes
Aussi, Parcourir la pile d'appels comme Adriano l'a suggéré pourrait être une alternative...
0 votes
S'agit-il d'une question d'ordre linguistique ? Pourquoi y a-t-il autant de balises de langue ? Veuillez retaguer avec seulement un la langue.