web-dev-qa-db-fra.com

Utilisation d'une DLL 32 bits ou 64 bits dans C # DllImport

Voici la situation, j'utilise une DLL basée sur C dans mon application dot.net. Il existe 2 DLL, l'une 32 bits appelée MyDll32.dll et l'autre une version 64 bits appelée MyDll64.dll.

Il existe une variable statique contenant le DLL nom de fichier: chaîne DLL_FILE_NAME.

et il est utilisé de la manière suivante:

[DllImport(DLL_FILE_NAME, CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
private static extern int is_Func1(int var1, int var2);

Jusqu'ici simple.

Comme vous pouvez l'imaginer, le logiciel est compilé avec "Any CPU" allumé.

J'ai également le code suivant pour déterminer si le système doit utiliser le fichier 64 bits ou le fichier 32 bits.

#if WIN64
        public const string DLL_FILE_NAME = "MyDll64.dll";
#else
        public const string DLL_FILE_NAME = "MyDll32.dll";        
#endif

Vous devriez maintenant voir le problème. DLL_FILE_NAME est défini au moment de la compilation et non au moment de l'exécution donc la bonne DLL n'est pas chargée selon le contexte d'exécution.

Quelle serait la bonne façon de régler ce problème? Je ne veux pas de deux fichiers d'exécution (un pour 32 bits et l'autre pour 64 bits)? Comment puis-je définir DLL_FILE_NAME avant il est utilisé dans l'instruction DllImport?

58
Gilad

J'ai trouvé que le moyen le plus simple de le faire est d'importer les deux méthodes avec des noms différents et d'appeler la bonne. Le DLL ne sera pas chargé jusqu'à ce que l'appel soit fait, donc ça va:

[DllImport("MyDll32.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_32(int var1, int var2);

[DllImport("MyDll64.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_64(int var1, int var2);

public static int Func1(int var1, int var2) {
    return IntPtr.Size == 8 /* 64bit */ ? Func1_64(var1, var2) : Func1_32(var1, var2);
}

Bien sûr, si vous avez de nombreuses importations, cela peut devenir assez lourd à gérer manuellement.

55

Voici une autre alternative qui nécessite que les deux DLL portent le même nom et soient placées dans des dossiers différents. Par exemple:

  • win32/MyDll.dll
  • win64/MyDll.dll

L'astuce consiste à charger manuellement le DLL avec LoadLibrary avant que le CLR ne le fasse. Il verra alors qu'un MyDll.dll est déjà chargé et utilisez-le.

Cela peut être fait facilement dans le constructeur statique de la classe parente.

static class MyDll
{
    static MyDll()
    {            
        var myPath = new Uri(typeof(MyDll).Assembly.CodeBase).LocalPath;
        var myFolder = Path.GetDirectoryName(myPath);

        var is64 = IntPtr.Size == 8;
        var subfolder = is64 ? "\\win64\\" : "\\win32\\";

        LoadLibrary(myFolder + subfolder + "MyDll.dll");
    }

    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("MyDll.dll")]
    public static extern int MyFunction(int var1, int var2);
}

EDIT 2017/02/01 : utilisez Assembly.CodeBase pour que cela fonctionne même si Copie fantôme est activé.

51
Benoit Blanchon

Dans ce cas, je devrais faire comme ça (créer 2 dossiers, x64 et x86 + mettre la DLL correspondante, AVEC LE MÊME NOM, dans les deux dossiers):

using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;

class Program {
    static void Main(string[] args) {
        var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
        path = Path.Combine(path, IntPtr.Size == 8 ? "x64" : "x86");
        bool ok = SetDllDirectory(path);
        if (!ok) throw new System.ComponentModel.Win32Exception();
    }
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool SetDllDirectory(string path);
}

Il existe une variable statique contenant le nom de fichier DLL

Ce n'est pas une variable statique. C'est une constante, au moment de la compilation. Vous ne pouvez pas modifier une constante de temps de compilation lors de l'exécution.

Quelle serait la bonne façon de régler ce problème?

Honnêtement, je recommanderais simplement de cibler x86 et d'oublier la version 64 bits tous ensemble, et de laisser votre application s'exécuter sur WOW64, sauf si votre application a un besoin impérieux de s'exécuter en tant que x64.

S'il y a un besoin de x64, vous pouvez:

  • Modifiez les DLL pour qu'elles portent le même nom, telles que MyDll.dll, et au moment de l'installation/du déploiement, mettez la bonne en place. (Si le système d'exploitation est x64, déployez la version 64 bits de la DLL, sinon la version x86).

  • Avoir deux versions distinctes au total, une pour x86 et une pour x64.

7
vcsjones

Ce que vous décrivez est appelé "assemblage côte à côte" (deux versions du même assemblage, l'une 32 et l'autre 64 bits) ... Je pense que vous les trouverez utiles:

Ici vous pouvez trouver une procédure pas à pas pour exactement votre scénario (.NET DLL encapsulation C++/CLI DLL référençant une DLL native)).

RECOMMANDATION:

Il suffit de le construire en x86 et d'en finir ... ou d'avoir 2 builds (un x86 et un x64) ... car les techniques ci-dessus sont assez compliquées ...

2
Yahia

une approche alternative peut être

public static class Sample
{
    public Sample()
    {

        string StartupDirEndingWithSlash = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName) + "\\";
        string ResolvedDomainTimeFileName = StartupDirEndingWithSlash + "ABCLib_Resolved.dll";
        if (!File.Exists(ResolvedDomainTimeFileName))
        {
            if (Environment.Is64BitProcess)
            {
                if (File.Exists(StartupDirEndingWithSlash + "ABCLib_64.dll"))
                    File.Copy(StartupDirEndingWithSlash + "ABCLib_64.dll", ResolvedDomainTimeFileName);
            }
            else
            {
                if (File.Exists(StartupDirEndingWithSlash + "ABCLib_32.dll"))
                    File.Copy(StartupDirEndingWithSlash + "ABCLib_32.dll", ResolvedDomainTimeFileName);
            }
        }
    }

    [DllImport("ABCLib__Resolved.dll")]
    private static extern bool SomeFunctionName(ref int FT);
}
1
user2381106

J'ai utilisé l'une des approches voulues par vcsjones:

"Modifiez les DLL pour qu'elles portent le même nom, telles que MyDll.dll, et au moment de l'installation/du déploiement, mettez la bonne en place."

Cette approche nécessite la maintenance de deux plates-formes de construction, mais consultez ce lien pour plus de détails: https://stackoverflow.com/a/6446638/38368

0
Danny Varod

L'astuce que j'utilise pour V8.Net est la suivante:

  1. Créez un nouveau projet "interface proxy" C # avec toutes les définitions pour basculer entre les différentes architectures. Dans mon cas, le projet a été nommé V8.Net-ProxyInterface; Exemple:
 public unsafe static class V8NetProxy
    {
    #if x86
            [DllImport("V8_Net_Proxy_x86")]
    #Elif x64
            [DllImport("V8_Net_Proxy_x64")]
    #else
            [DllImport("V8_Net_Proxy")] // (dummy - NOT USED!)
    #endif
            public static extern NativeV8EngineProxy* CreateV8EngineProxy(bool enableDebugging, void* debugMessageDispatcher, int debugPort);

CECI est le projet que vous référencerez. NE référencez PAS les deux suivants:

  1. Créez deux autres projets pour générer des versions x64 et x86 de la bibliothèque. C'est TRÈS FACILE: il suffit de copier-coller pour dupliquer le .csproj fichier dans le même dossier et les a renommés. Dans mon cas, le fichier de projet a été renommé en V8.Net-ProxyInterface-x64 et V8.Net-ProxyInterface-x86, puis j'ai ajouté les projets à ma solution. Ouvrez les paramètres du projet pour chacun d'eux dans Visual Studio et assurez-vous que le Assembly Name a le nom x64 ou x86. À ce stade, vous avez 3 projets: le premier projet "espace réservé" et les 2 projets spécifiques à l'architecture. Pour les 2 nouveaux projets:

    a) Ouvrez les paramètres du projet d'interface x64, allez dans l'onglet Build, sélectionnez All Platforms pour Platform en haut, puis entrez x64 dans Conditional compilation symbols.

    b) Ouvrez les paramètres du projet d'interface x86, accédez à l'onglet Build, sélectionnez All Platforms pour Platform en haut, puis entrez x86 dans Conditional compilation symbols.

  2. Ouvrir Build->Configuration Manager... et assurez-vous que x64 est sélectionné comme plate-forme pour les projets x64 et x86 est sélectionné pour les projets x86, pour les deux configurations Debug ET Release.

  3. Assurez-vous que les 2 nouveaux projets d'interface (pour x64 et x86) sortent au même emplacement que votre projet hôte (voir paramètre de projet Build->Output path).

  4. La magie finale: dans un constructeur statique pour mon moteur, je m'attache rapidement au résolveur d'assemblage:

static V8Engine()
{
    AppDomain.CurrentDomain.AssemblyResolve += Resolver;
}

Dans la méthode Resolver, je charge simplement le fichier basé sur la plate-forme actuelle indiquée par le processus en cours (note: ce code est une version allégée et non testée):

var currentExecPath = Assembly.GetExecutingAssembly().Location;
var platform = Environment.Is64BitProcess ? "x64" : "x86";
var filename = "V8.Net.Proxy.Interface." + platform + ".dll"
return Assembly.LoadFrom(Path.Combine(currentExecPath , fileName));

Enfin, accédez à votre projet Host dans l'Explorateur de solutions, développez References, sélectionnez le premier projet factice que vous avez créé à l'étape 1, cliquez dessus avec le bouton droit pour ouvrir les propriétés et définissez Copy Local à false. Cela vous permet de développer avec UN nom pour chaque fonction P/Invoke, tout en utilisant le résolveur pour déterminer celle à charger.

Notez que le chargeur d'assemblage ne fonctionne qu'en cas de besoin. Il n'est déclenché (dans mon cas) automatiquement par le système CLR qu'au premier accès à la classe moteur. La façon dont cela se traduit pour vous dépend de la façon dont votre projet hôte est conçu.

0
James Wilkins