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

我有一个托管 C # dll,它使用非托管 C + + dll 使用 DLLImport。 但是,我想把这个非托管 DLL 嵌入到我的托管 DLL 中,微软解释说:

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

因此,我将非托管 dll 文件添加到我的托管 dll 项目中,将属性设置为“嵌入式资源”,并将 DLLImport 修改为类似如下内容:

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

哪里 “包装器引擎”是托管 DLL 的程序集名称 “非托管 Driver.DLL”是非托管 DLL

当我跑步的时候,我得到:

拒绝访问。(来自 HResult T 的异常: 0x80070005 (E _ ACCESSDENIED)

我从 MSDN 和 http://blogs.msdn.com/suzcook/看到,这应该是可能的..。

69030 次浏览

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.

Something like Process Monitor from SysInternals might give you a clue if the pronblem is that creating the DLL file is failing...

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.

See http://msdn.microsoft.com/en-us/library/xawyf94k.aspx, where it says:

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.

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.

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:

// 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. Save the file below into a class.cs file.

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. Documentation says, that it's able to handle unmanaged files. I only used it for managed files, and it works like a charm :)

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

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