Bagaimana cara menentukan jalur [DllImport] saat runtime?

141

Bahkan, saya mendapat DLL C ++ (berfungsi) yang ingin saya impor ke proyek C # untuk memanggil fungsinya.

Itu berfungsi ketika saya menentukan path lengkap ke DLL, seperti ini:

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

Masalahnya adalah bahwa itu akan menjadi proyek yang dapat diinstal, sehingga folder pengguna tidak akan sama (mis: pierre, paul, jack, ibu, ayah, ...) tergantung komputer / sesi di mana ia akan dijalankan.

Jadi saya ingin kode saya menjadi sedikit lebih umum, seperti ini:

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

Masalahnya adalah "DllImport" menginginkan parameter "const string" untuk direktori DLL.

Jadi pertanyaan saya adalah :: Apa yang bisa dilakukan dalam kasus ini?

Jsncrdnl
sumber
15
Cukup gunakan DLL di folder yang sama dengan EXE sehingga Anda tidak perlu melakukan apa pun selain menentukan nama DLL tanpa jalur. Skema lain dimungkinkan tetapi semuanya merepotkan.
Hans Passant
2
Masalahnya adalah itu akan menjadi MS Office Excel Add In, jadi saya tidak menempatkan dll di direktori exe akan menjadi solusi terbaik ...
Jsncrdnl
8
Solusi Anda salah. Jangan letakkan file di folder Windows atau sistem. Mereka memilih nama-nama itu karena suatu alasan: karena itu untuk file sistem Windows. Anda tidak membuat salah satunya karena Anda tidak bekerja untuk Microsoft di tim Windows. Ingat apa yang Anda pelajari di TK tentang menggunakan hal-hal yang bukan milik Anda tanpa izin, dan letakkan file Anda di mana pun kecuali di sana.
Cody Grey
Solusi Anda masih salah. Aplikasi berperilaku baik yang tidak benar - benar melakukan hal-hal administratif seharusnya tidak memerlukan akses administratif. Masalah lainnya adalah Anda tidak tahu aplikasi Anda sebenarnya akan diinstal di folder itu. Saya mungkin memindahkannya di tempat lain, atau mengubah jalur instal selama pengaturan (saya melakukan hal-hal semacam itu untuk bersenang-senang, hanya untuk memecah aplikasi yang berperilaku buruk). Jalur hard-coding adalah lambang perilaku buruk, dan itu sama sekali tidak perlu. Jika Anda menggunakan folder aplikasi Anda, maka itu adalah jalur pertama dalam urutan pencarian default untuk DLL. Semua otomatis.
Cody Grey
3
memasukkannya ke dalam file program TIDAK konstan. Mesin 64bit memiliki File Program (x86) sebagai gantinya, misalnya.
Louis Kottmann

Jawaban:

184

Bertentangan dengan saran dari beberapa jawaban lain, penggunaan DllImportatribut masih merupakan pendekatan yang benar.

Jujur saya tidak mengerti mengapa Anda tidak bisa melakukan seperti orang lain di dunia dan menentukan jalur relatif ke DLL Anda. Ya, jalur di mana aplikasi Anda akan diinstal berbeda pada komputer orang yang berbeda, tapi itu pada dasarnya aturan universal ketika datang ke penyebaran. The DllImportMekanisme dirancang dengan pikiran ini.

Bahkan, itu bahkan tidak DllImportmenanganinya. Ini adalah aturan pemuatan asli Win32 DLL yang mengatur berbagai hal, terlepas dari apakah Anda menggunakan pembungkus yang mudah ditangani (marshaller P / Invoke hanya menelepon LoadLibrary). Aturan-aturan itu disebutkan dengan sangat rinci di sini , tetapi yang penting dikutip di sini:

Sebelum sistem mencari DLL, ia memeriksa yang berikut:

  • Jika DLL dengan nama modul yang sama sudah dimuat dalam memori, sistem menggunakan DLL yang dimuat, tidak peduli di mana direktori itu berada. Sistem tidak mencari DLL.
  • Jika DLL ada di daftar DLL yang dikenal untuk versi Windows di mana aplikasi berjalan, sistem menggunakan salinan DLL yang dikenal (dan DLL dependen DLL yang diketahui, jika ada). Sistem tidak mencari DLL.

Jika SafeDllSearchModediaktifkan (default), urutan pencarian adalah sebagai berikut:

  1. Direktori tempat aplikasi dimuat.
  2. Direktori sistem. Gunakan GetSystemDirectoryfungsi untuk mendapatkan jalur direktori ini.
  3. Direktori sistem 16-bit. Tidak ada fungsi yang mendapatkan jalur direktori ini, tetapi sedang dicari.
  4. Direktori Windows. Gunakan GetWindowsDirectoryfungsi untuk mendapatkan jalur direktori ini.
  5. Direktori saat ini.
  6. Direktori yang tercantum dalam PATHvariabel lingkungan. Perhatikan bahwa ini tidak termasuk jalur per-aplikasi yang ditentukan oleh kunci registri App Paths. Kunci App Paths tidak digunakan saat menghitung jalur pencarian DLL.

Jadi, kecuali jika Anda menamai DLL Anda dengan hal yang sama dengan DLL sistem (yang seharusnya tidak Anda lakukan, dalam keadaan apa pun), urutan pencarian default akan mulai mencari di direktori tempat aplikasi Anda dimuat. Jika Anda menempatkan DLL di sana selama instalasi, itu akan ditemukan. Semua masalah rumit hilang jika Anda hanya menggunakan jalur relatif.

Tulis saja:

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

Tetapi jika itu tidak berhasil karena alasan apa pun, dan Anda perlu memaksa aplikasi untuk mencari di direktori lain untuk DLL, Anda dapat memodifikasi jalur pencarian default menggunakan SetDllDirectoryfungsi .
Perhatikan bahwa, sesuai dokumentasi:

Setelah menelepon SetDllDirectory, jalur pencarian DLL standar adalah:

  1. Direktori tempat aplikasi dimuat.
  2. Direktori ditentukan oleh lpPathNameparameter.
  3. Direktori sistem. Gunakan GetSystemDirectoryfungsi untuk mendapatkan jalur direktori ini.
  4. Direktori sistem 16-bit. Tidak ada fungsi yang mendapatkan jalur direktori ini, tetapi sedang dicari.
  5. Direktori Windows. Gunakan GetWindowsDirectoryfungsi untuk mendapatkan jalur direktori ini.
  6. Direktori yang tercantum dalam PATHvariabel lingkungan.

Jadi, selama Anda memanggil fungsi ini sebelum Anda memanggil fungsi yang diimpor dari DLL untuk pertama kalinya, Anda dapat memodifikasi jalur pencarian default yang digunakan untuk menemukan DLL. Manfaatnya, tentu saja, adalah Anda dapat memberikan nilai dinamis ke fungsi ini yang dihitung pada saat dijalankan. Itu tidak mungkin dengan DllImportatribut, jadi Anda masih akan menggunakan jalur relatif (hanya nama DLL) di sana, dan bergantung pada urutan pencarian baru untuk menemukannya untuk Anda.

Anda harus P / Aktifkan fungsi ini. Deklarasi terlihat seperti ini:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
Cody Grey
sumber
16
Perbaikan kecil lain pada ini mungkin untuk menjatuhkan ekstensi dari nama DLL. Windows akan secara otomatis menambahkan .dlldan sistem lain akan menambahkan ekstensi yang sesuai di bawah Mono (misalnya .sodi Linux). Ini dapat membantu jika portabilitas menjadi perhatian.
jheddings
6
+1 untuk SetDllDirectory. Anda juga dapat mengubah Environment.CurrentDirectorydan semua jalur relatif akan dievaluasi dari jalur itu!
GameScripting
2
Bahkan sebelum ini diposting, OP mengklarifikasi bahwa dia membuat plugin, jadi menempatkan DLL di file program Microsoft adalah semacam non-starter. Juga, mengubah proses DllDirectory atau CWD mungkin bukan ide yang baik, mereka dapat menyebabkan proses gagal. Sekarang AddDllDirectorydi sisi lain ...
Mooing Duck
3
Mengandalkan direktori kerja adalah kerentanan keamanan yang berpotensi serius, @GameScripting, dan terutama keliru untuk sesuatu yang berjalan dengan izin pengguna super. Layak menulis kode dan melakukan pekerjaan desain untuk memperbaikinya.
Cody Gray
2
Perhatikan bahwa DllImportini lebih dari sekadar bungkus LoadLibrary. Ini juga mempertimbangkan direktori perakitan externmetode didefinisikan . Jalur DllImportpencarian juga dapat dibatasi menggunakan DefaultDllImportSearchPath.
Mitch
38

Bahkan lebih baik daripada saran Ran untuk menggunakan GetProcAddress, cukup buat panggilan LoadLibrarysebelum panggilan ke DllImportfungsi (hanya dengan nama file tanpa jalur) dan mereka akan menggunakan modul yang dimuat secara otomatis.

Saya telah menggunakan metode ini untuk memilih pada saat runtime apakah akan memuat DLL asli 32-bit atau 64-bit tanpa harus memodifikasi banyak fungsi P / Invoke-d. Masukkan kode pemuatan ke konstruktor statis untuk tipe yang memiliki fungsi yang diimpor dan semuanya akan berfungsi dengan baik.

MikeP
sumber
1
Saya tidak yakin apakah ini dijamin berfungsi. Atau jika itu hanya terjadi pada versi kerangka saat ini.
CodesInChaos
3
@ Kode: Tampaknya dijamin bagi saya: Urutan Pencarian Perpustakaan Dynamic-Link . Secara khusus, "Faktor-Faktor Yang Mempengaruhi Pencarian", poin satu.
Cody Gray
Bagus. Yah solusi saya memiliki keunggulan tambahan kecil, karena bahkan nama fungsi tidak harus statis dan dikenal pada waktu kompilasi. Jika Anda memiliki 2 fungsi dengan tanda tangan yang sama dan nama yang berbeda, Anda dapat memintanya menggunakan FunctionLoaderkode saya .
Berlari
Ini kedengarannya seperti yang saya inginkan. Saya berharap untuk menggunakan nama file seperti mylibrary32.dll dan mylibrary64.dll, tapi saya rasa saya bisa hidup dengan mereka yang memiliki nama yang sama tetapi dalam folder yang berbeda.
yoyo
27

Jika Anda memerlukan file .dll yang tidak ada di jalur atau di lokasi aplikasi, maka saya tidak berpikir Anda bisa melakukan itu, karena DllImportmerupakan atribut, dan atribut hanya metadata yang ditetapkan pada tipe, anggota dan lainnya elemen bahasa.

Alternatif yang dapat membantu Anda mencapai apa yang saya pikir Anda coba, adalah menggunakan asli LoadLibrarymelalui P / Invoke, untuk memuat .dll dari jalur yang Anda butuhkan, dan kemudian gunakan GetProcAddressuntuk mendapatkan referensi ke fungsi yang Anda butuhkan. dari itu. Kemudian gunakan ini untuk membuat delegasi yang dapat Anda panggil.

Untuk membuatnya lebih mudah digunakan, Anda dapat mengatur delegasi ini ke bidang di kelas Anda, sehingga menggunakannya tampak seperti memanggil metode anggota.

EDIT

Berikut ini cuplikan kode yang berfungsi, dan menunjukkan apa yang saya maksud.

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

Catatan: Saya tidak repot menggunakan FreeLibrary, jadi kode ini tidak lengkap. Dalam aplikasi nyata, Anda harus berhati-hati untuk melepaskan modul yang dimuat untuk menghindari kebocoran memori.

Ran
sumber
Ada mitra yang dikelola untuk LoadLibrary (di kelas Majelis).
Luca
Jika Anda memiliki beberapa contoh kode, akan lebih mudah bagi saya untuk mengerti! ^^ (Sebenarnya, ini agak berkabut)
Jsncrdnl
1
@Luca Piccioni: Jika Anda bermaksud Assembly.LoadFrom, ini hanya memuat .NET assemblies, bukan library asli. Apa yang kamu maksud?
Berlari
1
Saya memaksudkan itu, tetapi saya tidak tahu tentang batasan ini. Mendesah.
Luca
1
Tentu saja tidak. Itu hanya contoh untuk menunjukkan bahwa Anda dapat memanggil fungsi dalam dll asli tanpa menggunakan P / Invoke yang membutuhkan jalur statis.
Berlari
5

Selama Anda tahu direktori tempat pustaka C ++ Anda dapat ditemukan pada saat run time, ini harus sederhana. Saya dapat dengan jelas melihat bahwa ini adalah kasus dalam kode Anda. Anda myDll.dllakan berada di dalam myLibFolderdirektori di dalam folder sementara pengguna saat ini.

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

Sekarang Anda dapat terus menggunakan pernyataan DllImport menggunakan string const seperti yang ditunjukkan di bawah ini:

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

Tepat pada saat dijalankan sebelum Anda memanggil DLLFunctionfungsi (ada di perpustakaan C ++) tambahkan baris kode ini dalam kode C #:

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

Ini hanya menginstruksikan CLR untuk mencari pustaka C ++ yang tidak dikelola di jalur direktori yang Anda peroleh saat menjalankan program Anda. Directory.SetCurrentDirectorypanggilan mengatur direktori kerja aplikasi saat ini ke direktori yang ditentukan. Jika Anda myDLL.dllhadir di path yang diwakili oleh assemblyProbeDirectorypath maka akan dimuat dan fungsi yang diinginkan akan dipanggil melalui p / invoke.

RBT
sumber
3
Ini berhasil untuk saya. Saya memiliki folder "Modul" yang terletak di direktori "tempat sampah" aplikasi saya yang sedang dieksekusi. Di sana saya menempatkan dll yang dikelola dan beberapa dll yang tidak dikelola yang membutuhkan dll dikelola. Menggunakan solusi ini DAN mengatur jalur probing di app.config saya memungkinkan saya untuk memuat rakitan yang diperlukan secara dinamis.
WBuck
Untuk orang yang menggunakan Azure Functions: string workingDirectory = Path.GetFullPath (Path.Combine (executionContext.FunctionDirectory, @ ".. \ bin"));
Red Riding Hood
4

atur jalur dll dalam file konfigurasi

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

sebelum memanggil dll di aplikasi Anda, lakukan hal berikut

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

lalu panggil dll dan Anda bisa gunakan seperti di bawah ini

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

DllImport akan bekerja dengan baik tanpa jalur lengkap yang ditentukan selama dll terletak di suatu tempat di jalur sistem. Anda mungkin dapat menambahkan folder pengguna untuk sementara waktu.

Mike W
sumber
Saya mencoba menempatkannya dalam sistem variabel Lingkungan TETAPI masih dianggap sebagai tidak konstan (logis, saya pikir)
Jsncrdnl
-14

Jika semuanya gagal, cukup letakkan DLL di windows\system32folder. Kompiler akan menemukannya. Tentukan DLL untuk memuat dari dengan:, DllImport("user32.dll"...setel EntryPoint = "my_unmanaged_function"untuk mengimpor fungsi yang tidak dikelola yang Anda inginkan ke aplikasi 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);    
   }
}

Sumber dan bahkan lebih banyak DllImportcontoh: http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx

Perancang Perangkat Lunak
sumber
Ok, saya setuju dengan solusi Anda menggunakan folder win32 (cara paling sederhana untuk melakukannya) tetapi bagaimana Anda memberikan akses ke folder itu ke Visual Studio debugger (dan juga ke aplikasi yang dikompilasi)? (Kecuali secara manual menjalankannya sebagai admin)
Jsncrdnl
Jika itu digunakan untuk sesuatu yang lebih dari bantuan debugging, itu akan jatuh melalui ulasan apa pun (keamanan atau sebaliknya) dalam buku saya.
Christian.K
21
Ini adalah solusi yang cukup mengerikan. Folder sistem adalah untuk DLL sistem . Sekarang Anda memerlukan hak admin dan mengandalkan praktik buruk hanya karena Anda malas.
MikeP
5
+1 untuk MikeP, -1 untuk jawaban ini. Ini adalah solusi yang mengerikan, siapa pun yang melakukan ini harus dicambuk berulang kali saat dipaksa membaca The Old New Thing . Seperti yang Anda pelajari di taman kanak-kanak: folder sistem bukan milik Anda, jadi Anda tidak boleh menggunakannya tanpa izin.
Cody Grey
Okok, saya setuju dengan Anda, tetapi masalah saya belum teratasi jadi ... Lokasi mana yang akan Anda rekomendasikan kepada saya? (Mengetahui bahwa saya tidak dapat menggunakan variabel untuk mengaturnya -Karena itu sedang menunggu string konstan-, jadi bahwa saya HARUS menggunakan lokasi yang akan sama di setiap komputer?) (Atau apakah ada cara untuk menggunakan variabel, daripada konstanta, untuk melakukan itu?)
Jsncrdnl