Embedding unmanaged dll into a managed C# dll - c#

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:
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:
[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
When I run, I get:
Access is denied. (Exception from HRESULT: 0x80070005
(E_ACCESSDENIED))
I saw from MSDN and from http://blogs.msdn.com/suzcook/ that's supposed to be possible...

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

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

Related

Avoiding assembly file locks with Microsoft.SqlServer.Types

Adding Microsoft.SqlServer.Types to an existing web project so that we can use the DBGeography and DBGeometry spacial types from NuGet adds native assemblies to the project and includes a Loader that looks something like this:
public class Utilities
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibrary(string libname);
/// <summary>
/// Loads the required native assemblies for the current architecture (x86 or x64)
/// </summary>
/// <param name="rootApplicationPath">
/// Root path of the current application. Use Server.MapPath(".") for ASP.NET applications
/// and AppDomain.CurrentDomain.BaseDirectory for desktop applications.
/// </param>
public static void LoadNativeAssemblies(string rootApplicationPath)
{
var nativeBinaryPath = IntPtr.Size > 4
? Path.Combine(rootApplicationPath, #"SqlServerTypes\x64\")
: Path.Combine(rootApplicationPath, #"SqlServerTypes\x86\");
LoadNativeAssembly(nativeBinaryPath, "msvcr120.dll");
LoadNativeAssembly(nativeBinaryPath, "SqlServerSpatial140.dll");
}
private static void LoadNativeAssembly(string nativeBinaryPath, string assemblyName)
{
var path = Path.Combine(nativeBinaryPath, assemblyName);
var ptr = LoadLibrary(path);
if (ptr == IntPtr.Zero)
{
throw new Exception(string.Format(
"Error loading {0} (ErrorCode: {1})",
assemblyName,
Marshal.GetLastWin32Error()));
}
}
}
Which then has to be called from our global.asax.cs like this:
SqlProviderServices.SqlServerTypesAssemblyName =
"Microsoft.SqlServer.Types, Version=14.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91";
SqlServerTypes.Utilities.LoadNativeAssemblies(Server.MapPath("~"));
(the first line being found only after some digging).
This all works fine locally, and works fine once we deploy...but, the problem is that when we redeploy our project those native assemblies are locked and that breaks the build. The worker process has to be stopped and restarted before deployment can happen and it is a major pain.
So my question is, is there a better way to handle this? The readme.htm that gets downloaded with the NuGet package suggests that you need to deploy the native assemblies to a machine that does not have 'System CLR Types for SQL Server' installed but doesn't quite elaborate on what, if anything, you need to do to install that and what you might need to change if you do.
We have the same issue. For our website we had update the publish profile to set EnableMSDeployAppOffline to true which cause the site to shut down during the deployment.
Then on the termination of the website event we release the dlls using the FreeLibrary method. This seems to work for us.

Can't find .XSL file embedded in project DLL

Quick question that's wrecked my morning and is driving me nuts.
I have a small project that includes a DLL from another project. The DLL has an XSL file embedded in it that I want to extract and apply to a webbrowser control.
I have no problem with extracting / accessing embedded resources in the main EXE file, but I cannot find the means of accessing it in the DLL!?
I've tried:
"SolutionName.DllName.Resource.xsl"
"ExeName.DllName.Resource.xsl"
"ProjectNamespace.DllNamespace.Resource.xsl"
...and pretty much every permutation thereof, but it's never able to find it.
I don't have a dot-notation reference for it in C# to use with nameof(), and I can't find any apparent reference / access to it with:
System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames();
So, what's the correct naming (or other?) method for retrieving this file?
In case any of this helps, here's some additional details:
Project Name: DataBuilder
Project Namespace: DataBuilder
DLL Name: CobCommon
DLL Namespaces: CobCommon, CobCommon.Classes, CobCommon.Classes.Data, CobCommon.Winforms, CobCommon.WinForms.Controls
XSL Resource Name: XmlFormating.xsl
The specified resource file operation is "Embedded Resource" and it's located in the "root" area of the DLL project.
Accessing global:: gives me CobCommon, and DataBuilder amongst the available choices, but CobCommon doesn't have either a .Properties or a .Resources option, and DataBuilder which does have .Properties.Resources gives "Culture" as the only reference.
The XSL file is listed on the DLL Project's "Properties|Resources|Files" tab.
What am I missing?
Using GetExecutingAssembly() will probably always refer to your assembly. Instead, create an instance of some innocuous, (hopefully) simple object declared in that external DLL, then use that instance object's...
<object-from-DLL>.GetType().Assembly.GetManifestResourceStream("what.youre.looking.for")
to get a stream handle to your embedded object.
So this was the way that I finally managed to put together a generic function to retrieve any text-encoded embedded resource from any project assembly (and that's working as intended in my project):
First, I extended the Assembly class to facilitate grabbing just the relevant leading portion of Assembly.FullName that we need to use to search for the requested resource:
/// <summary>Adds a function to dereference just the base name portion of an Assembly from the FullName.</summary>
/// <returns>The extracted base name if able, otherwise just the FullName.</returns>
public static string ExtractName(this Assembly root)
{
string pattern = #"^(?<assy>(?:[a-zA-Z\d]+[.]?)+)(?>,).*$", work = root.FullName;
MatchCollection matches = Regex.Matches(work, pattern, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
if (matches.Count > 0) return matches[0].Groups["assy"].Value;
if (work.IndexOf(",") > 3) return work.Substring(0, work.IndexOf(','));
return root.FullName;
}
Then I wrote this function to search for the specified assembly + embedded resource file and return its contents as a string, if found:
/// <summary>Searches all loaded assemblies in a project for the specified embedded resource text file.</summary>
/// <returns>If the specified resource is found, its contents as a string, otherwise throws a DllNotFoundException.</returns>
/// <exception cref="DllNotFoundException"></exception>
public static string FetchAssemblyResourceFile(string name)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
int i = -1; while ((++i < assemblies.Length) && !Regex.IsMatch(name, "^(" + assemblies[i].ExtractName() + ")", RegexOptions.IgnoreCase)) ;
if (i < assemblies.Length)
{
try {
using (System.IO.Stream stream = assemblies[i].GetManifestResourceStream(name))
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream))
return reader.ReadToEnd();
}
catch (Exception innerExc)
{
Exception result = new Exception("The requested resource (\"" + name + "\") was not found.");
result.InnerException = innerExc;
throw result;
}
}
throw new DllNotFoundException("The requested assembly resource (\"" + name + "\") could not be found.");
}

Can´t load mpusbapi.dll on C# .NET WPF project

I need to build a gui to communicate via bulk USB from my Windows PC to a PIC microcontroller. I'm trying to use mpusbapi.dll as I see over diferent tutorials on internet, but I can´t acommplish to succesfully reference the dll in my project. VS 2015 show me this error: "mpusbapi.dll" could not be added. Make sure that the file is accessible, and that its a valis assembly or COM component.
I did research and i figured out the problem was the unmanaged dll, so I tried to reference via DllImport. But at this time, that did not work either.
I share my code below expecting someone could help me or give me some reference to a better way to accomplish my objective with usb.
using System.Runtime.InteropServices;
namespace GUI_ROBOT
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//Problem here
[DllImport("mpusbapi.dll",CallingConvention = CallingConvention.Cdecl)]
static extern UInt32 MPUSBGetDLLVersion();
[DllImport("mpusbapi.dll")]
static extern UInt32 MPUSBGetDeviceCount(string pVID_PID);
private void btnStart_Click(object sender, RoutedEventArgs e)
{
try
{
string dCount = MPUSBGetDLLVersion().ToString();
listBox1.Items.Add(dCount);
}
catch (Exception j)
{
System.Windows.MessageBox.Show("Error: " + "\nMessage = " +
j.Message);
}
}
}
}
The try catch statetment give me the follow result:
Error after try catch statetment
Translate "Can´t load the .dll file 'mpuasbapi.dll': Can´t find the specified module."
I don´t undesrtand because I just added the file in my project.
Thanks people!
The problem was that the .dll have not being copied to the output folder, as Fruchtzwerg said. So I just changed the properties of the .dll file as I say in the comments.
Then [see comments] I had a 32-bit / 64-bit conflict with the mpusbapi.dll The problem was solve using this answer:Could not load file or assembly ... An attempt was made to load a program with an incorrect format (System.BadImageFormatException)

