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


141

실제로 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"매개 변수를 원한다는 것입니다.

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


15
EXE와 동일한 폴더에 DLL을 배포하기 만하면 경로없이 DLL 이름을 지정하기 만하면됩니다. 다른 계획도 가능하지만 모두 귀찮습니다.
Hans Passant

2
문제는 그것이 MS 오피스 엑셀 추가 기능이 될 것입니다. 그래서 exe의 디렉토리에 dll을 넣는 것이 가장 좋은 해결책은
아닙니다

8
해결책이 잘못되었습니다. Windows 또는 시스템 폴더에 파일을 두지 마십시오. 그들은 이유를 위해 그 이름을 선택했습니다. 왜냐하면 그들은 Windows 시스템 파일이기 때문입니다. Windows 팀에서 Microsoft에서 일하지 않기 때문에 그 중 하나를 작성하지 않습니다. 유치원에서 허가없이 자신에게 속하지 않은 것을 사용하는 것에 대해 배운 것을 기억하고 파일을 어디에나 두십시오.
코디 그레이

해결책이 여전히 잘못되었습니다. 실제로 관리 작업 을 수행하지 않는 올바르게 작동하는 응용 프로그램 에는 관리 액세스 권한이 필요하지 않습니다. 또 다른 문제는 당신이 당신의 응용 프로그램이 실제로 알고 없다는 것입니다 해당 폴더에 설치되어 있어야합니다. 다른 곳으로 옮기거나 설치 중에 설치 경로를 변경할 수 있습니다. 하드 코딩 경로는 나쁜 행동의 전형이며 완전히 불필요합니다. 응용 프로그램의 폴더를 사용하는 경우 이것이 DLL 의 기본 검색 순서에서 첫 번째 경로입니다 . 모두 자동.
코디 그레이

3
프로그램 파일에 넣는 것은 일정하지 않습니다. 예를 들어 64 비트 시스템에는 프로그램 파일 (x86)이 있습니다.
Louis Kottmann

답변:


184

다른 답변 중 일부의 제안과 달리 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);

16
이것에 대한 또 다른 사소한 개선은 DLL 이름에서 확장명을 삭제하는 것입니다. Windows는 자동으로 추가 .dll되고 다른 시스템은 Mono (예 : .soLinux) 에서 적절한 확장을 추가합니다 . 이식성이 문제가되는 경우 도움이 될 수 있습니다.
jheddings

6
+1 SetDllDirectory. 당신은 또한 변경 Environment.CurrentDirectory하고 모든 상대 경로는 해당 경로에서 평가됩니다!
GameScripting

2
OP가 게시되기 전에도 OP는 그가 플러그인을 만들고 있음을 분명히 했으므로 DLL을 Microsoft의 프로그램 파일에 넣는 것은 일종의 시작이 아닙니다. 또한 DllDirectory 또는 CWD 프로세스를 변경하는 것은 좋은 생각이 아니므로 프로세스가 실패 할 수 있습니다. 지금 AddDllDirectory반면에 ...
Mooing Duck

3
작업 디렉토리에 의존하는 것은 잠재적으로 심각한 보안 취약점 인 @GameScripting이며 특히 수퍼 유저 권한으로 실행되는 항목에는 적합하지 않습니다. 코드를 작성하고 설계 작업을 올바르게 수행 할 가치가 있습니다.
코디 그레이

2
DllImport단순한 래퍼 이상입니다 LoadLibrary. 또한 메소드가 정의 된 어셈블리의 디렉토리를 고려합니다extern . 를 DllImport사용하여 검색 경로를 추가로 제한 할 수 있습니다 DefaultDllImportSearchPath.
Mitch

38

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

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


1
이것이 작동하는지 확실하지 않습니다. 또는 현재 버전의 프레임 워크에서 발생하는 경우.
코드 InChaos

3
@Code : 나에게 보장 된 것 : Dynamic-Link Library Search Order . 구체적으로, "검색에 영향을 미치는 요소"는 하나를 가리 킵니다.
코디 그레이

