[英]Embedding unmanaged dll into a managed C# dll
我有一个托管 C# dll 使用非托管 C++ Z06416233FE5EC4C5933122E4AB248 使用DLL。 一切都很好。 但是,我想将非托管 DLL 嵌入到我的托管 DLL 中,正如微软在此处解释的那样:
所以我将非托管 dll 文件添加到我的托管 dll 项目中,将属性设置为“嵌入式资源”并将 DLLImport 修改为:
[DllImport("Unmanaged Driver.dll, Wrapper Engine, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null",
CallingConvention = CallingConvention.Winapi)]
其中“Wrapper Engine”是我的托管 DLL 的程序集名称“Unmanaged Driver.dll”是非托管 DLL
当我跑步时,我得到:
访问被拒绝。 (来自 HRESULT 的异常:0x80070005 (E_ACCESSDENIED))
我从 MSDN 和http://blogs.msdn.com/suzcook/看到这应该是可能的......
如果您在初始化期间将非托管 DLL 解压缩到临时目录中,并在使用 P/Invoke 之前使用 LoadLibrary 显式加载它,则可以将非托管 DLL 作为资源嵌入。 我已经使用过这种技术并且效果很好。 正如 Michael 所指出的,您可能更愿意将它作为单独的文件链接到程序集,但是将它们全部放在一个文件中具有其优势。 这是我使用的方法:
// 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);
这是我的解决方案,它是 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);
}
}
}
}
你可以试试Costura.Fody 。 文档说,它能够处理非托管文件。 我只将它用于托管文件,它就像一个魅力:)
我不知道这是可能的 - 我猜 CLR 需要在某处提取嵌入式本机 DLL(Windows 需要有一个文件供 DLL 加载它 - 它无法从原始内存加载图像),以及任何地方它试图做到这一点,该过程没有权限。
如果问题是创建 DLL 文件失败,则来自 SysInternals 的Process Monitor之类的东西可能会为您提供线索...
更新:
啊......现在我已经能够阅读 Suzanne Cook 的文章(该页面之前没有为我出现),请注意,她不是在谈论将原生 DLL 作为资源嵌入到托管 DLL 中,而是作为链接资源- 原生 DLL 仍然需要在文件系统中成为自己的文件。
请参阅http://msdn.microsoft.com/en-us/library/xawyf94k.aspx ,其中说:
资源文件未添加到 output 文件中。 这与在 output 文件中嵌入资源文件的 /resource 选项不同。
这似乎是向程序集添加元数据,导致本机 DLL 在逻辑上成为程序集的一部分(即使它在物理上是一个单独的文件)。 因此,将托管程序集放入 GAC 之类的操作将自动包含本机 DLL 等。
也可以将 DLL 复制到任何文件夹,然后调用SetDllDirectory到该文件夹。 那时不需要调用 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.