DLL not found when running outside debug folder

Application names, paths and urls have been renamed to 'MyApp' and 'example' just for easier reading.
Hello, I currently use 1 dll file for my application and that is log4net in c#. Now I include my dll in references from
C:\Users\ashle\AppData\Roaming\MyApp
Simply because I will be publicly releasing my application. Now it works fine outside debug mode and inside, but when I run the exe outside the /bin/debug folder it throws an error..
Unhandled Exception: System.IO.FileNotFoundException: Could not load
file or assembly 'log4net, Version=1.2.15.0, Culture=neutral,
PublicKeyToken=669e0ddf0bb1aa2a' or one of its dependencies. The
system cannot find the file specified.
at MyApp.Program.Main(String[] args)
I have also put this code in which I thought would stop it happening.. but what am I doing wrong? this code should cover my a**
if (!Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/MyApp"))
{
Console.WriteLine("Created directory: " + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/MyApp");
Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/MyApp");
}
if (!File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/MyApp/log4net.dll"))
{
Console.WriteLine("Please wait while we download some files...");
string downloadUrl = "http://example.com";
if (checkWebsiteAvalibility(downloadUrl))
{
WebClient webClient = new WebClient();
webClient.DownloadFileAsync(new Uri(downloadUrl + "/downloads/log4net.dll"),
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/MyApp/log4net.dll");
Console.Clear();
But when running the exe solo outside /bin/debug it doesn't even display the "Please wait while we download some files..." line
Before publishing your project, delete the dll file from your reference. Add the same dll through Add Reference. Then try to publish it.
It worked for me.
When I ran into this issue, I deployed the .dll to the running location of the executable. Add the dll to the project Resources and set its Build Action to Content.
class Program
{
//Programmatically, the Program() method is called prior to Main() and before any registrations are made.
//This is where we write the dll to the disk.
static Program()
{
//Path.GetDirectoryName() returns the folder path to a particular file.
//Assembly.GetExecutingAssembly().Location returns the path to the current running location of the compiled executable, with the name. E.G. C:\MyProject\MyProgram.exe
//We combine this with Path.GetDirectoryName to get the folder, and then write the dll into this folder. That way, when this method finishes and main is called, it will find the dll in the folder.
File.WriteAllBytes(string.Format("{0}{1}", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "\\log4net.dll"), FindResourceByName("log4net"));
}
/// <summary>
/// Returns a byte array of the object searched for
/// </summary>
/// <param name="objectName">Name of the resource object</param>
/// <returns>Byte array of the specified resource object</returns>
private static byte[] FindResourceByName(string objectName)
{
object obj = Properties.Resources.ResourceManager.GetObject(objectName);
return ((byte[])(obj));
}
//Rest of your code goes here...
}
In my situation, my DLL was called "Microsoft.Win32.TaskScheduler.dll" If this is the case, with your application, the periods will be replaced with an underscore when your DLL is added as a resource. Make sure that you reflect this when calling FindResourceByName or your lookup will fail:
File.WriteAllBytes(string.Format("{0}{1}", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "\\Microsoft.Win32.TaskScheduler.dll"), FindResourceByName("Microsoft_Win32_TaskScheduler"));

Load mixed assembly at runtime

I have a project uses Sqlite (System.Data.SQLite.DLL) it is mixed assembly. I need to load it at runtime. As I know it impossible using AssemblyResolve event. So the target is to unpack assembly to Temp directory and show to application where to find it.
The code is:
public static void SaveSqlite()
{
byte[] sqliteAsm = EmbedAssembly.System_Data_SQLite;
string tempFile = Path.GetTempPath();
File.WriteAllBytes(tempFile + "System.Data.SQLite.DLL", sqliteAsm);
}
// and the setup int start method
SaveSqlite();
AppDomain.CurrentDomain.SetupInformation.PrivateBinPath = Path.GetTempPath();
So it not works. Application cant find sqlite assembly but it successfuly saved to Temp dir. How to solve this problem? Thanks.
The solution has founded.
Not using mixed assembly but managed for sqlite interop Using
SQLite.
SQLite.Interop.dll - native dll (unpack to any directory)
Call SetDllDirectory WinAPI to set dll search directory
code for extract sqlite native dll from resource looks like that:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetDllDirectory(string lpPathName);
//...
string text4 = Path.Combine(text, "SQLite.Interop.dll");
if (!File.Exists(text4))
{
byte[] array3 = LoadCompressedDllFromResource("nativedll.SQLite.Interop.z");
array3 = DecompressBytes(array3);
SaveToFile(array3, text4);
}
//...
pack all over managed assemblies using libz application

Categories