좋은. 함수 이름조차도 정적이며 컴파일 타임에 알 필요가 없기 때문에 내 솔루션에는 약간의 추가 이점이 있습니다. 동일한 서명과 다른 이름을 가진 2 개의 함수가있는 경우 내 FunctionLoader코드를 사용하여 호출 할 수 있습니다 .
Ran

이것은 내가 원하는 것 같습니다. mylibrary32.dll 및 mylibrary64.dll과 같은 파일 이름을 사용하고 싶었지만 동일한 이름이지만 다른 폴더에 파일 이름으로 살 수 있다고 생각합니다.
yoyo

27

경로 또는 응용 프로그램의 위치에없는 .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코드는 완전하지 않습니다. 실제 응용 프로그램에서는 메모리 누수를 피하기 위해로드 된 모듈을 해제해야합니다.


LoadLibrary에 대한 관리되는 대응 항목이 있습니다 (Assembly 클래스).
Luca

코드 예제가 있다면 이해하기가 더 쉬울 것입니다! ^^ (실제로 약간 안개 낀다)
Jsncrdnl

1
@Luca Piccioni : Assembly.LoadFrom을 의미하는 경우 기본 라이브러리가 아닌 .NET 어셈블리 만로드합니다. 무슨 소리 야?
Ran

1
나는 그 의미를 알고 있었지만이 한계에 대해 몰랐습니다. 한숨.
Luca

1
당연히 아니지. 정적 경로가 필요한 P / Invoke를 사용하지 않고 네이티브 dll에서 함수를 호출 할 수 있음을 보여주는 샘플 일뿐입니다.
Ran

5

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 / 호출을 통해 전화를받을 것입니다.


3
이것은 나를 위해 일했습니다. 실행중인 응용 프로그램의 "bin"디렉토리에 "모듈"폴더가 있습니다. 거기에 관리되는 dll과 관리되는 dll에 필요한 일부 관리되지 않는 dll을 배치하고 있습니다. 이 솔루션을 사용하고 app.config에서 프로브 경로를 설정하면 필요한 어셈블리를 동적으로로드 할 수 있습니다.
WBuck

Azure Functions를 사용하는 사람들 : string workingDirectory = Path.GetFullPath (Path.Combine (executionContext.FunctionDirectory, @ ".. \ bin"));
빨간 망토

4

구성 파일에서 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);

0

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


시스템 환경 변수에 배치하려고 시도했지만 여전히 비 상수로 간주됩니다 (논리적이라고 생각합니다)
Jsncrdnl

-14

모두 실패하면 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


좋아, win32 폴더 (가장 간단한 방법)를 사용하는 솔루션에 동의하지만 Visual Studio 디버거 (및 컴파일 된 응용 프로그램)에 해당 폴더에 대한 액세스 권한을 어떻게 부여합니까? (admin으로 수동으로 실행하는 것을 제외하고)
Jsncrdnl

그것이 디버깅 보조 도구 이상으로 사용되는 경우 내 책의 모든 검토 (보안 또는 기타)를 거치게됩니다.
Christian

21
이것은 매우 끔찍한 해결책입니다. 시스템 폴더는 시스템 DLL 용입니다. 이제 관리자 권한이 필요하고 게으 르기 때문에 나쁜 습관에 의존하고 있습니다.
MikeP

5
MikeP의 경우 +1,이 답변의 경우 -1 이것은 끔찍한 해결책입니다.이 작업을 수행하는 사람은 The Old New Thing 을 읽으면서 반복적으로 채찍질해야합니다 . 유치원에서 배운 것처럼 시스템 폴더는 귀하에게 속하지 않으므로 허가없이 사용해서는 안됩니다.
코디 그레이

Okok, 나는 당신에게 동의하지만 내 문제는 해결되지 않았습니다 ... 다음 위치를 추천할까요? (변수를 사용하여 설정할 수 없다는 것을 알고 있습니다-상수 문자열을 기다리고 있기 때문에- 모든 컴퓨터에서 동일한 위치를 사용해야합니까?) (또는 상수 대신 변수를 사용하여 수행 할 수있는 방법이 있습니까?)
Jsncrdnl
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.