2009-03-20 68 views
63

được quản lý Tôi có một tập tin C# được quản lý sử dụng dll C++ không được quản lý bằng cách sử dụng DLLImport. Tất cả đều hoạt động tốt. Tuy nhiên, tôi muốn nhúng rằng DLL không được quản lý bên trong DLL quản lý của tôi như là giải thích bởi Microsoft có:Nhúng dll không được quản lý vào một C# dll

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.dllimportattribute.dllimportattribute.aspx

Vì vậy, tôi đã thêm các file dll không được quản lý cho dự án dll quản lý của tôi, thiết lập thuộc tính cho 'Resource Embedded' và sửa đổi các DllImport để cái gì đó như:

[DllImport("Unmanaged Driver.dll, Wrapper Engine, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=null", 
CallingConvention = CallingConvention.Winapi)] 

nơi 'Wrapper Engine' là tên lắp ráp các DLL được quản lý của tôi 'Switch Driver.dll' là DLL không được quản lý

Khi tôi chạy, tôi nhận được:

Truy cập bị từ chối. (Ngoại lệ từ HRESULT: 0x80070005 (E_ACCESSDENIED))

tôi thấy từ MSDN và từ http://blogs.msdn.com/suzcook/ đó là nghĩa vụ phải được thể ...

+0

