実行時に[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
DLLをEXEと同じフォルダーに配置するだけで、パスなしでDLL名を指定するだけで、何もする必要はありません。他のスキームも可能ですが、すべて面倒です。
ハンスパッサント2012年

2
これは、MS Office Excelアドインになるので、DLLをexeのディレクトリに配置するのが最善の解決策ではない...
Jsncrdnl

8
あなたの解決策は間違っています。Windowsまたはシステムフォルダーにファイルを配置しないでください。これらの名前を選んだ理由は、Windowsシステムファイル用だからです。あなたはWindowsチームでMicrosoftのために働いていないので、それらの1つを作成していません。幼稚園で自分が所有していないものを許可なく使用することについて学んだことを思い出し、ファイルをそこ以外の場所に置きます。
コーディグレイ

あなたの解決策はまだ間違っています。実際に管理作業を行わない適切に動作するアプリケーションは、管理アクセスを必要としません。他の問題は、あなたのアプリケーションが実際になるかわからないということであることがそのフォルダにインストールされています。別の場所に移動したり、セットアップ中にインストールパスを変更したりする場合があります(私は、この種のことを面白くするために行います。パスをハードコーディングすることは、不正な動作の縮図であり、完全に不要です。アプリケーションのフォルダーを使用している場合、それがDLLのデフォルトの検索順序の最初のパスになります。すべて自動。
コーディグレイ

3
プログラムファイルに置くことは一定ではありません。たとえば、64ビットマシンには、代わりにプログラムファイル(x86)があります。
Louis Kottmann、2012年

回答:


184

他のいくつかの回答による提案とは異なり、DllImport属性を使用することは依然として正しいアプローチです。

正直なところ、世界中の人と同じようにできない理由を理解できず、DLLへの相対パスを指定します。はい、アプリケーションがインストールされるパスはユーザーのコンピューターによって異なりますが、それは基本的に、展開に関しては一般的なルールです。DllImportメカニズムはこれを念頭において設計されています。

実際には、それDllImportを処理することすらありません。便利なマネージラッパー(P / Invokeマーシャラーがを呼び出すだけLoadLibrary)を使用しているかどうかに関係なく、それはWin32 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検索パスを計算する場合、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
これに関するもう1つの小さな改善は、DLL名から拡張子を削除することです。Windowsは自動的に追加し.dll、他のシステムはMonoの下に適切な拡張子を追加します(.soLinuxなど)。これは、移植性が懸念される場合に役立ちます。
jheddings 2012年

6
の+1 SetDllDirectory。変更することもできEnvironment.CurrentDirectory、すべての相対パスはそのパスから評価されます。
GameScripting 2013

2
これが投稿される前でも、OPはプラグインを作成していることを明らかにしているため、MicrosoftのプログラムファイルにDLLを配置するのは簡単ではありません。また、プロセスDllDirectoryまたはCWDを変更することはお勧めできません。プロセスが失敗する可能性があります。さてAddDllDirectory一方、...
Mooingダック

3
作業ディレクトリに依存することは、潜在的に深刻なセキュリティの脆弱性である@GameScriptingであり、特にスーパーユーザー権限で実行されているものには不適切です。コードを書き、それを正しくするための設計作業を行う価値があります。
コーディグレイ

2
DllImport上の単なるラッパー以上のものですLoadLibrary。またexternメソッドがで定義されているアセンブリのディレクトリ考慮ます。を使用して、DllImport検索パスをさらに制限できますDefaultDllImportSearchPath
ミッチ

38

さらに良いことに使っての蘭の提案よりもGetProcAddress、単純に電話をかけることLoadLibraryへの呼び出しの前にDllImport(パスを含まないファイル名だけで)機能と、それらは自動的にロードされたモジュールを使用します。

このメソッドを使用して、一連のP / Invoke-d関数を変更せずに、実行時に32ビットまたは64ビットのネイティブDLLをロードするかどうかを選択しました。ロードされたコードを、インポートされた関数を持つ型の静的コンストラクターに貼り付ければ、すべて正常に動作します。


1
これが機能することが保証されているかどうかはわかりません。または、フレームワークの現在のバージョンで発生した場合。
CodesInChaos

3
@コード:保証されているようです:Dynamic-Link Library Search Order。具体的には、「検索に影響を与える要因」を挙げます。
コーディグレイ

いいね。関数名でさえ静的でコンパイル時に既知である必要がないので、まあ私の解決策には小さな追加の利点があります。同じシグネチャと異なる名前の2つの関数がある場合、私のFunctionLoaderコードを使用してそれらを呼び出すことができます。
2012年

これは私が欲しいもののように思えます。私はmylibrary32.dllやmylibrary64.dllのようなファイル名を使用することを望んでいましたが、同じ名前で別のフォルダーに存在することもできると思います。
ヨーヨー、2015

27

パスまたはアプリケーションの場所にない.dllファイルが必要な場合は、それができるとは思いません。DllImport属性は、属性であり、属性は、型、メンバー、その他に設定されているメタデータのみです言語要素。

私があなたがしようとしていることを達成するのを助けることができる代替手段は、LoadLibrary必要なパスから.dllをロードするためにP / Invokeを介してネイティブを使用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アセンブリのみをロードし、ネイティブライブラリはロードしません。どういう意味?

1
私はそれを意味していましたが、この制限については知りませんでした。はぁ。
ルカ

1
もちろん違います。これは、静的パスを必要とするP / Invokeを使用せずにネイティブDLLの関数を呼び出すことができることを示すためのサンプルにすぎません。

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

これは単に、プログラムの実行時に取得したディレクトリパスでアンマネージC ++ライブラリを探すようにCLRに指示します。Directory.SetCurrentDirectory呼び出しは、アプリケーションの現在の作業ディレクトリを指定されたディレクトリに設定します。あなたの場合はmyDLL.dll、パスに存在しているが、によって表されるassemblyProbeDirectoryパス、それがロードされますし、所望の機能は、p /呼び出しによって呼び出されます。


3
これでうまくいきました。実行中のアプリケーションの「bin」ディレクトリに「Modules」というフォルダがあります。マネージdllと、マネージdllが必要とするアンマネージdllをいくつか配置しています。このソリューションを使用し、app.configでプローブパスを設定すると、必要なアセンブリを動的にロードできます。
WBuck 2017

Azure関数を使用している場合: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デバッガー(およびコンパイルされたアプリケーション)にどのように許可しますか?(手動で管理者として実行する場合を除く)
Jsncrdnl 2012年

それがデバッグ支援以外の目的で使用される場合、私の本のレビュー(セキュリティまたはその他)に失敗します。
Christian.K

21
これはかなりひどい解決策です。システムフォルダはシステム DLL用です。今、あなたは管理者特権を必要とし、あなたが怠惰だからといって悪い習慣に頼っています。
MikeP

5
MikePの場合は+1、この回答の場合は-1。これはひどい解決策です。これを行う人は誰でも、The Old New Thingを読むことを余儀なくされている間、繰り返しむち打たれるべきです。幼稚園で学んだように、システムフォルダは自分のものではないので、無断で使用しないでください。
コーディグレイ

Okok、私はあなたに同意しますが、私の問題は解決していません...どの場所を勧めますか?(変数を使用して設定できないことを知っている-定数文字列を待っているため-なので、すべてのコンピューターで同じ場所を使用する必要がありますか?)(または、定数の代わりに変数を使用して実行する方法はありますか?)
Jsncrdnl
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.