Incrustar dll no administrado en un dll de C # administrado


87

Tengo una dll C # administrada que usa una dll C ++ no administrada usando DLLImport. Todo está funcionando muy bien. Sin embargo, quiero incrustar esa DLL no administrada dentro de mi DLL administrada como lo explica Microsoft allí:

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

Así que agregué el archivo dll no administrado a mi proyecto dll administrado, configuré la propiedad en 'Recurso incrustado' y modifiqué DLLImport a algo como:

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

donde 'Wrapper Engine' es el nombre de ensamblado de mi DLL administrada 'Unmanaged Driver.dll' es la DLL no administrada

Cuando corro, obtengo:

Acceso denegado. (Excepción de HRESULT: 0x80070005 (E_ACCESSDENIED))

Vi desde MSDN y desde http://blogs.msdn.com/suzcook/ que se supone que es posible ...



1
Puede considerar BxILMerge para su caso
MastAvalons

Respuestas:


64

Puede incrustar la DLL no administrada como un recurso si la extrae usted mismo en un directorio temporal durante la inicialización y la carga explícitamente con LoadLibrary antes de usar P / Invoke. He usado esta técnica y funciona bien. Es posible que prefiera vincularlo al ensamblaje como un archivo separado como señaló Michael, pero tenerlo todo en un archivo tiene sus ventajas. Este es el enfoque que utilicé:

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

¿LoadLibrary está usando DLLImport de kenel32? Debug.Assert me falla al usar el mismo código dentro del servicio WCF.
Klaus Nji

Esta es una buena solución, pero sería aún mejor encontrar una resolución confiable para los casos en que dos aplicaciones intentan escribir en la misma ubicación al mismo tiempo. El controlador de excepciones se completa antes de que la otra aplicación termine de desempaquetar la DLL.
Robert Važan

Esto es perfecto. Lo único innecesario es que directory.createdirectory ya tiene el directorio existente, verifique dentro de él
Gaspa79

13

Aquí está mi solución, que es una versión modificada de la respuesta de JayMcClellan. Guarde el archivo siguiente en un archivo 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);
            }
        }

    }
}

2
Mark, esto es realmente genial. Para mis usos, descubrí que podía eliminar el método LoadDll () y llamar a LoadLibrary () al final de ExtractEmbeddedDlls (). Esto también me permitió eliminar el código de modificación PATH.
Cameron

9

No sabía que esto es posible; supongo que el CLR necesita extraer la DLL nativa incorporada en algún lugar (Windows necesita tener un archivo para que la DLL lo cargue; no puede cargar una imagen desde la memoria sin procesar) y donde sea está tratando de hacer que el proceso no tenga permiso.

Algo como Process Monitor de SysInternals podría darle una pista si el problema es que la creación del archivo DLL está fallando ...

Actualizar:


Ah ... ahora que he podido leer el artículo de Suzanne Cook (la página no se me apareció antes), tenga en cuenta que no está hablando de incrustar la DLL nativa como un recurso dentro de la DLL administrada, sino más bien como recurso vinculado : la DLL nativa aún debe ser su propio archivo en el sistema de archivos.

Consulte http://msdn.microsoft.com/en-us/library/xawyf94k.aspx , donde dice:

El archivo de recursos no se agrega al archivo de salida. Esto difiere de la opción / resource que incrusta un archivo de recursos en el archivo de salida.

Lo que esto parece hacer es agregar metadatos al ensamblado que hacen que la DLL nativa sea parte lógicamente del ensamblado (aunque físicamente es un archivo separado). Entonces, cosas como poner el ensamblado administrado en el GAC incluirán automáticamente la DLL nativa, etc.


¿Cómo usar las opciones de "linkresource" en Visual Studio? No puedo encontrar ningún ejemplo.
Alexey Subbota

9

Puedes probar Costura.Fody . La documentación dice que es capaz de manejar archivos no administrados. Solo lo usé para archivos administrados, y funciona como un encanto :)


4

También se pueden copiar los archivos DLL a cualquier carpeta y luego llamar a SetDllDirectory a esa carpeta. Entonces no se necesita ninguna llamada a LoadLibrary.

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

1
Gran idea, solo tenga en cuenta que esto podría tener ramificaciones de seguridad, ya que se abre para la inyección de dll, por lo que en un entorno de alta seguridad debe usarse con precaución
yoel halb
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.