Yönetilmeyen dll'yi yönetilen bir C # dll'ye katıştırma


87

DLLImport kullanarak yönetilmeyen bir C ++ dll kullanan yönetilen bir C # dll var. Hepsi harika çalışıyor. Ancak, bu yönetilmeyen DLL'yi Microsoft tarafından açıklandığı gibi yönetilen DLL dosyamın içine yerleştirmek istiyorum:

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

Bu yüzden yönetilmeyen dll dosyasını yönetilen dll projeme ekledim, özelliği 'Gömülü Kaynak' olarak ayarladım ve DLLImport'u aşağıdaki gibi değiştirdim:

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

'Wrapper Engine', yönetilen DLL'imin derleme adı olan 'Unmanaged Driver.dll' ise yönetilmeyen DLL'dir

Koştuğumda şunu elde ederim:

Giriş reddedildi. (HRESULT istisnası: 0x80070005 (E_ACCESSDENIED))

MSDN'den ve mümkün olması gereken http://blogs.msdn.com/suzcook/ adresinden gördüm ...



1
Davanız için BxILMerge'i düşünebilirsiniz
MastAvalons

Yanıtlar:


64

Yönetilmeyen DLL'yi, başlatma sırasında geçici bir dizine çıkarırsanız kaynak olarak gömebilirsiniz ve P / Invoke'u kullanmadan önce onu doğrudan LoadLibrary ile yükleyebilirsiniz. Bu tekniği kullandım ve iyi çalışıyor. Michael'ın belirttiği gibi, bunu montaja ayrı bir dosya olarak bağlamayı tercih edebilirsiniz, ancak hepsini tek bir dosyada bulundurmanın avantajları vardır. İşte kullandığım yaklaşım:

// 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, kenel32'den DLLImport kullanıyor mu? Debug.Assert, WCF hizmetinde aynı kodu kullanarak benim için başarısız oluyor.
Klaus Nji

Bu iyi bir çözümdür, ancak iki uygulamanın aynı anda aynı konuma yazmaya çalıştığı durumlar için güvenilir bir çözüm bulmak daha da iyi olacaktır. Özel durum işleyicisi, diğer uygulama DLL dosyasını açmayı bitirmeden önce tamamlanır.
Robert Važan

Bu harika. Sadece gereksiz olan şey, directory.createdirectory'de dizinin zaten var olup olmadığını kontrol
etmesidir

13

İşte JayMcClellan'ın cevabının değiştirilmiş bir versiyonu olan benim çözümüm. Aşağıdaki dosyayı bir class.cs dosyasına kaydedin.

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, bu gerçekten harika. Kullanımlarım için, LoadDll () yöntemini kaldırabileceğimi ve ExtractEmbeddedDlls () 'nin sonunda LoadLibrary () öğesini çağırabileceğimi öğrendim. Bu ayrıca PATH değiştirme kodunu kaldırmama izin verdi.
Cameron

9

Bunun mümkün olduğunun farkında değildim - CLR'nin gömülü yerel DLL'yi bir yere çıkarması gerektiğini tahmin ediyorum (Windows'un DLL'yi yüklemek için bir dosyaya ihtiyacı var - ham bellekten bir görüntü yükleyemez) ve her yerde İşlemin izni olmadığını yapmaya çalışıyor.

SysInternals'ten Process Monitor gibi bir şey , sorun DLL dosyasını oluşturmanın başarısız olmasıysa size bir ipucu verebilir ...

Güncelleme:


Ah ... şimdi Suzanne Cook'un makalesini okuyabildiğime göre (sayfa daha önce benim için gelmedi), yerel DLL'yi yönetilen DLL'nin içine bir kaynak olarak yerleştirmekten bahsetmediğini unutmayın, bunun yerine bir şekilde bağlanmış bir kaynak - yerel bir DLL hala dosya sisteminde kendi dosya olması gerekiyor.

Http://msdn.microsoft.com/en-us/library/xawyf94k.aspx adresine bakın , burada:

Kaynak dosyası çıktı dosyasına eklenmez. Bu, çıktı dosyasına bir kaynak dosyası yerleştiren / resource seçeneğinden farklıdır.

Bunun yaptığı şey, yerel DLL'nin mantıksal olarak derlemenin parçası olmasına neden olan meta verileri derlemeye eklemektir (fiziksel olarak ayrı bir dosya olsa bile). Dolayısıyla, yönetilen derlemeyi GAC'ye koymak gibi şeyler otomatik olarak yerel DLL'yi vb. İçerecektir.


Visual Studio'da "bağlantı kaynağı" seçenekleri nasıl kullanılır? Örnek bulamıyorum.
Alexey Subbota

9

Costura.Fody'yi deneyebilirsiniz . Belgeler, yönetilmeyen dosyaları idare edebileceğini söylüyor. Onu sadece yönetilen dosyalar için kullandım ve harika çalışıyor :)


4

Biri ayrıca DLL'leri herhangi bir klasöre kopyalayabilir ve ardından SetDllDirectory'yi bu klasöre çağırabilir . LoadLibrary'ye çağrı yapmanız gerekmez.

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

1
Harika bir fikir, bunun dll enjeksiyonu için açıldığı için güvenlik sonuçları olabileceğini unutmayın, bu nedenle yüksek güvenlik ortamında dikkatli kullanılmalıdır
yoel halb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.