[C#] 런타임에 [DllImport] 경로를 어떻게 지정합니까?

실제로 C # 프로젝트로 가져와 함수를 호출하려는 C ++ (작동) DLL이 있습니다.

다음과 같이 DLL의 전체 경로를 지정하면 작동합니다.

string str = "C:\\Users\\userName\\AppData\\Local\\myLibFolder\\myDLL.dll";
[DllImport(str, CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);

문제는 설치 가능한 프로젝트가 될 것이므로 사용자의 폴더는 컴퓨터 / 세션이 실행되는 컴퓨터 / 세션에 따라 동일하지 않습니다 (예 : pierre, paul, jack, mum, dad 등).

그래서 내 코드가 다음과 같이 좀 더 일반적이기를 원합니다.

/* 
goes right to the temp folder of the user 
    "C:\\Users\\userName\\AppData\\Local\\temp"
then go to parent folder
    "C:\\Users\\userName\\AppData\\Local"
and finally go to the DLL's folder
    "C:\\Users\\userName\\AppData\\Local\\temp\\myLibFolder"
*/

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
[DllImport(str, CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);

중요한 것은 “DllImport”가 DLL 디렉토리에 대한 “const string”매개 변수를 원한다는 것입니다.

그래서 내 질문은 ::이 경우 어떻게 할 수 있습니까?



답변

다른 답변 중 일부의 제안과 달리 DllImport속성을 사용하는 것이 여전히 올바른 접근법입니다.

나는 당신이 왜 세상의 다른 사람들처럼 할 수없고 DLL에 대한 상대 경로를 지정할 수 없는지 솔직히 이해하지 못합니다 . 예, 응용 프로그램이 설치되는 경로는 사람들의 컴퓨터마다 다르지만 기본적으로 배포시 보편적 인 규칙입니다. 이 DllImport메커니즘은이를 염두에두고 설계되었습니다.

사실, 그것을 DllImport다루는 것 조차도 아닙니다 . 편리한 관리 래퍼 (P / Invoke marshaller가 호출 LoadLibrary)를 사용하는지 여부에 관계없이 사물을 관리하는 기본 Win32 DLL로드 규칙입니다 . 이러한 규칙은 여기 에 매우 자세하게 열거되어 있지만 중요한 규칙은 여기 에서 인용됩니다.

시스템은 DLL을 검색하기 전에 다음을 확인합니다.

  • 동일한 모듈 이름을 가진 DLL이 메모리에 이미로드되어 있으면 시스템은로드 된 DLL을 사용하여 디렉토리에 상관없이 DLL을 사용합니다. 시스템은 DLL을 검색하지 않습니다.
  • DLL이 응용 프로그램이 실행중인 Windows 버전의 알려진 DLL 목록에 있으면 시스템은 알려진 DLL (및 알려진 DLL의 종속 DLL (있는 경우))의 사본을 사용합니다. 시스템은 DLL을 검색하지 않습니다.

SafeDllSearchMode활성화 된 경우 (기본값) 검색 순서는 다음과 같습니다.

  1. 애플리케이션이로드 된 디렉토리입니다.
  2. 시스템 디렉토리. GetSystemDirectory이 디렉토리의 경로를 얻으려면 함수를 사용하십시오 .
  3. 16 비트 시스템 디렉토리 이 디렉토리의 경로를 얻는 기능은 없지만 검색됩니다.
  4. Windows 디렉토리 GetWindowsDirectory이 디렉토리의 경로를 얻으려면 함수를 사용하십시오 .
  5. 현재 디렉토리
  6. PATH환경 변수에 나열된 디렉토리 . 여기에는 App Paths 레지스트리 키로 지정된 응용 프로그램 별 경로가 포함되지 않습니다. DLL 검색 경로를 계산할 때는 앱 경로 키가 사용되지 않습니다.

따라서 DLL을 시스템 DLL과 같은 이름으로 지정하지 않는 한 (어떤 상황에서도 절대로 수행해서는 안되는) 기본 검색 순서는 응용 프로그램이로드 된 디렉토리에서 찾기 시작합니다. 설치하는 동안 DLL을 설치하면 찾을 수 있습니다. 상대 경로 만 사용하면 복잡한 문제가 모두 사라집니다.

그냥 써:

[DllImport("MyAppDll.dll")] // relative path; just give the DLL's name
static extern bool MyGreatFunction(int myFirstParam, int mySecondParam);

그러나 어떤 이유로 든 작동 하지 않고 응용 프로그램이 DLL의 다른 디렉토리를 찾도록 강제 해야하는 경우 SetDllDirectory함수를 사용하여 기본 검색 경로를 수정할 수 있습니다 .
설명서에 따라 :

를 호출 한 후 SetDllDirectory표준 DLL 검색 경로는 다음과 같습니다.

  1. 애플리케이션이로드 된 디렉토리입니다.
  2. lpPathName매개 변수로 지정된 디렉토리 .
  3. 시스템 디렉토리. GetSystemDirectory이 디렉토리의 경로를 얻으려면 함수를 사용하십시오 .
  4. 16 비트 시스템 디렉토리 이 디렉토리의 경로를 얻는 기능은 없지만 검색됩니다.
  5. Windows 디렉토리 GetWindowsDirectory이 디렉토리의 경로를 얻으려면 함수를 사용하십시오 .
  6. PATH환경 변수에 나열된 디렉토리 .

DLL에서 가져온 함수를 처음 호출하기 전에이 함수를 호출하면 DLL을 찾는 데 사용되는 기본 검색 경로를 수정할 수 있습니다. 물론 장점 은 런타임에 계산되는이 함수에 동적 값을 전달할 수 있다는 것 입니다. DllImport속성 으로는 가능하지 않으므로 여전히 상대 경로 (DLL의 이름 만 해당)를 사용하고 새로운 검색 순서를 사용하여 찾을 수 있습니다.

이 기능을 P / Invoke해야합니다. 선언은 다음과 같습니다.

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);


답변

Ran의 제안을 사용하는 것보다 낫기 때문에 GetProcAddress단순히 경로가없는 파일 이름으로 함수 LoadLibrary를 호출하기 전에 호출 DllImport하면로드 된 모듈이 자동으로 사용됩니다.

이 방법을 사용하여 P / Invoke-d 함수를 수정하지 않고도 32 비트 또는 64 비트 기본 DLL을로드 할 것인지 런타임에 선택했습니다. 가져온 함수가있는 유형의 정적 생성자에로드 코드를 붙이면 모두 정상적으로 작동합니다.


답변

경로 또는 응용 프로그램의 위치에없는 .dll 파일이 필요한 경우 DllImport특성이 있고 특성은 유형, 멤버 및 기타에 설정된 메타 데이터 일 뿐이므로 그렇게 할 수 있다고 생각하지 않습니다. 언어 요소.

당신은 내가 당신이하려는 생각을 수행하는 데 도움이 수있는 대안은 기본을 사용하는 것입니다 LoadLibrary당신이 필요로하는 경로에서 .DLL를로드하기 위해, P / 호출을 통해 다음 사용 GetProcAddress당신이 필요로하는 함수에 대한 참조를 가져 오는 그 .dll에서. 그런 다음이를 사용하여 호출 할 수있는 대리자를 만듭니다.

사용하기 쉽도록이 대리자를 클래스의 필드로 설정하면 멤버 메서드를 호출하는 것처럼 보일 수 있습니다.

편집하다

다음은 작동하는 코드 스 니펫이며 의미하는 바를 보여줍니다.

class Program
{
    static void Main(string[] args)
    {
        var a = new MyClass();
        var result = a.ShowMessage();
    }
}

class FunctionLoader
{
    [DllImport("Kernel32.dll")]
    private static extern IntPtr LoadLibrary(string path);

    [DllImport("Kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    public static Delegate LoadFunction<T>(string dllPath, string functionName)
    {
        var hModule = LoadLibrary(dllPath);
        var functionAddress = GetProcAddress(hModule, functionName);
        return Marshal.GetDelegateForFunctionPointer(functionAddress, typeof (T));
    }
}

public class MyClass
{
    static MyClass()
    {
        // Load functions and set them up as delegates
        // This is just an example - you could load the .dll from any path,
        // and you could even determine the file location at runtime.
        MessageBox = (MessageBoxDelegate) 
            FunctionLoader.LoadFunction<MessageBoxDelegate>(
                @"c:\windows\system32\user32.dll", "MessageBoxA");
    }

    private delegate int MessageBoxDelegate(
        IntPtr hwnd, string title, string message, int buttons); 

    /// <summary>
    /// This is the dynamic P/Invoke alternative
    /// </summary>
    static private MessageBoxDelegate MessageBox;

    /// <summary>
    /// Example for a method that uses the "dynamic P/Invoke"
    /// </summary>
    public int ShowMessage()
    {
        // 3 means "yes/no/cancel" buttons, just to show that it works...
        return MessageBox(IntPtr.Zero, "Hello world", "Loaded dynamically", 3);
    }
}

참고 :을 사용하지 않았 으므로이 FreeLibrary코드는 완전하지 않습니다. 실제 응용 프로그램에서는 메모리 누수를 피하기 위해로드 된 모듈을 해제해야합니다.


답변

C ++ 라이브러리를 런타임에 찾을 수있는 디렉토리를 아는 한 간단합니다. 귀하의 코드에서 이것이 사실임을 분명히 알 수 있습니다. 귀하는 myDll.dll현재 내부의 것 myLibFolder현재 사용자의 임시 폴더 내의 디렉토리.

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 

이제 아래와 같이 const 문자열을 사용하여 DllImport 문을 계속 사용할 수 있습니다.

[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);

런타임에 DLLFunction함수 를 호출하기 전에 (C ++ 라이브러리에 있음) C # 코드에 다음 코드 줄을 추가하십시오.

string assemblyProbeDirectory = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
Directory.SetCurrentDirectory(assemblyProbeDirectory);

이것은 단순히 CLR에게 프로그램 실행시 얻은 디렉토리 경로에서 관리되지 않는 C ++ 라이브러리를 찾도록 지시합니다. Directory.SetCurrentDirectorycall은 응용 프로그램의 현재 작업 디렉토리를 지정된 디렉토리로 설정합니다. 당신의 경우 myDLL.dll가 나타내는 경로에 존재하는 assemblyProbeDirectory경로 다음로드받을 것이며, 원하는 기능은 P / 호출을 통해 전화를받을 것입니다.


답변

구성 파일에서 dll 경로를 설정하십시오.

<add key="dllPath" value="C:\Users\UserName\YourApp\myLibFolder\myDLL.dll" />

앱에서 dll을 호출하기 전에 다음을 수행하십시오.

string dllPath= ConfigurationManager.AppSettings["dllPath"];
   string appDirectory = Path.GetDirectoryName(dllPath);
   Directory.SetCurrentDirectory(appDirectory);

그런 다음 dll을 호출하면 아래와 같이 사용할 수 있습니다

 [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);


답변

dll이 시스템 경로의 어딘가에있는 한 DllImport는 전체 경로를 지정하지 않아도 정상적으로 작동합니다. 경로에 사용자 폴더를 임시로 추가 할 수 있습니다.


답변

모두 실패하면 DLL을 windows\system32폴더 에 넣으십시오 . 컴파일러가 찾을 것입니다. 로로드 할 DLL을 지정하십시오 : DllImport("user32.dll"..., EntryPoint = "my_unmanaged_function"원하는 관리되지 않는 함수를 C # 앱으로 가져 오도록 설정 하십시오.

 using System;
using System.Runtime.InteropServices;

class Example
{
   // Use DllImport to import the Win32 MessageBox function.

   [DllImport ("user32.dll", CharSet = CharSet.Auto)]
   public static extern int MessageBox
      (IntPtr hWnd, String text, String caption, uint type);

   static void Main()
   {
      // Call the MessageBox function using platform invoke.
      MessageBox (new IntPtr(0), "Hello, World!", "Hello Dialog", 0);
   }
}

출처 및 더 많은 DllImport예 : http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx