web-dev-qa-db-ja.com

C#DllImportで32ビットまたは64ビットDLLを使用する

ここに状況があります。私はdot.netアプリケーションでCベースのdllを使用しています。 2つのDLLがあり、1つはMyDll32.dllと呼ばれる32ビットで、もう1つはMyDll64.dllと呼ばれる64ビットバージョンです。

DLLファイル名:文字列DLL_FILE_NAMEを保持する静的変数があります。

そして、次の方法で使用されます。

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

これまでのところシンプル。

ご想像のとおり、ソフトウェアは「Any CPU」をオンにしてコンパイルされています。

また、システムで64ビットファイルを使用するか32ビットファイルを使用するかを決定する次のコードもあります。

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

これで問題が表示されるはずです。DLL_FILE_NAMEは実行時ではなくコンパイル時に定義されているため、実行コンテキストに従って正しいdllがロードされません。

この問題に対処する正しい方法は何でしょうか? 2つの実行ファイル(32ビット用と64ビット用)が必要ないのですか? DLL_FILE_NAMEを設定するにはどうすればよいですか?before DllImportステートメントで使用されていますか?

58
Gilad

これを行う最も簡単な方法は、異なる名前の2つのメソッドをインポートし、正しいメソッドを呼び出すことです。 DLLは呼び出しが行われるまでロードされないので問題ありません:

[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);
}

もちろん、多くのインポートがある場合、これを手動で維持するのは非常に面倒になります。

55

2つのDLLが同じ名前を持ち、異なるフォルダーに配置されることを必要とする別の代替方法を次に示します。例えば:

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

トリックは、CLRが実行する前に手動でDLLをLoadLibraryでロードします。その後、MyDll.dllが既にロードされていることを確認して使用します。

これは、親クラスの静的コンストラクターで簡単に実行できます。

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/01Assembly.CodeBaseを使用して、 Shadow Copying が有効になっていても動作するようにします。

51
Benoit Blanchon

この場合、私はこのようにする必要があります(x64およびx86の2つのフォルダを作成し、両方のフォルダに同じ名前の対応するdllを配置します):

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);
}

DLLファイル名を保持する静的変数があります

静的変数ではありません。コンパイル時の定数です。実行時にコンパイル時定数を変更することはできません。

この問題に対処する正しい方法は何でしょうか?

正直なところ、x86をターゲットにし、64ビットバージョンをすべて忘れて、アプリケーションをWOW64で実行することをお勧めします。ただし、アプリケーションがx64として実行する必要がある場合を除きます。

X64が必要な場合は、次のことができます。

  • DLLをMyDll.dllなどの同じ名前に変更し、インストール/デプロイ時に正しいものを配置します。 (OSがx64の場合、64ビットバージョンのDLLをデプロイします。それ以外の場合はx86バージョンをデプロイします)。

  • X86用とx64用の2つの別々のビルドがあります。

7
vcsjones

あなたが説明するものは「サイドバイサイドアセンブリ」(同じアセンブリの2つのバージョン、1つは32ビット、もう1つは64ビット)として知られています...これらは役に立つと思います:

こちら シナリオにぴったりのチュートリアルを見つけることができます(.NET DLL C++/CLIのラッピングDLLネイティブDLLの参照)。

勧告:

X86としてビルドし、それで完了します...または、2つのビルド(x86とx64)があります...上記の手法はかなり複雑です...

2
Yahia

別のアプローチがあります

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

私はvcsjonesが意味するアプローチの1つを使用しました。

「DLLをMyDll.dllなどの同じ名前に変更し、インストール/デプロイ時に適切なDLLを配置します。」

このアプローチでは、2つのビルドプラットフォームを維持する必要がありますが、詳細については次のリンクを参照してください。 https://stackoverflow.com/a/6446638/38368

0
Danny Varod

V8.Net に使用するトリックは次のとおりです。

  1. 異なるアーキテクチャ間で切り替えるためのすべての定義を使用して、新しいC#「プロキシインターフェイス」プロジェクトを作成します。私の場合、プロジェクトにはV8.Net-ProxyInterfaceという名前が付けられました。例:
 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);

これは、参照するプロジェクトです。次の2つを参照しないでください。

  1. ライブラリのx64およびx86バージョンを生成するプロジェクトをさらに2つ作成します。これは非常に簡単です。同じフォルダ内の.csprojファイルをコピーして貼り付け、名前を変更するだけです。私の場合、プロジェクトファイルの名前はV8.Net-ProxyInterface-x64およびV8.Net-ProxyInterface-x86に変更され、プロジェクトをソリューションに追加しました。 Visual Studioでそれぞれのプロジェクト設定を開き、Assembly Nameの名前にx64またはx86が含まれていることを確認します。この時点で、3つのプロジェクトがあります。最初の「プレースホルダー」プロジェクトと、2つのアーキテクチャ固有のプロジェクトです。 2つの新しいプロジェクトの場合:

    a)x64インターフェイスプロジェクト設定を開き、Buildタブに移動して、上部のPlatformAll Platformsを選択し、x64Conditional compilation symbolsと入力します。

    b)x86インターフェースプロジェクト設定を開き、Buildタブに移動して、上部のPlatformAll Platformsを選択し、x86Conditional compilation symbolsと入力します。

  2. Build->Configuration Manager...を開き、x64がx64プロジェクトのプラットフォームとして選択され、x86がx86プロジェクトで選択されていることを確認します。両方ともDebug AND Release構成。

  3. 2つの新しいインターフェイスプロジェクト(x64およびx86用)がホストプロジェクトの同じ場所に出力されることを確認します(プロジェクト設定Build->Output pathを参照)。

  4. 最後の魔法:エンジンの静的コンストラクターで、アセンブリリゾルバーにすばやくアタッチします。

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

Resolverメソッドでは、現在のプロセスによって示された現在のプラットフォームに基づいてファイルをロードします(注:このコードは簡略化されたバージョンであり、テストされていません)。

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

最後に、ソリューションエクスプローラーでホストプロジェクトに移動し、Referencesを展開し、手順1で作成した最初のダミープロジェクトを選択して右クリックしてプロパティを開き、Copy Localfalse。これにより、P/Invoke関数ごとに1つの名前で開発し、リゾルバーを使用して実際にロードする関数を決定できます。

アセンブリローダーは、必要な場合にのみ実行されることに注意してください。エンジンクラスへの最初のアクセス時に、CLRシステムによって(私の場合は)自動的にトリガーされます。それがあなたにどのように翻訳されるかは、ホストプロジェクトがどのように設計されているかによります。

0
James Wilkins