Nhúng dll không được quản lý vào một dll C # được quản lý


87

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

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

Vì vậy, tôi đã thêm tệp dll không được quản lý vào dự án dll được quản lý của mình, đặt thuộc tính thành 'Tài nguyên được nhúng' và sửa đổi DLLImport thành một cái gì đó như:

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

trong đó 'Wrapper Engine' là tên lắp ráp của DLL được quản lý của tôi 'Unmanaged 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 đã xem từ MSDN và từ http://blogs.msdn.com/suzcook/ điều đó được cho là có thể ...



1
Bạn có thể cân nhắc BxILMerge cho trường hợp của mình
MastAvalons

Câu trả lời:


64

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 quá trình 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ể thích chỉ liên kết nó với assembly dưới dạng một tệp riêng biệt như Michael đã lưu ý, nhưng có tất cả trong một tệp 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);

LoadLibrary có sử dụng DLLImport từ kenel32 không? Debug.Assert không thành công đối với tôi khi sử dụng cùng một mã trong dịch vụ WCF.
Klaus Nji

Đây là một giải pháp tốt, nhưng sẽ tốt hơn nếu tìm ra giải pháp đá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 DLL.
Robert Važan

Đây là hoàn hảo. Chỉ có điều không cần thiết là directory.createdirectory rằng đã có thư mục tồn tại kiểm tra bên trong của nó
Gaspa79

13

Đây là giải pháp của tôi, 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 thành 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);
            }
        }

    }
}

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

9

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

Một cái gì đó như Process Monitor từ SysInternals có thể cung cấp cho bạn manh mối nếu vấn đề là việc tạo tệp DLL không thành công ...

Cập nhật:


Ah ... bây giờ tôi đã có thể đọc bài viết của Suzanne Cook (trang này không xuất hiện trước đây cho tôi), 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à dưới dạng tài nguyên được liên kết - DLL gốc vẫn 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ó cho biết:

Tệp tài nguyên không được thêm vào tệp đầu ra. Điều này khác với tùy chọn / resource nhúng 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 assembly khiến DLL gốc về mặt logic là một phần của assembly (mặc dù về mặt vật lý nó là một tệp riêng biệt). Vì vậy, những thứ như đặt assembly được quản lý vào GAC sẽ tự động bao gồm DLL gốc, v.v.


Làm cách nào để sử dụng các tùy chọn "linkresource" trong Visual Studio? Tôi không thể tìm thấy bất kỳ ví dụ nào.
Alexey Subbota

9

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


4

Người ta cũng có thể chỉ cần sao chép các tệp DLL vào bất kỳ thư mục nào, rồi gọi SetDllDirectory đến thư mục đó. Sau đó không cần gọi đến LoadLibrary.

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool SetDllDirectory(string lpPathName);

1
Ý tưởng tuyệt vời, chỉ cần lưu ý rằng điều này có thể có hậu quả an ninh, vì nó mở ra cho dll tiêm, vì vậy trong một môi trường an ninh cao nó nên được sử dụng một cách thận trọng
halb Yoel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.