C# DllImport调用非托管DLL完整指南及动态加载方案
Dllimport是C#进行平台调用(P/Invoke)的核心属性,用于连接托管代码与非托管的动态链接库。本文将深入解析其用法、常见陷阱,并提供一种在Web环境下动态加载任意路径DLL的稳健方案。
DllImport基本语法与核心参数
DllImportAttribute 位于 System.Runtime.InteropServices 命名空间下。使用该属性修饰的方法必须同时使用 extern 修饰符。调用者至少需要提供包含入口点的 DLL 名称。
其定义结构如下:
[AttributeUsage(AttributeTargets.Method)]
public class DllImportAttribute : System.Attribute
{
public DllImportAttribute(string dllName); // 必需:DLL文件名
public CallingConvention CallingConvention; // 调用约定,默认 Winapi
public CharSet CharSet; // 字符集,默认 Auto
public string EntryPoint; // 入口点名称,默认同方法名
public bool ExactSpelling; // 是否精确匹配拼写,默认 false
public bool PreserveSig; // 保留签名还是转换,默认 true
public bool SetLastError; // 是否保留Win32错误码,默认 false
}
第1步:声明非托管方法
首先需要引入命名空间并使用静态外部方法声明。以下是一个调用 Windows API 写入 INI 文件的示例:
using System.Runtime.InteropServices;
public class IniHelper
{
// 声明 kernel32.dll 中的 WritePrivateProfileString 函数
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(
string section,
string key,
string val,
string filePath);
}
数据类型映射规则
C# 与 C/C++ 之间的数据交互需要遵循特定的映射关系,否则会导致内存错误或程序崩溃:
- DWORD / unsigned int: 映射为 C# 的
int或uint。 - BOOL: 映射为 C# 的
bool。 - 预定义常量: 建议使用
enum提高可读性。 - 结构体 (struct): 需标记
[StructLayout(LayoutKind.Sequential)]以保持内存布局一致。
DLL 搜索路径与加载限制
当仅传递 dllName 而不提供绝对路径时,CLR 会按以下顺序搜索 DLL:
- 可执行文件(.exe)所在目录。
- 系统目录(如
System32)。 - 环境变量
PATH中列出的目录。
ASP.NET 环境下的特殊挑战
在 ASP.NET 应用程序中,直接使用相对路径或 Server.MapPath 作为 DllImport 的参数往往无效。这是因为:
1. DllImport 只能接受编译时常量字符串。
2. IIS 将应用部署在临时目录下运行,且 CLR 默认只拷贝托管程序集到 bin 目录,非托管 DLL 若放在 bin 下可能被忽略或导致“模块找不到”错误。
虽然可以将 DLL 放置在 System32 或修改服务器 PATH 环境变量,但这在虚拟主机环境中通常不可行。
替代方案:动态加载非托管DLL
为了解决路径不确定和权限受限的问题,可以使用 LoadLibrary 和 GetProcAddress API 动态加载 DLL,并结合委托(Delegate)进行调用。这种方法允许使用 Server.MapPath 获取物理路径。
第2步:构建通用 DLL 加载类
创建一个封装了 Windows API 调用的工具类,负责加载 DLL、获取函数地址并生成委托。
using System;
using System.Runtime.InteropServices;
using System.Reflection;
public class DllInvoke
{
// 加载库
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr LoadLibrary(string path);
// 获取函数地址
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr lib, String funcName);
// 释放库
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool FreeLibrary(IntPtr lib);
private IntPtr hLib;
// 构造函数:加载指定路径的 DLL
public DllInvoke(String DLLPath)
{
hLib = LoadLibrary(DLLPath);
if (hLib == IntPtr.Zero)
{
throw new Exception($"Failed to load DLL: {DLLPath}");
}
}
// 析构函数:自动释放资源
~DllInvoke()
{
if (hLib != IntPtr.Zero)
FreeLibrary(hLib);
}
/// <summary>
/// 根据函数名获取对应类型的委托
/// </summary>
/// <param name="APIName">DLL 中的函数名</param>
/// <param name="t">委托类型</param>
/// <returns>委托实例</returns>
public Delegate Invoke(String APIName, Type t)
{
IntPtr api = GetProcAddress(hLib, APIName);
if (api == IntPtr.Zero)
{
throw new Exception($"Function '{APIName}' not found in loaded DLL.");
}
return Marshal.GetDelegateForFunctionPointer(api, t);
}
}
第3步:定义委托并执行调用
你需要根据目标 DLL 导出的函数签名,定义对应的 C# 委托。注意委托的参数和返回类型必须与非托管函数的签名严格一致。
// 假设 Judge.dll 中有一个名为 Compile 的函数:
// int Compile(char* command, char* outputBuffer, int bufferSize)
// 为了简化演示,这里定义一个简单的委托
public delegate int CompileFunc(string command, StringBuilder info);
public void CallNativeMethod()
{
// 1. 获取 DLL 的物理路径(适用于 Web 环境)
// 注意:在实际生产环境中,建议缓存此路径以避免重复计算
string dllPath = Server.MapPath(@"~/Bin/Judge.dll");
try
{
// 2. 初始化加载器
using (var loader = new DllInvoke(dllPath))
{
// 3. 将 DLL 中的 'Compile' 函数转换为委托
// 类型需完全匹配:返回值 int,参数 string, StringBuilder
CompileFunc compile = (CompileFunc)loader.Invoke("Compile", typeof(CompileFunc));
// 4. 执行调用
StringBuilder resultInfo = new StringBuilder(256);
int hr = compile("gcc test.c -o test.exe", resultInfo);
Console.WriteLine($"Result: {hr}, Info: {resultInfo.ToString()}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading or calling DLL: {ex.Message}");
}
}
通过上述方式,开发者可以灵活地在任意可用路径下加载非托管库,规避了 DllImport 静态绑定带来的局限性,特别适用于插件化架构或部署环境受限的场景。
以上就是C# DllImport使用方法及动态加载DLL的详细内容,更多关于C#互操作技术资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









