事实上,我得到了一个C ++(工作)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);
问题在于它将是一个可安装的项目,因此用户的文件夹将不同(例如:皮埃尔,保罗,杰克,妈妈,爸爸......),具体取决于计算机/会话的运行情况上。
所以我希望我的代码更加通用,比如:
/*
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”参数。
所以我的问题是:: 在这种情况下可以做些什么?
答案 0 :(得分:159)
与其他一些答案的建议相反,使用DllImport
属性仍然是正确的方法。
老实说,我不明白为什么你不能像世界上其他人一样做,并指定你的DLL的相对路径。是的,应用程序安装的路径在不同人的计算机上有所不同,但这在部署时基本上是一个通用规则。 DllImport
机制的设计考虑到了这一点。
事实上,它甚至不是DllImport
来处理它。这是管理事物的本机Win32 DLL加载规则,无论您是否使用方便的托管包装器(P / Invoke marshaller只调用LoadLibrary
)。这些规则非常详细地列举here,但重要的是在这里摘录:
在系统搜索DLL之前,它会检查以下内容:
- 如果已在内存中加载了具有相同模块名称的DLL,则系统将使用加载的DLL,无论它在哪个目录中。系统不会搜索DLL。
- 如果DLL位于运行应用程序的Windows版本的已知DLL列表中,则系统将使用其已知DLL的副本(以及已知DLL的相关DLL,如果有)。系统不搜索DLL。
如果启用了
SafeDllSearchMode
(默认设置),则搜索顺序如下:
- 加载应用程序的目录。
- 系统目录。使用
GetSystemDirectory
函数获取此目录的路径。- 16位系统目录。没有函数可以获取此目录的路径,但会搜索它。
- Windows目录。使用
GetWindowsDirectory
函数获取此目录的路径。- 当前目录。
- 醇>
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的其他目录,则可以使用{{3}修改默认搜索路径}}。
请注意,根据文档:
调用
SetDllDirectory
后,标准DLL搜索路径为:
- 加载应用程序的目录。
lpPathName
参数指定的目录。- 系统目录。使用
GetSystemDirectory
函数获取此目录的路径。- 16位系统目录。没有函数可以获取此目录的路径,但会搜索它。
- Windows目录。使用
GetWindowsDirectory
函数获取此目录的路径。- 醇>
PATH
环境变量中列出的目录。
因此,只要在第一次调用从DLL导入的函数之前调用此函数,就可以修改用于查找DLL的默认搜索路径。当然,好处是您可以将动态值传递给在运行时计算的此函数。使用DllImport
属性是不可能的,因此您仍然会使用相对路径(仅限DLL的名称),并依赖新的搜索顺序为您找到它。
你必须P / Invoke这个功能。声明如下:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
答案 1 :(得分:31)
甚至比Ran的使用GetProcAddress
的建议更好,只需在调用LoadLibrary
函数之前调用DllImport
(只有一个没有路径的文件名),他们会自动使用加载的模块。
我已经使用此方法在运行时选择是否加载32位或64位本机DLL而无需修改一堆P / Invoke-d函数。将加载代码粘贴到具有导入函数的类型的静态构造函数中,它们都可以正常工作。
答案 2 :(得分:24)
如果你需要一个不在路径或应用程序位置的.dll文件,那么我认为你不能这样做,因为DllImport
是一个属性,属性只是元数据在类型,成员和其他语言元素上设置。
可以帮助您完成我认为您正在尝试的内容的替代方法是使用本机LoadLibrary
到P / Invoke,以便从您需要的路径加载.dll,然后使用{ {1}}从.dll获取您需要的函数的引用。然后使用这些来创建一个可以调用的委托。
为了便于使用,您可以将此委托设置为类中的字段,以便使用它看起来像调用成员方法。
修改强>
这是一个有效的代码片段,并显示了我的意思。
GetProcAddress
注意:我没有费心使用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);
}
}
,因此此代码不完整。在实际应用程序中,您应该注意释放已加载的模块以避免内存泄漏。
答案 3 :(得分:4)
只要您知道在运行时可以找到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);
这只是指示CLR在程序运行时获取的目录路径中查找非托管C ++库。 Directory.SetCurrentDirectory
调用将应用程序的当前工作目录设置为指定目录。如果myDLL.dll
出现在由assemblyProbeDirectory
路径表示的路径上,那么它将被加载,并且将通过p / invoke调用所需的函数。
答案 4 :(得分:0)
DllImport就可以正常工作。您可以暂时将用户的文件夹添加到路径中。
答案 5 :(得分:0)
在配置文件中设置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);
答案 6 :(得分:0)
在所有其他好的答案中,在 .NET Core 3.0 之后,您可以使用 NativeLibrary。
例如在 Linux 中你没有这样的 kernerl32.dll。 NaiveLirary.Load
和 Native.SetDllImportResolver
可能是要走的路:
static MyLib()
{
//Avaliable for .NET Core 3+
NativeLibrary.SetDllImportResolver(typeof(MyLib).Assembly, ImportResolver);
}
private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
IntPtr libHandle = IntPtr.Zero;
if (libraryName == "MyLib")
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
libHandle = NativeLibrary.Load("xxxx.dll");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
libHandle = NativeLibrary.Load("xxxx.so");
}
}
return libHandle;
}
[DllImport("MyLib", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr foo(string name);
答案 7 :(得分:-13)
如果全部失败,只需将DLL放入windows\system32
文件夹即可。编译器会找到它。
指定要加载的DLL:DllImport("user32.dll"...
,设置EntryPoint = "my_unmanaged_function"
以将您想要的非托管函数导入C#app:
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