62 votes

Profilage d'un pinvoke dynamique

Je travaille sur le profileur MSIL et j'ai rencontré des problèmes avec ManagedToUnmanagedTransition y UnmanagedToManagedTransition des rappels de ICorProfilerCallback interface.

Ce que je veux récupérer est une information sur la méthode appelée (nom et nom du module dans lequel elle réside).

Jusqu'à présent, ça fonctionnait bien. Jusqu'à ce que ce qu'on appelle le pinvoke dynamique se produise (décrit en détail à l'adresse : http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx )

Dans ce scénario IMetaDataImport::GetPinvokeMap échoue. Aussi IMetaDataAssemblyImport::GetAssemblyProps renvoie "dynamic_pinvoke" comme nom de l'assemblage.

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"

Comment obtenir le nom d'un module (.dll) et le nom de la fonction invoquée par pinvoke dynamique ?

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).

5voto

joncham Points 1136

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

Réponse très intéressante. Bien que cela puisse avoir du sens d'utiliser AssemblyBuilderAccess.RunAndCollect pour éviter les fuites de mémoire.

0 votes

C'est vrai pour les applications à long terme. Idéalement, il faudrait aussi créer plus d'un pinvoke à la fois. Peut-être tous pour une bibliothèque donnée dans un seul ModuleBuilder ou TypeBuilder.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X