2013-05-15 79 views
17

我有一個應用程序構建爲'任何CPU',並有相同的庫的兩個第三方Dll針對x86和x64。我想在運行時包含這些庫中的一個,具體取決於它在客戶機上運行的平臺。最好的辦法是什麼?根據平臺加載x64或x86 DLL?

+1

這些託管程序集或本機代碼? –

+0

爲什麼你不想構建應用程序的x86和x64版本? – outcoldman

+0

我想說他們是混合的。反編譯給了我很多用C編寫的代碼,但我仍然不確定。 – Ammark

回答

21

如果我們談論的是託管DLL,申報P /調用是這樣的:

[DllImport("DllName.dll")] 
static extern foo(); 

請注意,我們沒有指定的DLL,只是它的名字,這是我設定一個路徑是相同的32位和64位版本。

然後,在調用任何p/invokes之前,將庫加載到您的進程中。通過p /調用到LoadLibrary API函數來完成此操作。此時,您將確定您的進程是32位還是64位,並相應地構建DLL的完整路徑。完整的路徑是你通過LoadLibrary

現在,當您調用庫的p/invokes時,它們將由您剛加載的模塊解析。

對於託管程序集,則可以使用Assembly.LoadFile來指定程序集的路徑。這可能是一個棘手的編排,但這篇優秀的文章告訴你如何:Automatically Choose 32 or 64 Bit Mixed Mode DLLs。有很多與混合模式相關的細節以及可能與您無關的本機DLL依賴關係。關鍵是AppDomain.CurrentDomain.AssemblyResolve事件處理程序。

+0

非常酷。我不知道這個。 – Ani

+0

感謝David,我需要在現有的dll中創建一個包裝,因爲我需要訪問的方法不是靜態的嗎? – Ammark

+0

當你像x86或只是64位一樣編譯時,你是否在調用代碼時使用'DllImport'? –

1

我其實對這個主題很有經驗,所以我想我會根據我在鉛筆遊戲中使用的方式發佈答案。首先,你需要「DllImport」兩個函數,一個來自32位dll,另一個來自64位dll(或者dylib,無論你的平臺使用什麼)。

static class Foo32 { 
    [DllImport("32bitdll.dll")] 
    internal static extern void Foo(); 
} 
static class Foo64 { 
    [DllImport("64bitdll.dll")] 
    internal static extern void Foo(); 
} 

然後,你需要含有代表一箇中間類,並且從32位或64位的互操作導入它們根據IntPtr的大小(我不使用Environment.Is64BitProcess,因爲這是一個.NET 4功能) :

internal delegate void FooDelegate(); 
static class FooDelegates { 
    internal static FooDelegate Foo; 

    static FooDelegates() { 
     Type interop = (IntPtr.Size == 8) ? typeof(Foo64) : typeof(Foo32); 
     FieldInfo[] fields = typeof(FooDelegates).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); 
     foreach (FieldInfo fi in fields) { 
      MethodInfo mi = glfwInterop.GetMethod(fi.Name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); 
      Delegate function = Delegate.CreateDelegate(fi.FieldType, mi); 
      fi.SetValue(null, function); 
     } 
    } 
} 

然後我通常使用一個「真正的」類,包含你輸入(儘管這在技術上並不需要)功能:

public static class FooApi { 
    public static void Foo() { 
     FooDelegates.Foo(); 
    } 
} 

如果您只需要一個或兩個函數,這是一個真正的痛苦,但導入代表的方式對於大型庫/應用程序非常有效。你可能想看看github上的Pencil.Gaming,因爲它使用這種方法相當廣泛(here是一個很多使用它的例子)。

此方法的另一個好處是它是100%的跨平臺,並且不依賴任何WinAPI函數。

+0

你不需要任何代表。對於所有導入的函數,您可以使用普通的舊'DllImport'來完成整個任務。 –

+0

@DavidHeffernan但是,你不能使用「任何CPU」。然後,您需要爲32位和64位系統重新分配您的應用程序的單獨版本。 – antonijn

+0

確定您可以使用AnyCPU。我的答案解釋瞭如何。您確實需要這些DLL才能擁有相同的名稱,但在不同的文件夾中您會注意到這一點。但對於32/64位的DLL,這是正常的。 –

2

我對我的問題的完整解決方案是使用David Heffernan提供的第二個鏈接。 我所做的是 1.引用了項目中的虛擬dll。 2.指定的兩個預生成事件

xcopy /y "$(SolutionDir)\Assemblies\Lib\x86\(Assembly name)*" "$(TargetDir)" 
xcopy /y "$(SolutionDir)\Assemblies\Lib\x64\(Assemble name)*" "$(TargetDir)" 

3和裝配中解決事件的應用程序的啓動改變取決於平臺對應的彙編。

 var currentDomain = AppDomain.CurrentDomain; 
     var location = Assembly.GetExecutingAssembly().Location; 
     var assemblyDir = Path.GetDirectoryName(location); 

     if (assemblyDir != null && (File.Exists(Path.Combine(assemblyDir, "(Assembly name).proxy.dll")) 
            || !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x86.dll")) 
            || !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x64.dll")))) 
     { 
      throw new InvalidOperationException("Found (Assembly name).proxy.dll which cannot exist. " 
       + "Must instead have (Assembly name).x86.dll and (Assembly name).x64.dll. Check your build settings."); 
     } 

     currentDomain.AssemblyResolve += (sender, arg) => 
     { 
      if (arg.Name.StartsWith("(Assembly name),", StringComparison.OrdinalIgnoreCase)) 
      { 
       string fileName = Path.Combine(assemblyDir, 
        string.Format("(Assembly).{0}.dll", (IntPtr.Size == 4) ? "x86" : "x64")); 
       return Assembly.LoadFile(fileName); 
      } 
      return null; 
     }; 
+1

但是,這隻適用於.net dll,不適用於任何DLL – Tyron