web-dev-qa-db-ja.com

実行時に[DllImport]パスを指定するにはどうすればよいですか?

実際、C#(working)DLLを取得しました。これをC#プロジェクトにインポートして、その関数を呼び出します。

次のように、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」パラメータを必要としていることです。

だから私の質問は::この場合何ができるだろうか?

126
Jsncrdnl

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

私はあなたが世界の他のみんなと同じようにできない理由を正直に理解しておらず、DLLへのrelativeパスを指定しています。はい、あなたのアプリケーションがインストールされるパスは人のコンピューターによって異なりますが、それは基本的に展開に関しては普遍的なルールです。 DllImportメカニズムは、これを念頭に置いて設計されています。

実際、それを処理するのはDllImportでさえありません。便利なマネージラッパーを使用しているかどうかに関係なく、物事を管理するのはネイティブWin32 DLLロードルールです(P/Invoke marshallerは単に LoadLibrary を呼び出します)。これらのルールは非常に詳細に列挙されています here ですが、重要なルールはここから抜粋しています:

システムが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をそこに置くと、それが見つかります。相対パスを使用するだけで、複雑な問題はすべてなくなります。

書くだけ:

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

ただし、そのが何らかの理由で機能しない場合、アプリケーションがDLLの別のディレクトリを検索するように強制する必要がある場合は、-を使用してデフォルトの検索パスを変更できます SetDllDirectory function
注意してください、ドキュメントに従って:

SetDllDirectoryを呼び出した後、標準のDLL検索パスは次のとおりです。

  1. アプリケーションのロード元のディレクトリ。
  2. lpPathNameパラメーターで指定されたディレクトリ。
  3. システムディレクトリ。 GetSystemDirectory関数を使用して、このディレクトリのパスを取得します。
  4. 16ビットのシステムディレクトリ。このディレクトリのパスを取得する関数はありませんが、検索されます。
  5. Windowsディレクトリ。 GetWindowsDirectory関数を使用して、このディレクトリのパスを取得します。
  6. PATH環境変数にリストされているディレクトリ。

したがって、DLLからインポートされた関数を初めて呼び出す前にこの関数を呼び出す限り、DLLの検索に使用されるデフォルトの検索パスを変更できます。もちろん、利点は、実行時に計算されるdynamic値をこの関数に渡すことができることです。 DllImport属性では不可能です。そのため、相対パス(DLLの名前のみ)を引き続き使用し、新しい検索順序に基づいて検索します。

この関数をP/Invokeする必要があります。宣言は次のようになります。

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

GetProcAddressを使用するというRanの提案よりも優れているのは、LoadLibrary関数(パスなしのファイル名のみ)を呼び出す前にDllImportを呼び出すだけで、ロードされたモジュールは自動的に。

このメソッドを使用して、多数のP/Invoke-d関数を変更せずに、32ビットまたは64ビットのネイティブDLLをロードするかどうかを実行時に選択しました。読み込まれた関数を含む型の静的コンストラクターに読み込みコードを貼り付けると、すべて正常に機能します。

33
MikeP

パスまたはアプリケーションの場所にない.dllファイルが必要な場合、DllImportは属性であり、属性は型、メンバー、および他の言語要素。

私があなたがしようとしていると思うことを達成するのに役立つ代替手段は、P/Invokeを介してネイティブLoadLibraryを使用し、必要なパスから.dllをロードし、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を使用することはないので、このコードは完全ではありません。実際のアプリケーションでは、メモリリークを回避するために、ロードされたモジュールを解放するように注意する必要があります。

24
Ran

実行時に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.dllassemblyProbeDirectoryパスで表されるパスに存在する場合、ロードされ、p/invokeを介して目的の関数が呼び出されます。

5
RBT

構成ファイルで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);
3
Sajithd

DllImportは、dllがシステムパスのどこかにある限り、完全なパスを指定しなくても正常に機能します。ユーザーのフォルダを一時的にパスに追加できる場合があります。

0
Mike W