Làm cách nào tôi có thể chỉ định đường dẫn [DLLImport] khi chạy?


141

Trong thực tế, tôi đã có một DLL C ++ (đang hoạt động) mà tôi muốn nhập vào dự án C # của mình để gọi nó là các hàm.

Nó hoạt động khi tôi chỉ định đường dẫn đầy đủ đến DLL, như thế này:

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

Vấn đề là nó sẽ là một dự án có thể cài đặt được, vì vậy thư mục của người dùng sẽ không giống nhau (ví dụ: pierre, paul, jack, mum, cha, ...) tùy thuộc vào máy tính / phiên mà nó sẽ được chạy.

Vì vậy, tôi muốn mã của tôi chung chung hơn một chút, như thế này:

/* 
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);

Vấn đề lớn là "DLLImport" mong muốn một tham số "const chuỗi" cho thư mục của DLL.

Vì vậy, câu hỏi của tôi là :: Những gì có thể được thực hiện trong trường hợp này?


15
Chỉ cần triển khai DLL trong cùng thư mục với EXE để bạn không phải làm gì ngoài việc chỉ định tên DLL mà không có đường dẫn. Đề án khác là có thể nhưng tất cả đều rắc rối.
Hans Passant

2
Vấn đề là nó sẽ là một MS Office Excel Thêm vào, vì vậy tôi không đặt dll vào thư mục của exe sẽ là giải pháp tốt nhất ...
Jsncrdnl

8
Giải pháp của bạn là sai. Không đặt các tệp trong Windows hoặc các thư mục hệ thống. Họ chọn những cái tên đó vì một lý do: vì chúng dành cho các tệp hệ thống Windows. Bạn không tạo một trong số đó vì bạn không làm việc cho Microsoft trong nhóm Windows. Hãy nhớ những gì bạn học được ở trường mẫu giáo về việc sử dụng những thứ không thuộc về bạn mà không được phép và đặt các tệp của bạn ở bất cứ đâu ngoài đó.
Cody Grey

Giải pháp của bạn vẫn sai. Các ứng dụng hoạt động tốt không thực sự làm công cụ quản trị không cần truy cập quản trị. Vấn đề khác là bạn không biết ứng dụng của mình sẽ thực sự được cài đặt trong thư mục đó. Tôi có thể di chuyển nó đi nơi khác hoặc thay đổi đường dẫn cài đặt trong khi thiết lập (Tôi làm công cụ đó cho vui, chỉ để phá vỡ các ứng dụng hoạt động kém). Đường dẫn mã hóa cứng là hình ảnh thu nhỏ của hành vi xấu và hoàn toàn không cần thiết. Nếu bạn đang sử dụng thư mục của ứng dụng, thì đó là đường dẫn đầu tiên trong thứ tự tìm kiếm mặc định cho DLL. Tất cả đều tự động.
Cody Grey

3
đặt nó trong các tập tin chương trình là không đổi. Ví dụ, máy 64 bit có Tệp chương trình (x86).
Louis Kottmann

Câu trả lời:


184

Trái với những gợi ý của một số câu trả lời khác, sử dụng DllImportthuộc tính vẫn là cách tiếp cận đúng.

Tôi thực sự không hiểu tại sao bạn không thể làm giống như mọi người khác trên thế giới và chỉ định một đường dẫn tương đối đến DLL của bạn. Đúng, đường dẫn ứng dụng của bạn sẽ được cài đặt khác nhau trên các máy tính của những người khác nhau, nhưng về cơ bản đó là một quy tắc chung khi triển khai. Các DllImportcơ chế được thiết kế với điều này trong tâm trí.

Trong thực tế, nó thậm chí không DllImportxử lý nó. Đó là quy tắc tải DLL Win32 bản địa chi phối mọi thứ, bất kể bạn có đang sử dụng trình bao bọc được quản lý tiện dụng hay không (P / Invoke marshaller chỉ gọi LoadLibrary). Những quy tắc được liệt kê rất chi tiết ở đây , nhưng những quy tắc quan trọng được trích ở đây:

Trước khi hệ thống tìm kiếm một DLL, nó sẽ kiểm tra như sau:

  • Nếu một DLL có cùng tên mô-đun đã được tải trong bộ nhớ, hệ thống sẽ sử dụng DLL được tải, bất kể nó nằm trong thư mục nào. Hệ thống không tìm kiếm DLL.
  • Nếu DLL nằm trong danh sách các DLL đã biết cho phiên bản Windows mà ứng dụng đang chạy, thì hệ thống sẽ sử dụng bản sao của DLL đã biết (và DLL phụ thuộc của DLL đã biết, nếu có). Hệ thống không tìm kiếm DLL.

Nếu SafeDllSearchModeđược bật (mặc định), thứ tự tìm kiếm như sau:

  1. Thư mục mà ứng dụng được tải.
  2. Thư mục hệ thống. Sử dụng GetSystemDirectorychức năng để có được đường dẫn của thư mục này.
  3. Thư mục hệ thống 16 bit. Không có chức năng nào có được đường dẫn của thư mục này, nhưng nó được tìm kiếm.
  4. Thư mục Windows. Sử dụng GetWindowsDirectorychức năng để có được đường dẫn của thư mục này.
  5. Thư mục hiện tại.
  6. Các thư mục được liệt kê trong PATHbiến môi trường. Lưu ý rằng điều này không bao gồm đường dẫn cho mỗi ứng dụng được chỉ định bởi khóa đăng ký Đường dẫn ứng dụng. Khóa Đường dẫn ứng dụng không được sử dụng khi tính toán đường dẫn tìm kiếm DLL.

Vì vậy, trừ khi bạn đặt tên DLL của bạn giống với DLL hệ thống (mà rõ ràng bạn không nên làm, trong mọi trường hợp), thứ tự tìm kiếm mặc định sẽ bắt đầu tìm trong thư mục mà ứng dụng của bạn được tải. Nếu bạn đặt DLL ở đó trong quá trình cài đặt, nó sẽ được tìm thấy. Tất cả các vấn đề phức tạp sẽ biến mất nếu bạn chỉ sử dụng các đường dẫn tương đối.

Chỉ viết:

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

Nhưng nếu điều đó không hoạt động vì bất kỳ lý do gì và bạn cần buộc ứng dụng tìm trong một thư mục khác cho DLL, bạn có thể sửa đổi đường dẫn tìm kiếm mặc định bằng cách sử dụng SetDllDirectoryhàm .
Lưu ý rằng, theo tài liệu:

Sau khi gọi SetDllDirectory, đường dẫn tìm kiếm DLL tiêu chuẩn là:

  1. Thư mục mà ứng dụng được tải.
  2. Thư mục được chỉ định bởi lpPathNametham số.
  3. Thư mục hệ thống. Sử dụng GetSystemDirectorychức năng để có được đường dẫn của thư mục này.
  4. Thư mục hệ thống 16 bit. Không có chức năng nào có được đường dẫn của thư mục này, nhưng nó được tìm kiếm.
  5. Thư mục Windows. Sử dụng GetWindowsDirectorychức năng để có được đường dẫn của thư mục này.
  6. Các thư mục được liệt kê trong PATHbiến môi trường.

Vì vậy, miễn là bạn gọi hàm này trước khi bạn gọi hàm được nhập từ DLL lần đầu tiên, bạn có thể sửa đổi đường dẫn tìm kiếm mặc định được sử dụng để định vị DLL. Tất nhiên, lợi ích là bạn có thể chuyển một giá trị động cho hàm này được tính toán trong thời gian chạy. Điều đó là không thể với DllImportthuộc tính, vì vậy bạn vẫn sẽ sử dụng một đường dẫn tương đối (chỉ tên của DLL) ở đó và dựa vào thứ tự tìm kiếm mới để tìm nó cho bạn.

Bạn sẽ phải P / Gọi chức năng này. Tuyên bố trông như thế này:

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

16
Một cải tiến nhỏ khác về điều này có thể là bỏ phần mở rộng khỏi tên DLL. Windows sẽ tự động thêm .dllvà các hệ thống khác sẽ thêm tiện ích mở rộng phù hợp trong Mono (ví dụ: .sotrên Linux). Điều này có thể giúp nếu tính di động là một mối quan tâm.
jheddings

6
+1 cho SetDllDirectory. Bạn cũng có thể thay đổi Environment.CurrentDirectoryvà tất cả các đường dẫn tương đối sẽ được đánh giá từ đường dẫn đó!
GameScripting

2
Ngay cả trước khi điều này được đăng, OP đã làm rõ rằng anh ta đang tạo một plugin, vì vậy việc đưa các tệp DLL vào các tệp chương trình của Microsoft là một loại không bắt đầu. Ngoài ra, việc thay đổi quy trình DLLDirectory hoặc CWD có thể không phải là một ý tưởng hay, chúng có thể khiến quy trình thất bại. Bây giờ AddDllDirectorymặt khác ...
Mooing Duck

3
Dựa vào thư mục làm việc là một lỗ hổng bảo mật nghiêm trọng tiềm tàng, @GameScripting, và đặc biệt không được khuyến khích cho một cái gì đó chạy với quyền siêu người dùng. Đáng để viết mã và thực hiện công việc thiết kế để làm cho đúng.
Cody Grey

2
Lưu ý rằng DllImportkhông chỉ là một trình bao bọc trên LoadLibrary. Nó cũng xem xét thư mục của hội đồngextern phương thức được định nghĩa trong . Các DllImportđường dẫn tìm kiếm có thể được giới hạn thêm bằng cách sử dụng DefaultDllImportSearchPath.
Mitch

38

Thậm chí tốt hơn đề xuất sử dụng của Ran GetProcAddress, chỉ cần thực hiện cuộc gọi đến LoadLibrarytrước bất kỳ cuộc gọi nào đến các DllImportchức năng (chỉ có tên tệp không có đường dẫn) và chúng sẽ tự động sử dụng mô-đun được tải.

Tôi đã sử dụng phương pháp này để chọn trong thời gian chạy xem có tải DLL gốc 32 bit hay 64 bit mà không phải sửa đổi một loạt các hàm P / Invoke-d. Dán mã tải trong một hàm tạo tĩnh cho loại có các hàm được nhập và tất cả sẽ hoạt động tốt.


1
Tôi không chắc chắn nếu điều này được đảm bảo để làm việc. Hoặc nếu nó chỉ xảy ra trên phiên bản hiện tại của khung.
CodeInChaos

3
@ Mã: Có vẻ như được đảm bảo với tôi: Thứ tự tìm kiếm thư viện liên kết động . Cụ thể, "Các yếu tố ảnh hưởng đến tìm kiếm", điểm một.
Cody Grey

Đẹp. Vâng, giải pháp của tôi có một lợi thế bổ sung nhỏ, vì ngay cả tên hàm cũng không phải là tĩnh và được biết đến tại thời điểm biên dịch. Nếu bạn có 2 hàm có cùng chữ ký và tên khác nhau, bạn có thể gọi chúng bằng FunctionLoadermã của tôi .
Ran

Điều này nghe có vẻ như những gì tôi muốn. Tôi đã hy vọng sử dụng tên tệp như myl Library32.dll và myl Library64.dll, nhưng tôi đoán tôi có thể sống với chúng có cùng tên nhưng trong các thư mục khác nhau.
yoyo

27

Nếu bạn cần một tệp dll không có trên đường dẫn hoặc vị trí của ứng dụng, thì tôi không nghĩ bạn có thể làm điều đó, bởi vì đó DllImportlà một thuộc tính và các thuộc tính chỉ là siêu dữ liệu được đặt trên các loại, thành viên và các loại khác yếu tố ngôn ngữ.

Một giải pháp thay thế có thể giúp bạn thực hiện những gì tôi nghĩ bạn đang cố gắng là sử dụng nguồn gốc LoadLibrarythông qua P / Gọi, để tải một tệp từ đường dẫn bạn cần, sau đó sử dụngGetProcAddress để có được tham chiếu đến chức năng bạn cần từ đó. Sau đó sử dụng chúng để tạo một đại biểu mà bạn có thể gọi.

Để làm cho nó dễ sử dụng hơn, sau đó bạn có thể đặt đại biểu này thành một trường trong lớp của bạn, để việc sử dụng nó trông giống như gọi một phương thức thành viên.

BIÊN TẬP

Đây là một đoạn mã hoạt động và hiển thị những gì tôi muốn nói.

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);
    }
}

Lưu ý: Tôi không bận tâm sử dụng FreeLibrary, vì vậy mã này không đầy đủ. Trong một ứng dụng thực tế, bạn nên cẩn thận giải phóng các mô-đun được tải để tránh rò rỉ bộ nhớ.


Có đối tác được quản lý cho LoadL Library (trong lớp hội).
Luca

Nếu bạn có một số ví dụ về mã, tôi sẽ dễ hiểu hơn! ^^ (Thật ra, nó hơi mù sương)
Jsncrdnl

1
@Luca Piccioni: Nếu bạn có nghĩa là Association.LoadFrom, thì điều này chỉ tải các cụm .NET chứ không phải các thư viện riêng. Bạn có ý gì?
Ran

1
Tôi có ý đó, nhưng tôi không biết về giới hạn này. Thở dài.
Luca

1
Dĩ nhiên là không. Đó chỉ là một mẫu để cho thấy rằng bạn có thể gọi một hàm trong dll gốc mà không cần sử dụng P / Invoke yêu cầu một đường dẫn tĩnh.
Ran

5

Miễn là bạn biết thư mục nơi thư viện C ++ của bạn có thể được tìm thấy trong thời gian chạy, việc này sẽ đơn giản. Tôi có thể thấy rõ đây là trường hợp trong mã của bạn. Bạn myDll.dllsẽ có mặt trong myLibFolderthư mục bên trong thư mục tạm thời của người dùng hiện tại.

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

Bây giờ bạn có thể tiếp tục sử dụng câu lệnh DLLImport bằng chuỗi const như dưới đây:

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

Chỉ trong thời gian chạy trước khi bạn gọi DLLFunctionhàm (có trong thư viện C ++), hãy thêm dòng mã này vào mã C #:

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

Điều này chỉ đơn giản là hướng dẫn CLR tìm kiếm các thư viện C ++ không được quản lý tại đường dẫn thư mục mà bạn có được trong thời gian chạy chương trình. Directory.SetCurrentDirectorycuộc gọi đặt thư mục làm việc hiện tại của ứng dụng vào thư mục được chỉ định. Nếu bạn myDLL.dllcó mặt tại đường dẫn được biểu thị bằng assemblyProbeDirectoryđường dẫn thì nó sẽ được tải và hàm mong muốn sẽ được gọi thông qua p / invoke.


3
Điều này làm việc cho tôi. Tôi có một thư mục "Mô-đun" nằm trong thư mục "bin" của ứng dụng thực thi của mình. Ở đó tôi đang đặt một dll được quản lý và một số dll không được quản lý mà dll được quản lý yêu cầu. Sử dụng giải pháp này VÀ đặt đường dẫn thăm dò trong app.config cho phép tôi tải động các cụm yêu cầu.
WBuck

Đối với những người sử dụng Hàm Azure: chuỗi workDirectory = Path.GetFullPath (Path.Combine (execContext.FeftDirectory, @ ".. \ bin"));
Red Hood Hood

4

Đặt đường dẫn dll trong tệp cấu hình

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

trước khi gọi dll trong ứng dụng của bạn, hãy làm như sau

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

sau đó gọi dll và bạn có thể sử dụng như dưới đây

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

0

DLLImport sẽ hoạt động tốt mà không cần đường dẫn hoàn chỉnh được chỉ định miễn là dll nằm ở đâu đó trên đường dẫn hệ thống. Bạn có thể tạm thời thêm thư mục người dùng vào đường dẫn.


Tôi đã thử đặt nó trong hệ thống Các biến môi trường NHƯNG nó vẫn được coi là không đổi (logic, tôi nghĩ vậy)
Jsncrdnl

-14

Nếu tất cả đều thất bại, chỉ cần đặt DLL vào windows\system32thư mục. Trình biên dịch sẽ tìm thấy nó. Chỉ định DLL để tải từ với : DllImport("user32.dll"..., được đặt EntryPoint = "my_unmanaged_function"để nhập chức năng không được quản lý mong muốn của bạn vào ứng dụng C # của bạn:

 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);    
   }
}

Nguồn và thậm chí nhiều DllImportví dụ khác: http://msdn.microsoft.com/en-us/l Library / aa288468 ( v = vs.71) .aspx


Ok, tôi đồng ý với giải pháp sử dụng thư mục win32 của bạn (cách đơn giản nhất để thực hiện) nhưng làm thế nào để bạn cấp quyền truy cập vào thư mục đó cho trình gỡ lỗi Visual Studio (và cả ứng dụng đã biên dịch)? (Ngoại trừ việc chạy thủ công với tư cách quản trị viên)
Jsncrdnl

Nếu nó được sử dụng cho bất kỳ thứ gì ngoài một công cụ gỡ lỗi, nó sẽ rơi vào bất kỳ đánh giá nào (bảo mật hoặc cách khác) trong cuốn sách của tôi.
Christian.K

21
Đây là một giải pháp khá khủng khiếp. Thư mục hệ thống dành cho DLL hệ thống . Bây giờ bạn yêu cầu đặc quyền của quản trị viên và đang dựa vào các thực tiễn xấu chỉ vì bạn lười biếng.
MikeP

5
+1 cho MikeP, -1 cho câu trả lời này. Đây là một giải pháp khủng khiếp, bất cứ ai làm điều này nên bị đánh đập liên tục trong khi bị buộc phải đọc The Old New Thing . Giống như bạn đã học ở mẫu giáo: thư mục hệ thống không thuộc về bạn, vì vậy bạn không nên sử dụng nó mà không được phép.
Cody Grey

Okok, tôi đồng ý với bạn, nhưng vấn đề của tôi không được giải quyết nên ... Vị trí nào bạn sẽ giới thiệu cho tôi sau đó? (Biết rằng tôi không thể sử dụng các biến để thiết lập nó - Bởi vì nó đang chờ một chuỗi không đổi- vì vậy rằng tôi PHẢI sử dụng một vị trí giống nhau trên mọi máy tính?) (Hoặc có cách nào để sử dụng một biến, thay vì hằng số, để thực hiện nó không?)
Jsncrdnl
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.