Вбудовування некерованої DLL в керовану C # dll


87

У мене є керована C # dll, яка використовує некеровану DLL C ++ за допомогою DLLImport. Все працює чудово. Однак я хочу вбудувати цю некеровану бібліотеку DLL всередину моєї керованої бібліотеки DLL, як пояснив там Microsoft:

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

де 'Wrapper Engine' - це назва збірки моєї керованої DLL 'Unmanaged Driver.dll' - некерована DLL

Коли я біжу, я отримую:

У доступі відмовлено. (Виняток з HRESULT: 0x80070005 (E_ACCESSDENIED))

Я бачив із MSDN та з http://blogs.msdn.com/suzcook/, що це повинно бути можливим ...



1
Ви можете розглянути BxILMerge для вашої справи
MastAvalons

Відповіді:


64

Ви можете вбудувати некеровану DLL як ресурс, якщо ви самостійно витягнете її до тимчасового каталогу під час ініціалізації та явно завантажите її в LoadLibrary перед використанням P / Invoke. Я використовував цю техніку, і вона працює добре. Можливо, ви віддасте перевагу просто пов’язати його зі збіркою як окремий файл, як зазначив Майкл, але наявність усього файлу має свої переваги. Ось підхід, який я використав:

// 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 бібліотеку DLLImport від kenel32? Debug.Assert мені не вдається, використовуючи той самий код у службі WCF.
Klaus Nji,

Це хороше рішення, але ще краще було б знайти надійне вирішення для випадків, коли два додатки намагаються писати в одне і те ж місце одночасно. Обробник винятків завершується до того, як інша програма завершить розпакування DLL.
Роберт Важан,

Це прекрасно. Єдиним непотрібним є те, що directory.createirectory вже має перевірку, що каталог існує всередині нього
Gaspa79

13

Ось моє рішення, яке є модифікованою версією відповіді 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);
            }
        }

    }
}

2
Марк, це справді круто. Для мого використання я виявив, що можу видалити метод LoadDll () і викликати LoadLibrary () в кінці ExtractEmbeddedDlls (). Це також дозволило мені видалити код модифікації PATH.
Cameron

9

Я не знав, що це можливо - я здогадуюсь, що CLR потрібно де-небудь витягти вбудовану рідну DLL (Windows повинна мати файл, щоб DLL завантажувала її - вона не може завантажувати зображення із необробленої пам'яті) та де завгодно він намагається зробити, щоб процес не мав дозволу.

Щось на зразок Process Monitor від SysInternals може дати вам підказку, якщо проблема полягає в тому, що створення файлу DLL не вдається ...

Оновлення:


Ах ... тепер, коли я вже міг прочитати статтю Сюзани Кук (ця сторінка раніше не з’являлася для мене), зауважте, що вона не говорить про вбудовування власної DLL як ресурсу всередині керованої DLL, а скоріше як зв’язаний ресурс - рідна DLL все ще повинна бути власним файлом у файловій системі.

Див. Http://msdn.microsoft.com/en-us/library/xawyf94k.aspx , де написано:

Файл ресурсу не додається до вихідного файлу. Це відрізняється від параметра / resource, який вбудовує файл ресурсу у вихідний файл.

Здається, це робить додавання метаданих до збірки, що змушує рідну DLL логічно бути частиною збірки (хоча це фізично окремий файл). Тож такі речі, як розміщення керованої збірки в GAC, автоматично включатиме власну DLL тощо.


Як використовувати параметри "linkresource" у Visual Studio? Я не можу знайти жодного прикладу.
Олексій Суббота

9

Ви можете спробувати Costura.Fody . Документація говорить, що вона здатна обробляти файли, що не управляються. Я використовував його лише для керованих файлів, і це працює як шарм :)


4

Можна також просто скопіювати бібліотеки DLL у будь-яку папку, а потім викликати SetDllDirectory до цієї папки. Тоді виклик LoadLibrary не потрібен.

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

1
Чудова ідея, просто зауважте, що це може мати наслідки для безпеки, оскільки воно відкривається для введення DLL, тому в середовищі високого рівня безпеки його слід використовувати з обережністю
yoel halb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.