简体   繁体   English

将非托管 dll 嵌入托管 C# dll

[英]Embedding unmanaged dll into a managed C# dll

I have a managed C# dll that uses an unmanaged C++ dll using DLLImport.我有一个托管 C# dll 使用非托管 C++ Z06416233FE5EC4C5933122E4AB248 使用DLL。 All is working great.一切都很好。 However, I want to embed that unmanaged DLL inside my managed DLL as explain by Microsoft there:但是,我想将非托管 DLL 嵌入到我的托管 DLL 中,正如微软在此处解释的那样:

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

So I added the unmanaged dll file to my managed dll project, set the property to 'Embedded Resource' and modify the DLLImport to something like:所以我将非托管 dll 文件添加到我的托管 dll 项目中,将属性设置为“嵌入式资源”并将 DLLImport 修改为:

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

where 'Wrapper Engine' is the assembly name of my managed DLL 'Unmanaged Driver.dll' is the unmanaged DLL其中“Wrapper Engine”是我的托管 DLL 的程序集名称“Unmanaged Driver.dll”是非托管 DLL

When I run, I get:当我跑步时,我得到:

Access is denied.访问被拒绝。 (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)) (来自 HRESULT 的异常:0x80070005 (E_ACCESSDENIED))

I saw from MSDN and from http://blogs.msdn.com/suzcook/ that's supposed to be possible...我从 MSDN 和http://blogs.msdn.com/suzcook/看到这应该是可能的......

You can embed the unmanaged DLL as a resource if you extract it yourself to a temporary directory during initialization, and load it explicitly with LoadLibrary before using P/Invoke.如果您在初始化期间将非托管 DLL 解压缩到临时目录中,并在使用 P/Invoke 之前使用 LoadLibrary 显式加载它,则可以将非托管 DLL 作为资源嵌入。 I have used this technique and it works well.我已经使用过这种技术并且效果很好。 You may prefer to just link it to the assembly as a separate file as Michael noted, but having it all in one file has its advantages.正如 Michael 所指出的,您可能更愿意将它作为单独的文件链接到程序集,但是将它们全部放在一个文件中具有其优势。 Here's the approach I used:这是我使用的方法:

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

Here is my solution, which is a modified version of JayMcClellan's answer.这是我的解决方案,它是 JayMcClellan 答案的修改版本。 Save the file below into a class.cs file.将下面的文件保存到 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);
            }
        }

    }
}

You can try Costura.Fody .你可以试试Costura.Fody Documentation says, that it's able to handle unmanaged files.文档说,它能够处理非托管文件。 I only used it for managed files, and it works like a charm:)我只将它用于托管文件,它就像一个魅力:)

I wasn't aware this is possible - I'd guess that the CLR needs to extract the embedded native DLL somewhere (Windows needs to have a file for the DLL to load it - it cannot load an image from raw memory), and wherever it's trying to do that the process does not have permission.我不知道这是可能的 - 我猜 CLR 需要在某处提取嵌入式本机 DLL(Windows 需要有一个文件供 DLL 加载它 - 它无法从原始内存加载图像),以及任何地方它试图做到这一点,该过程没有权限。

Something like Process Monitor from SysInternals might give you a clue if the pronblem is that creating the DLL file is failing...如果问题是创建 DLL 文件失败,则来自 SysInternals 的Process Monitor之类的东西可能会为您提供线索...

Update:更新:


Ah... now that I've been able to read Suzanne Cook's article (the page didn't come up for me before), note that she is not talking about embedding the native DLL as a resource inside the managed DLL, but rather as a linked resource - the native DLL still needs to be its own file in the file system.啊......现在我已经能够阅读 Suzanne Cook 的文章(该页面之前没有为我出现),请注意,她不是在谈论将原生 DLL 作为资源嵌入到托管 DLL 中,而是作为链接资源- 原生 DLL 仍然需要在文件系统中成为自己的文件。

See http://msdn.microsoft.com/en-us/library/xawyf94k.aspx , where it says:请参阅http://msdn.microsoft.com/en-us/library/xawyf94k.aspx ,其中说:

The resource file is not added to the output file.资源文件未添加到 output 文件中。 This differs from the /resource option which does embed a resource file in the output file.这与在 output 文件中嵌入资源文件的 /resource 选项不同。

What this seems to do is add metadata to the assembly that causes the native DLL to logically be part of the assembly (even though it's physically a separate file).这似乎是向程序集添加元数据,导致本机 DLL 在逻辑上成为程序集的一部分(即使它在物理上是一个单独的文件)。 So things like putting the managed assembly into the GAC will automatically include the native DLL, etc.因此,将托管程序集放入 GAC 之类的操作将自动包含本机 DLL 等。

One could also just copy the DLLs to any folder, and then call SetDllDirectory to that folder.也可以将 DLL 复制到任何文件夹,然后调用SetDllDirectory到该文件夹。 No call to LoadLibrary is needed then.那时不需要调用 LoadLibrary。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM