Çalışma zamanında [DllImport] yolunu nasıl belirleyebilirim?


141

Aslında, işlevleri çağırmak için C # projem içine almak istediğiniz bir C ++ (çalışma) DLL var.

Ben böyle DLL için tam yol belirttiğinizde çalışır:

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

Sorun, yüklenebilir bir proje olacağı için, kullanıcının klasörü çalıştırılacağı bilgisayara / oturuma bağlı olarak aynı olmayacaktır (örn: pierre, paul, jack, anne, baba, ...).

Bu yüzden kodumun biraz daha genel olmasını istiyorum, şöyle:

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

Önemli olan "DllImport" DLL dizini için bir "const dize" parametresi arzu olmasıdır.

Yani sorum şu: Bu durumda ne yapılabilir?


15
DLL dosyasını EXE ile aynı klasöre dağıtın, böylece hiçbir şey yapmanız gerekmez, ancak yol olmadan DLL adını belirtin. Başka şemalar da mümkündür ancak hepsi zahmetlidir.
Hans Passant

2
Şey, bu bir MS Office Excel Add In olacak, bu yüzden exe dizinine dll koyarak bir şey yok en iyi çözüm olurdu ...
Jsncrdnl

8
Çözümünüz yanlış. Dosyaları Windows veya sistem klasörlerine yerleştirmeyin. Bu isimleri bir nedenden dolayı seçtiler: çünkü Windows sistem dosyaları içindir. Bunlardan birini oluşturmuyorsunuz çünkü Windows ekibinde Microsoft için çalışmıyorsunuz. İzinsiz olarak size ait olmayan şeyleri kullanma hakkında anaokulunda öğrendiklerinizi hatırlayın ve dosyalarınızı oradan başka bir yere koyun.
Cody Gray

Çözümünüz hala yanlış. Olmayan uygulamaları uslu aslında yönetici erişimi gerektirmemelidir idari şeyler. Diğer konu da uygulama aslında biliyorum kalmamasıdır edilmesi bu klasörde yüklü. Başka bir yere taşıyabilir veya kurulum sırasında yükleme yolunu değiştirebilirim (eğlenmek için bu tür şeyleri yaparım, sadece kötü niyetli uygulamaları kırmak için). Zor kodlama yolları, kötü davranışın özüdür ve tamamen gereksizdir. Uygulamanızın klasörünü kullanıyorsanız, DLL'ler için varsayılan arama sırasındaki ilk yol budur . Hepsi otomatik.
Cody Gray

3
program dosyalarına koymak sabit DEĞİLDİR. Örneğin 64 bit makinelerde Program Dosyası (x86) bulunur.
Louis Kottmann

Yanıtlar:


184

Diğer cevapların bazılarının önerilerinin aksine, DllImportniteliği kullanmak hala doğru yaklaşımdır.

Dürüst olmak gerekirse neden dünyadaki herkes gibi yapamıyorsunuz ve DLL için göreceli bir yol belirtmiyorum . Evet, uygulamanızın yükleneceği yol farklı kişilerin bilgisayarlarında farklılık gösterir, ancak bu dağıtım konusunda temelde evrensel bir kuraldır. DllImportMekanizma Bu düşünceyle tasarlanmıştır.

Aslında, bunu bile DllImporthalledemez. Kullanışlı yönetilen sarmalayıcıları (P / Invoke marshaller sadece çağırır LoadLibrary) kullanıp kullanmadığınızdan bağımsız olarak işleri yöneten yerel Win32 DLL yükleme kurallarıdır . Bu kurallar burada ayrıntılı olarak numaralandırılmıştır , ancak önemli kurallar burada belirtilmiştir:

Sistem bir DLL dosyasını aramadan önce aşağıdakileri kontrol eder:

  • Aynı modül adına sahip bir DLL zaten belleğe yüklenmişse, sistem hangi dizinde olursa olsun yüklü DLL'yi kullanır. Sistem DLL'yi aramaz.
  • DLL, uygulamanın çalıştığı Windows sürümü için bilinen DLL listesindeyse, sistem bilinen DLL (ve varsa bilinen DLL bağımlı DLL) kopyasını kullanır. Sistem DLL'yi aramaz.

Eğer SafeDllSearchMode(varsayılan) etkinleştirildiğinde aşağıdaki gibi arama sırası:

  1. Uygulamanın yüklendiği dizin.
  2. Sistem dizini. GetSystemDirectoryBu dizinin yolunu almak için işlevi kullanın .
  3. 16 bit sistem dizini. Bu dizinin yolunu alan bir işlev yoktur, ancak aranır.
  4. Windows dizini. GetWindowsDirectoryBu dizinin yolunu almak için işlevi kullanın .
  5. Geçerli dizin.
  6. PATHOrtam değişkeninde listelenen dizinler . Bunun, Uygulama Yolları kayıt defteri anahtarı tarafından belirtilen uygulama başına yolu içermediğini unutmayın. Uygulama yolları anahtarı, DLL arama yolu hesaplanırken kullanılmaz.

Bu nedenle, DLL'nizi bir sistem DLL'si ile aynı şekilde adlandırmıyorsanız (ki hiçbir koşulda hiçbir zaman yapmamanız gerekir), varsayılan arama sırası uygulamanızın yüklendiği dizinde aramaya başlayacaktır. DLL dosyasını yükleme sırasında oraya yerleştirirseniz bulunur. Göreli yolları kullanırsanız, tüm karmaşık sorunlar ortadan kalkar.

Sadece yaz:

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

Ancak bu herhangi bir nedenle işe yaramazsa ve uygulamayı DLL için farklı bir dizine bakmaya zorlamanız gerekiyorsa, SetDllDirectoryişlevi kullanarak varsayılan arama yolunu değiştirebilirsiniz .
Belgelere göre:

Aramadan sonra SetDllDirectorystandart DLL arama yolu:

  1. Uygulamanın yüklendiği dizin.
  2. lpPathNameParametre tarafından belirtilen dizin .
  3. Sistem dizini. GetSystemDirectoryBu dizinin yolunu almak için işlevi kullanın .
  4. 16 bit sistem dizini. Bu dizinin yolunu alan bir işlev yoktur, ancak aranır.
  5. Windows dizini. GetWindowsDirectoryBu dizinin yolunu almak için işlevi kullanın .
  6. PATHOrtam değişkeninde listelenen dizinler .

DLL'den ilk kez içe aktarılan işlevi aramadan önce bu işlevi çağırdığınız sürece, DLL'leri bulmak için kullanılan varsayılan arama yolunu değiştirebilirsiniz. Avantajı, elbette, çalışma zamanında hesaplanan bu işleve dinamik bir değer iletebilmenizdir . Bu DllImportözellik ile mümkün değildir , bu yüzden orada göreceli bir yol (yalnızca DLL'in adı) kullanacak ve sizin için bulmak için yeni arama sırasına güveneceksiniz.

Bu işlevi P / Çağırmanız gerekir. Bildirge şöyle:

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

16
Bu konuda başka bir küçük gelişme, DLL adı uzantısını bırakmak olabilir. Windows otomatik olarak ekleyecek .dllve diğer sistemler Mono altında uygun uzantıyı ekleyecektir (örneğin .soLinux'ta). Bu, taşınabilirliğin bir endişe kaynağı olması durumunda yardımcı olabilir.
jheddings

6
İçin +1 SetDllDirectory. Ayrıca değiştirebilirsiniz Environment.CurrentDirectoryve tüm göreli yollar bu yoldan değerlendirilecektir!
2013

2
Bu gönderilmeden önce bile OP bir eklenti yaptığını açıklığa kavuşturdu, bu yüzden DLL'leri Microsoft'un program dosyalarına koymak bir tür başlangıçsız. Ayrıca, DllDirectory veya CWD işleminin değiştirilmesi iyi bir fikir olmayabilir, işlemin başarısız olmasına neden olabilir. Şimdi AddDllDirectoryÖte yandan ...
Mooing Duck

3
Çalışma dizinine güvenmek potansiyel olarak ciddi bir güvenlik açığıdır, @GameScripting ve özellikle süper kullanıcı izinleriyle çalışan bir şey için tavsiye edilmez. Kodu yazmak ve tasarım işini doğru yapmak için yapmaya değer.
Cody Gray

2
Bunun DllImportbir sarıcıdan daha fazlası olduğunu unutmayın LoadLibrary. Ayrıca , externyöntemin tanımlandığı derlemenin dizinini de dikkate alır . DllImportArama yolları ek kullanılarak sınırlandırılabilir DefaultDllImportSearchPath.
Mitch

38

Daha da iyisi kullanmanın Ran'ın önerisi daha GetProcAddressbasitçe çağrısı yapmak LoadLibraryçağrılmadan önce DllImport(bir yola sahip olmayan tek bir dosya adıyla) fonksiyonları ve bunlar otomatik yüklenen modülünü kullanacağız.

Bir sürü P / Invoke-d işlevlerini değiştirmek zorunda kalmadan 32-bit veya 64-bit yerel DLL yüklemek isteyip istemediğinizi seçmek için bu yöntemi kullandım. Yükleme kodunu, içe aktarılan işlevlere sahip tür için statik bir yapıcıya yapıştırın ve hepsi iyi çalışır.


1
Bunun işe yarayacağından emin değilim. Ya da sadece çerçevenin mevcut sürümünde olursa.
CodesInChaos

3
@Code: Bana garanti veriyor: Dynamic-Link Library Arama Sırası . Özellikle, "Aramayı Etkileyen Faktörler", birinci maddeyi işaret eder.
Cody Gray

Güzel. Çözüm ismimin bile statik olması ve derleme zamanında bilinmesi gerekmediği için çözümümün ek bir avantajı daha var. Aynı imzayla ve farklı bir ada sahip 2 işleviniz varsa, FunctionLoaderkodumu kullanarak bunları çağırabilirsiniz .
koştu

Kulağa istediğim gibi geliyor. Mylibrary32.dll ve mylibrary64.dll gibi dosya adlarını kullanmayı umuyordum, ancak sanırım onlarla aynı ada sahip ancak farklı klasörlerde yaşayabilirim.
yoyo

27

Yolda veya uygulamanın konumunda olmayan bir .dll dosyasına ihtiyacınız varsa, o zaman bunu yapabileceğinizi sanmıyorum, çünkü DllImportbir özniteliktir ve öznitelikler yalnızca türler, üyeler ve diğer dil öğeleri.

Denediğimi başarmanıza yardımcı olabilecek bir alternatif, LoadLibraryihtiyacınız olan yoldan bir .dll yüklemek için P / Invoke aracılığıyla yerel'i kullanmak ve daha sonra GetProcAddressihtiyacınız olan işleve referans almak için kullanmaktır . bu .dll'den. Ardından, çağırabileceğiniz bir temsilci oluşturmak için bunları kullanın.

Kullanımı daha kolay hale getirmek için, bu temsilciyi sınıfınızdaki bir alana ayarlayabilirsiniz, böylece kullanmak bir üye yöntemini çağırmak gibi görünür.

DÜZENLE

İşte çalışan ve ne demek istediğimi gösteren bir kod snippet'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);
    }
}

Not: Ben kullanmak için uğraşmadı FreeLibrary, bu yüzden bu kod tam değil. Gerçek bir uygulamada, bellek sızıntısını önlemek için yüklü modülleri serbest bırakmaya dikkat etmelisiniz.


LoadLibrary için yönetilen karşılığı vardır (Assembly sınıfında).
Luca

Kod örneğiniz olsaydı, anlaması daha kolay olurdu! ^^ (Aslında, biraz puslu)
Jsncrdnl

1
@Luca Piccioni: Assembly.LoadFrom'u kastediyorsanız, bu yalnızca yerel derlemeleri değil .NET derlemelerini yükler. Ne demek istedin?
koştu

1
Öyle demek istiyordum, ama bu sınırlamayı bilmiyordum. İç çekmek.
Luca

1
Tabii ki değil. Bu sadece statik bir yol gerektiren P / Invoke kullanmadan yerel bir dll bir işlevi çağırabileceğini göstermek için bir örnek oldu.
Ran

5

C ++ kitaplıklarınızın çalışma zamanında bulunabileceği dizini bildiğiniz sürece, bu basit olmalıdır. Kodunuzda durumun bu olduğunu açıkça görebiliyorum. Sizin myDll.dllMevcut içeride olurdu myLibFolderGeçerli kullanıcının geçici klasör içinde dizin.

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

Şimdi aşağıda gösterildiği gibi bir const dizesi kullanarak DllImport deyimini kullanmaya devam edebilirsiniz:

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

DLLFunction(C ++ kitaplığında bulunur) işlevi çağırmadan hemen önce çalışma zamanında bu kod satırını C # koduna ekleyin:

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

Bu, CLR'ye programınızın çalışma zamanında elde ettiğiniz dizin yolundaki yönetilmeyen C ++ kitaplıklarını aramasını bildirir. Directory.SetCurrentDirectorycall, uygulamanın geçerli çalışma dizinini belirtilen dizine ayarlar. Eğer senin myDLL.dlltarafından temsil yolda mevcut olduğu assemblyProbeDirectoryyolundaki o zaman yüklenir alacak ve istenen fonksiyon p / Invoke aracılığıyla adlı.


3
Bu benim için çalıştı. Benim yürütme uygulamasının "bin" dizininde bulunan bir klasör "Modüller" var. Orada yönetilen bir dll ve yönetilen dll gerektiren bazı yönetilmeyen dll's yerleştiriyorum. Bu çözümü kullanmak VE app.config dosyamda problama yolunu ayarlamak, gerekli montajları dinamik olarak yüklememe izin veriyor.
WBuck

Azure İşlevleri kullanan kişiler için: string workingDirectory = Path.GetFullPath (Path.Combine (executionContext.FunctionDirectory, @ ".. \ bin"));
Kırmızı Başlıklı Kız

4

yapılandırma dosyasında dll yolunu ayarlama

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

uygulamasında dll çağırmadan önce aşağıdakileri yapın

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

sonra dll arayın ve aşağıdaki gibi kullanabilirsiniz

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

0

DllImport, dll sistem yolunda bir yerde bulunduğu sürece belirtilen tam yol olmadan iyi çalışır. Kullanıcının klasörünü yola geçici olarak ekleyebilirsiniz.


Sisteme yerleştirmeyi denedim Ortam değişkenleri AMA hala sabit olmayan (mantıklı, sanırım)
Jsncrdnl

-14

Hepsi başarısız olursa, DLL dosyasını windows\system32klasöre yerleştirin. Derleyici bulacaktır. Yüklenecek DLL dosyasını belirtin:, istediğiniz yönetilmeyen işlevi C # uygulamanıza aktarmak için DllImport("user32.dll"...ayarlayın EntryPoint = "my_unmanaged_function":

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

Kaynak ve hatta daha fazla DllImportörnek: http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx


Tamam, win32 klasörünü kullanma çözümünüze katılıyorum (bunu yapmanın en basit yolu), ancak bu klasöre Visual Studio hata ayıklayıcısına (ve ayrıca derlenmiş uygulamaya) nasıl erişim veriyorsunuz? (Manuel olarak yönetici olarak çalıştırılması hariç)
Jsncrdnl

Bu, hata ayıklama yardımından başka bir şey için kullanılırsa, kitabımdaki herhangi bir incelemeden (güvenlik veya başka bir şekilde) düşecektir.
Christian.K

21
Bu oldukça korkunç bir çözüm. Sistem klasörü sistem DLL'leri içindir. Şimdi yönetici ayrıcalıklarına ihtiyacınız var ve sadece tembel olduğunuz için kötü uygulamalara güveniyorsunuz.
MikeP

5
MikeP için +1, bu cevap için -1. Bu korkunç bir çözümdür, bunu yapan herkes Eski Yeni Şey'i okumak zorunda kalırken tekrar tekrar tıkanmalıdır . Tıpkı anaokulunda öğrendiğiniz gibi: sistem klasörü size ait değildir, bu yüzden izinsiz kullanmamalısınız.
Cody Gray

Tamam, sana katılıyorum, ama sorunum çözülmedi ... O zaman bana hangi yeri önerirsin? ? I) en her bilgisayarda aynı olacak bir konumu kullanabilir GEREKİR (Ya bir değişkenleri kullanmak için herhangi bir yolu yerine sabiti, olduğu, bunu gerçekleştirmek için)?
Jsncrdnl
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.