Bản sao có thể có của [Làm cách nào có thể kết hợp cửa sổ C++ vào ứng dụng C# exe?] (Http://stackoverflow.com/questions/72264/how-can-ac-windows-dll-be- sáp nhập-thành-ac-sắc nét-ứng dụng-exe) – Noah

Trả lời

6

Tôi không biết đây là có thể - Tôi đoán rằng CLR cần trích xuất DLL gốc được nhúng ở đâu đó (Windows cần phải có một tệp cho DLL để tải nó - nó không thể tải một hình ảnh từ bộ nhớ thô), và bất cứ nơi nào nó cố gắng làm điều đó quá trình không có quyền.

Something như Process Monitor from SysInternals có thể cung cấp cho bạn một đầu mối nếu pronblem là tạo ra các tập tin DLL là không ...

Cập nhật:


Ah ... bây giờ mà tôi đã có thể để đọc bài viết của Suzanne Cook (trang đã không đưa ra cho tôi trước đây), lưu ý rằng cô ấy không nói về việc nhúng DLL gốc làm tài nguyên bên trong DLL được quản lý, mà là một tài nguyên được liên kết - DLL gốc cần phải là tệp riêng của nó trong hệ thống tệp.

Xem http://msdn.microsoft.com/en-us/library/xawyf94k.aspx, nơi nó nói:

Các tập tin tài nguyên không được thêm vào tập tin đầu ra. Điều này khác với tùy chọn/resource mà nhúng một tệp tài nguyên vào tệp đầu ra.

Điều này dường như làm là thêm siêu dữ liệu vào hội đồng khiến cho DLL gốc trở thành một phần của assembly (mặc dù nó là một tệp riêng biệt). Vì vậy, những thứ như đưa hội đồng quản lý vào GAC sẽ tự động bao gồm DLL gốc, v.v.

55

Bạn có thể nhúng DLL không được quản lý dưới dạng tài nguyên nếu bạn tự giải nén nó vào thư mục tạm thời trong khi khởi tạo và tải nó một cách rõ ràng bằng LoadLibrary trước khi sử dụng P/Invoke. Tôi đã sử dụng kỹ thuật này và nó hoạt động tốt. Bạn có thể chỉ muốn liên kết nó với assembly như một tập tin riêng biệt như Michael đã lưu ý, nhưng có tất cả trong một tập tin có lợi thế của nó. Đây là phương pháp tôi đã sử dụng:

// Get a temporary directory in which we can store the unmanaged DLL, with 
// this assembly's version number in the path in order to avoid version 
// conflicts in case two applications are running at once with different versions 
string dirName = Path.Combine(Path.GetTempPath(), "MyAssembly." + 
    Assembly.GetExecutingAssembly().GetName().Version.ToString()); 
if (!Directory.Exists(dirName)) 
    Directory.CreateDirectory(dirName); 
string dllPath = Path.Combine(dirName, "MyAssembly.Unmanaged.dll"); 

// Get the embedded resource stream that holds the Internal DLL in this assembly. 
// The name looks funny because it must be the default namespace of this project 
// (MyAssembly.) plus the name of the Properties subdirectory where the 
// embedded resource resides (Properties.) plus the name of the file. 
using (Stream stm = Assembly.GetExecutingAssembly().GetManifestResourceStream(
    "MyAssembly.Properties.MyAssembly.Unmanaged.dll")) 
{ 
    // Copy the assembly to the temporary file 
    try 
    { 
    using (Stream outFile = File.Create(dllPath)) 
    { 
     const int sz = 4096; 
     byte[] buf = new byte[sz]; 
     while (true) 
     { 
     int nRead = stm.Read(buf, 0, sz); 
     if (nRead < 1) 
      break; 
     outFile.Write(buf, 0, nRead); 
     } 
    } 
    } 
    catch 
    { 
    // This may happen if another process has already created and loaded the file. 
    // Since the directory includes the version number of this assembly we can 
    // assume that it's the same bits, so we just ignore the excecption here and 
    // load the DLL. 
    } 
} 

// We must explicitly load the DLL here because the temporary directory 
// is not in the PATH. 
// Once it is loaded, the DllImport directives that use the DLL will use 
// the one that is already loaded into the process. 
IntPtr h = LoadLibrary(dllPath); 
Debug.Assert(h != IntPtr.Zero, "Unable to load library " + dllPath); 
+0

Là LoadLibrary sử dụng DLLImport từ kenel32? Debug.Assert là không cho tôi sử dụng cùng một mã trong dịch vụ WCF. –

+0

Đây là một giải pháp tốt, nhưng sẽ tốt hơn nếu tìm độ phân giải đáng tin cậy cho các trường hợp khi hai ứng dụng cố gắng ghi vào cùng một vị trí cùng một lúc. Trình xử lý ngoại lệ hoàn tất trước khi ứng dụng khác hoàn tất việc giải nén tệp DLL. –

8

Đây là giải pháp của tôi, đây là phiên bản sửa đổi của câu trả lời của JayMcClellan. Lưu tệp bên dưới vào tệp class.cs.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Runtime.InteropServices; 
using System.IO; 
using System.Reflection; 
using System.Diagnostics; 
using System.ComponentModel; 

namespace Qromodyn 
{ 
    /// <summary> 
    /// A class used by managed classes to managed unmanaged DLLs. 
    /// This will extract and load DLLs from embedded binary resources. 
    /// 
    /// This can be used with pinvoke, as well as manually loading DLLs your own way. If you use pinvoke, you don't need to load the DLLs, just 
    /// extract them. When the DLLs are extracted, the %PATH% environment variable is updated to point to the temporary folder. 
    /// 
    /// To Use 
    /// <list type=""> 
    /// <item>Add all of the DLLs as binary file resources to the project Propeties. Double click Properties/Resources.resx, 
    /// Add Resource, Add Existing File. The resource name will be similar but not exactly the same as the DLL file name.</item> 
    /// <item>In a static constructor of your application, call EmbeddedDllClass.ExtractEmbeddedDlls() for each DLL that is needed</item> 
    /// <example> 
    ///    EmbeddedDllClass.ExtractEmbeddedDlls("libFrontPanel-pinv.dll", Properties.Resources.libFrontPanel_pinv); 
    /// </example> 
    /// <item>Optional: In a static constructor of your application, call EmbeddedDllClass.LoadDll() to load the DLLs you have extracted. This is not necessary for pinvoke</item> 
    /// <example> 
    ///    EmbeddedDllClass.LoadDll("myscrewball.dll"); 
    /// </example> 
    /// <item>Continue using standard Pinvoke methods for the desired functions in the DLL</item> 
    /// </list> 
    /// </summary> 
    public class EmbeddedDllClass 
    { 
     private static string tempFolder = ""; 

     /// <summary> 
     /// Extract DLLs from resources to temporary folder 
     /// </summary> 
     /// <param name="dllName">name of DLL file to create (including dll suffix)</param> 
     /// <param name="resourceBytes">The resource name (fully qualified)</param> 
     public static void ExtractEmbeddedDlls(string dllName, byte[] resourceBytes) 
     { 
      Assembly assem = Assembly.GetExecutingAssembly(); 
      string[] names = assem.GetManifestResourceNames(); 
      AssemblyName an = assem.GetName(); 

      // The temporary folder holds one or more of the temporary DLLs 
      // It is made "unique" to avoid different versions of the DLL or architectures. 
      tempFolder = String.Format("{0}.{1}.{2}", an.Name, an.ProcessorArchitecture, an.Version); 

      string dirName = Path.Combine(Path.GetTempPath(), tempFolder); 
      if (!Directory.Exists(dirName)) 
      { 
       Directory.CreateDirectory(dirName); 
      } 

      // Add the temporary dirName to the PATH environment variable (at the head!) 
      string path = Environment.GetEnvironmentVariable("PATH"); 
      string[] pathPieces = path.Split(';'); 
      bool found = false; 
      foreach (string pathPiece in pathPieces) 
      { 
       if (pathPiece == dirName) 
       { 
        found = true; 
        break; 
       } 
      } 
      if (!found) 
      { 
       Environment.SetEnvironmentVariable("PATH", dirName + ";" + path); 
      } 

      // See if the file exists, avoid rewriting it if not necessary 
      string dllPath = Path.Combine(dirName, dllName); 
      bool rewrite = true; 
      if (File.Exists(dllPath)) { 
       byte[] existing = File.ReadAllBytes(dllPath); 
       if (resourceBytes.SequenceEqual(existing)) 
       { 
        rewrite = false; 
       } 
      } 
      if (rewrite) 
      { 
       File.WriteAllBytes(dllPath, resourceBytes); 
      } 
     } 

     [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] 
     static extern IntPtr LoadLibrary(string lpFileName); 

     /// <summary> 
     /// managed wrapper around LoadLibrary 
     /// </summary> 
     /// <param name="dllName"></param> 
     static public void LoadDll(string dllName) 
     { 
      if (tempFolder == "") 
      { 
       throw new Exception("Please call ExtractEmbeddedDlls before LoadDll"); 
      } 
      IntPtr h = LoadLibrary(dllName); 
      if (h == IntPtr.Zero) 
      { 
       Exception e = new Win32Exception(); 
       throw new DllNotFoundException("Unable to load library: " + dllName + " from " + tempFolder, e); 
      } 
     } 

    } 
} 
+1

Đánh dấu, điều này thực sự tuyệt vời. Đối với sử dụng của tôi, tôi thấy tôi có thể loại bỏ các LoadDll() phương pháp, và gọi LoadLibrary() ở phần cuối của ExtractEmbeddedDlls(). Điều này cũng cho phép tôi loại bỏ mã sửa đổi PATH. – Cameron

5

Bạn có thể thử Costura.Fody. Tài liệu nói rằng, nó có thể xử lý các tập tin không được quản lý. Tôi chỉ sử dụng nó cho các tập tin được quản lý, và nó hoạt động như một nét duyên dáng :)

2

Một cũng có thể chỉ sao chép các tệp DLL vào bất kỳ thư mục nào và sau đó gọi SetDllDirectory vào thư mục đó. Không có cuộc gọi đến LoadLibrary là cần thiết sau đó.

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    static extern bool SetDllDirectory(string lpPathName); 
Các vấn đề liên quan