如何在运行时指定path?
实际上,我得到了一个C ++(工作)DLL,我想将其导入到我的C#项目中来调用它的函数。
当我指定DLL的完整path时,它确实有效,如下所示:
string str = "C:\\Users\\userName\\AppData\\Local\\myLibFolder\\myDLL.dll"; [DllImport(str, CallingConvention = CallingConvention.Cdecl)] public static extern int DLLFunction(int Number1, int Number2);
问题是这将是一个可安装的项目,所以用户的文件夹将不会是相同的(例如:皮埃尔,保罗,杰克,妈妈,爸爸,…)取决于计算机/会话将在哪里运行。
所以我希望我的代码更通用一些,如下所示:
/* 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”参数。
所以我的问题是:在这种情况下可以做些什么?
与一些其他答案的build议相反,使用DllImport
属性仍然是正确的方法。
我真的不明白为什么你不能像世界上其他人一样做,并指定一个相对path到您的DLL。 是的,安装应用程序的path在不同的人的计算机上是不同的,但是这对于部署来说基本上是一个通用的规则。 DllImport
机制就是为此而devise的。
事实上,它甚至不是DllImport
来处理它。 无论是否使用方便的托pipe包装器(P / Invoke编组器都只是调用LoadLibrary
),它都是原生Win32 DLL加载规则来pipe理事情。 这些规则在这里详细列举,但重要的是在这里摘录:
在系统searchDLL之前,它会检查以下内容:
- 如果具有相同模块名称的DLL已经加载到内存中,系统将使用加载的DLL,而不pipe它在哪个目录中。系统不search该DLL。
- 如果该DLL位于正在运行该应用程序的Windows版本的已知DLL的列表中,则系统将使用其已知DLL的副本(以及已知DLL的依赖DLL,如果有的话)。 系统不search该DLL。
如果启用
SafeDllSearchMode
(默认),search顺序如下:
- 加载应用程序的目录。
- 系统目录。 使用
GetSystemDirectory
函数获取此目录的path。- 16位系统目录。 没有获得这个目录的path的函数,但是它被search。
- Windows目录。 使用
GetWindowsDirectory
函数获取此目录的path。- 当前目录。
PATH
环境variables中列出的目录。 请注意,这不包括应用程序pathregistry项指定的每个应用程序path。 计算DLLsearchpath时不使用App Paths键。
所以,除非你命名DLL和系统DLL(在任何情况下显然不会这样做)一样,默认的search顺序将开始在你的应用程序被加载的目录中查找。 如果您在安装过程中将DLL放在那里,它将被find。 如果你只是使用相对path,所有复杂的问题都会消失。
只要写:
[DllImport("MyAppDll.dll")] // relative path; just give the DLL's name static extern bool MyGreatFunction(int myFirstParam, int mySecondParam);
但是,如果因为某种原因无法正常工作,并且您需要强制应用程序查看DLL的其他目录,则可以使用SetDllDirectory
函数修改默认的searchpath。
请注意,根据文件:
调用
SetDllDirectory
,标准的DLLsearchpath是:
- 加载应用程序的目录。
- 由
lpPathName
参数指定的目录。- 系统目录。 使用
GetSystemDirectory
函数获取此目录的path。- 16位系统目录。 没有获得这个目录的path的函数,但是它被search。
- Windows目录。 使用
GetWindowsDirectory
函数获取此目录的path。PATH
环境variables中列出的目录。
因此,只要您在第一次调用从DLL导入的函数之前调用此函数,就可以修改用于定位DLL的默认searchpath。 当然,好处是您可以将dynamic值传递给在运行时计算的此函数。 这对于DllImport
属性来说是不可能的,所以你仍然会使用一个相对path(只有DLL的名字),并且依靠新的search顺序来为你find它。
你将不得不P /调用这个function。 声明如下所示:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern bool SetDllDirectory(string lpPathName);
甚至比Ran提出的使用GetProcAddress的build议还要好,只要在调用DllImport函数之前调用LoadLibrary(只有一个没有path的文件名),他们就会自动使用加载的模块。
我已经使用这个方法在运行时select是否加载一个32位或64位本地DLL而不必修改一堆P / Invoke-d函数。 将加载代码粘贴到具有导入函数的types的静态构造函数中,它将一切正常。
如果您需要不在path或应用程序位置的.dll文件,那么我认为您不能这样做,因为DllImport
是一个属性,属性只是在types,成员和其他语言元素。
另外一个可以帮助你实现我认为你正在尝试的方法是通过P / Invoke使用本地LoadLibrary
,以便从你需要的path加载一个.dll文件,然后使用GetProcAddress
获得对函数的引用你需要从这个.dll。 然后使用这些来创build一个可以调用的委托。
为了使它更容易使用,您可以将此代理设置为您的类中的一个字段,以便使用它看起来像调用成员方法。
编辑
这里是一个代码片段,并显示我的意思。
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
,所以这个代码是不完整的。 在实际的应用程序中,您应该小心地释放加载的模块以避免内存泄漏。
只要dll位于系统path的某个位置,DllImport就可以正常工作,不需要指定完整的path。 您可能能够临时将用户的文件夹添加到path。
只要知道运行时可以findC ++库的目录,这应该很简单。 我可以清楚地看到,你的代码就是这种情况。 你的myDll.dll
会出现在当前用户的临时文件夹内的myLibFolder目录中。
string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll";
现在,您可以继续使用常量string使用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);
这只是指示CLR在您的程序运行时获取的目录path中查找非托pipeC ++库。 Directory.SetCurrentDirectory
调用将应用程序的当前工作目录设置为指定的目录。 如果你的myDLL.dll
出现在由assemblyProbeDirectory
path表示的path上,那么它将被加载,并且通过p / invoke调用所需的函数。
如果全部失败,只需将DLL放在windows\system32
文件夹中即可。 编译器会find它。 指定要加载的DLL: DllImport("user32.dll"...
,设置EntryPoint = "my_unmanaged_function"
将您想要的非托pipe函数导入到您的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( DllImport
.aspx