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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/666799/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 12:31:21  来源:igfitidea点击:

Embedding unmanaged dll into a managed C# dll

c#unmanageddllimportmanaged

提问by

I have a managed C# dll that uses an unmanaged C++ dll using DLLImport. All is working great. However, I want to embed that unmanaged DLL inside my managed DLL as explain by Microsoft there:

我有一个托管 C# dll,它使用一个使用 DLLImport 的非托管 C++ dll。一切都很好。但是,我想将该非托管 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/看到这应该是可能的......

回答by Michael Burr

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 SysInternalsmight 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. This differs from the /resource option which does embed a resource file in the output file.

资源文件不会添加到输出文件中。这与在输出文件中嵌入资源文件的 /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). So things like putting the managed assembly into the GAC will automatically include the native DLL, etc.

这似乎是将元数据添加到程序集,使本机 DLL 在逻辑上成为程序集的一部分(即使它在物理上是一个单独的文件)。因此,将托管程序集放入 GAC 之类的事情将自动包含本机 DLL 等。

回答by JayMcClellan

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. 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. Here's the approach I used:

如果您在初始化期间自己将非托管 DLL 提取到临时目录,并在使用 P/Invoke 之前使用 LoadLibrary 显式加载它,则您可以将非托管 DLL 作为资源嵌入。我已经使用过这种技术并且效果很好。正如迈克尔所说,您可能更喜欢将它作为单独的文件链接到程序集,但将其全部放在一个文件中也有其优点。这是我使用的方法:

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

回答by Mark Lakata

Here is my solution, which is a modified version of JayMcClellan's answer. Save the file below into a class.cs file.

这是我的解决方案,它是 JayMcClellan 答案的修改版本。将下面的文件保存到 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);
            }
        }

    }
}

回答by Matthias

You can try 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 :)

你可以试试Costura.Fody。文档说,它能够处理非托管文件。我只将它用于托管文件,它就像一个魅力:)

回答by Ziriax

One could also just copy the DLLs to any folder, and then call SetDllDirectoryto that folder. No call to LoadLibrary is needed then.

也可以将 DLL 复制到任何文件夹,然后调用SetDllDirectory到该文件夹​​。这样就不需要调用 LoadLibrary。

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