การฝัง 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 ที่มีการจัดการของฉันตั้งค่าคุณสมบัติเป็น 'Embedded Resource' และแก้ไข DLLImport เป็นสิ่งที่ต้องการ:

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

โดยที่ 'Wrapper Engine' เป็นชื่อแอสเซมบลีของ 'Unmanaged Driver.dll' ที่มีการจัดการของฉันคือ DLL ที่ไม่มีการจัดการ

เมื่อฉันวิ่งฉันจะได้รับ:

การเข้าถึงถูกปฏิเสธ. (ข้อยกเว้นจาก HRESULT: 0x80070005 (E_ACCESSDENIED))

ฉันเห็นจาก MSDN และจากhttp://blogs.msdn.com/suzcook/น่าจะเป็นไปได้ ...



1
คุณสามารถพิจารณา BxILMerge สำหรับกรณีของคุณ
MastAvalons

คำตอบ:


64

คุณสามารถฝัง DLL ที่ไม่มีการจัดการเป็นรีซอร์สได้หากคุณแตกไฟล์ด้วยตัวเองไปยังไดเร็กทอรีชั่วคราวระหว่างการเริ่มต้นและโหลดอย่างชัดเจนด้วย LoadLibrary ก่อนที่จะใช้ P / Invoke ฉันได้ใช้เทคนิคนี้และได้ผลดี คุณอาจต้องการเพียงแค่เชื่อมโยงไปยังแอสเซมบลีเป็นไฟล์แยกต่างหากตามที่ Michael กล่าวไว้ แต่การมีทุกอย่างในไฟล์เดียวมีข้อดี นี่คือแนวทางที่ฉันใช้:

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

นี่เป็นวิธีแก้ปัญหาที่ดี แต่จะดีกว่าหากพบวิธีแก้ปัญหาที่เชื่อถือได้สำหรับกรณีที่แอปพลิเคชั่นสองตัวพยายามเขียนไปยังตำแหน่งเดียวกันในเวลาเดียวกัน ตัวจัดการข้อยกเว้นจะดำเนินการให้เสร็จสิ้นก่อนที่แอปพลิเคชันอื่นจะเสร็จสิ้นการคลายไฟล์
Robert Važan

เหมาะมากครับ สิ่งที่ไม่จำเป็นเพียงอย่างเดียวคือ directory.createdirectory มีไดเร็กทอรีอยู่แล้วให้ตรวจสอบภายใน
Gaspa79

14

นี่คือวิธีแก้ปัญหาของฉันซึ่งเป็นคำตอบของ 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 () นอกจากนี้ยังอนุญาตให้ฉันลบรหัสแก้ไขเส้นทาง
Cameron

9

ฉันไม่ทราบว่าเป็นไปได้ - ฉันเดาว่า CLR จำเป็นต้องแยก DLL ดั้งเดิมที่ฝังไว้ที่ไหนสักแห่ง (Windows ต้องมีไฟล์สำหรับ DLL เพื่อโหลด - ไม่สามารถโหลดภาพจากหน่วยความจำดิบ) และที่ใดก็ตาม กำลังพยายามดำเนินการโดยที่กระบวนการไม่ได้รับอนุญาต

บางอย่างเช่นProcess Monitor จาก SysInternalsอาจให้เบาะแสหากสรรพนามคือการสร้างไฟล์ DLL ล้มเหลว ...

อัปเดต:


อา ... ตอนนี้ฉันสามารถอ่านบทความของ Suzanne Cook ได้แล้ว (หน้านี้ไม่ได้มาก่อน) โปรดทราบว่าเธอไม่ได้พูดถึงการฝัง DLL ดั้งเดิมเป็นทรัพยากรภายใน DLL ที่มีการจัดการ แต่เป็น เป็นทรัพยากรที่เชื่อมโยง - DLL ดั้งเดิมยังคงต้องเป็นไฟล์ของตัวเองในระบบไฟล์

โปรดดูhttp://msdn.microsoft.com/en-us/library/xawyf94k.aspxโดยระบุว่า:

ไฟล์รีซอร์สไม่ได้ถูกเพิ่มลงในไฟล์เอาต์พุต สิ่งนี้แตกต่างจากอ็อพชัน / resource ซึ่งฝังไฟล์รีซอร์สในไฟล์เอาต์พุต

สิ่งที่ดูเหมือนจะทำคือเพิ่มข้อมูลเมตาในแอสเซมบลีที่ทำให้ DLL ดั้งเดิมเป็นส่วนหนึ่งของแอสเซมบลีอย่างมีเหตุผล ดังนั้นสิ่งต่างๆเช่นการใส่แอสเซมบลีที่มีการจัดการลงใน GAC จะรวม DLL ดั้งเดิมโดยอัตโนมัติเป็นต้น


วิธีใช้ตัวเลือก "linkresource" ใน Visual Studio หาตัวอย่างไม่เจอ
Alexey Subbota

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.