관리되지 않는 dll을 관리되는 C # dll에 포함


87

DLLImport를 사용하여 관리되지 않는 C ++ dll을 사용하는 관리되는 C # dll이 있습니다. 모두 훌륭하게 작동합니다. 그러나 Microsoft에서 설명하는 것처럼 관리되지 않는 DLL을 관리되는 DLL에 포함하고 싶습니다.

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'은 관리되는 DLL의 어셈블리 이름입니다. 'Unmanaged Driver.dll'은 관리되지 않는 DLL입니다.

실행하면 다음과 같은 결과가 나타납니다.

접근이 금지되어있다. (HRESULT에서 예외 : 0x80070005 (E_ACCESSDENIED))

나는 MSDN과 http://blogs.msdn.com/suzcook/ 에서 가능하다고 생각되는 것을 보았습니다 ...



1
귀하의 사례에 대해 BxILMerge를 고려할 수 있습니다
MastAvalons

답변:


64

초기화 중에 임시 디렉터리에 직접 추출한 경우 관리되지 않는 DLL을 리소스로 포함하고 P / Invoke를 사용하기 전에 LoadLibrary를 사용하여 명시 적으로로드 할 수 있습니다. 이 기술을 사용했으며 잘 작동합니다. 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가 kenel32에서 DLLImport를 사용하고 있습니까? Debug.Assert가 WCF 서비스 내에서 동일한 코드를 사용하여 실패합니다.
Klaus Nji

이것은 좋은 솔루션이지만 두 응용 프로그램이 동시에 같은 위치에 쓰기를 시도하는 경우 신뢰할 수있는 해결 방법을 찾는 것이 더 좋습니다. 다른 애플리케이션이 DLL 압축 풀기를 완료하기 전에 예외 처리기가 완료됩니다.
Robert Važan 2014

이것은 완벽 해요. 만 불필요한 것은 그것의 체크 내부에 존재하는 directory.createdirectory 이미 디렉토리를 가지고
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 () 메서드를 제거하고 ExtractEmbeddedDlls () 끝에 LoadLibrary ()를 호출 할 수 있음을 알았습니다. 또한 PATH 수정 코드를 제거 할 수있었습니다.
Cameron

9

나는 이것이 가능하다는 것을 몰랐습니다. CLR이 어딘가에 포함 된 네이티브 DLL을 추출해야한다고 생각합니다 (Windows는 DLL을로드 할 파일이 있어야합니다. 원시 메모리에서 이미지를로드 할 수 없습니다). 프로세스에 권한이 없다는 것을 시도하고 있습니다.

SysInternals의 Process Monitor 와 같은 것이 DLL 파일 생성이 실패한다는 문제인 경우 단서를 제공 할 수 있습니다.

최신 정보:


아 ... 이제 Suzanne Cook의 기사를 읽을 수 있었으므로 (이전에는이 ​​페이지가 나에게 제공되지 않았습니다), 그녀는 관리되는 DLL 내부에 리소스로 네이티브 DLL을 포함하는 것에 대해 이야기하는 것이 아니라 연결된 리소스 로서 -네이티브 DLL은 파일 시스템에서 자체 파일이어야합니다.

http://msdn.microsoft.com/en-us/library/xawyf94k.aspx를 참조 하십시오 .

리소스 파일은 출력 파일에 추가되지 않습니다. 이것은 출력 파일에 리소스 파일을 포함하는 / resource 옵션과 다릅니다.

이것이하는 것처럼 보이는 것은 네이티브 DLL이 논리적으로 어셈블리의 일부가되도록하는 어셈블리에 메타 데이터를 추가하는 것입니다 (물리적으로 별도의 파일 임에도 불구하고). 따라서 관리되는 어셈블리를 GAC에 넣는 것과 같은 작업에는 자동으로 네이티브 DLL 등이 포함됩니다.


Visual Studio에서 "linkresource"옵션을 사용하는 방법은 무엇입니까? 예를 찾을 수 없습니다.
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을 주입 열어으로이 그렇게 높은 보안 환경에서는주의해서 사용해야합니다, 보안 파급 효과가있을 것으로 단지 참고
요엘 halb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.