[P/Invoke] 使用 `SetDllImportResolver`[^1] 改写 `DllImport` 的库解析规则
[P/Invoke] 使用 SetDllImportResolver
[1] 改写 DllImport
的库解析规则
问题导入
我们都知道,DllImport
在加载本机库时,是在程序文件夹里,或者环境变量指定的路径里,按照特定的规则来寻找库的。那么,如果我们想要稍微改变一下加载规则,要怎么做呢?
这个问题我是在 F# 交互环境中引用 PaddleOCRSharp 遇到的。当时,我像在项目中引用一样,直接在交互环境中引用 PaddleOCRSharp,配好模型参数,然后构建 PaddleOCREngine
,结果报错了:
- let engine = PaddleOCREngine(config);;
System.DllNotFoundException: Unable to load DLL 'PaddleOCR' or one of its dependencies: 找不到指定的模块。 (0x8007007E)
at PaddleOCRSharp.PaddleOCREngine.Initialize(String det_infer, String cls_infer, String rec_infer, String keys, OCRParameter parameter)
at ...
已因出错而停止
尝试解决
看起来是缺 DLL 文件。由于交互环境是在 dotnet 安装目录里面的,我只能将所需的 DLL 复制出来放到另外的文件夹,然后试试库自带的更改加载路径方法:
- let dllPath = "..."
- PaddleOCREngine.PaddleOCRdllPath <- dllPath
- let engine = PaddleOCREngine(config);;
System.DllNotFoundException: Unable to load DLL 'PaddleOCR' or one of its dependencies: 找不到指定的模块。 (0x8007007E)
at PaddleOCRSharp.PaddleOCREngine.Initialize(String det_infer, String cls_infer, String rec_infer, String keys, OCRParameter parameter)
at ...
已因出错而停止
还是报错。此时我还没有意识到报错的真正原因,只是怀疑还是找不到 DLL 。于是我又换了个方法——使用 SetDllImportResolver
来试试。
SetDllImportResolver
的说明文档主要如下:
Sets a callback for resolving native library imports from an assembly.
static member SetDllImportResolver : System.Reflection.Assembly * System.Runtime.InteropServices.DllImportResolver -> unit
This per-assembly resolver is the first attempt to resolve native library loads initiated by this assembly.
可以看到,SetDllImportResolver
是用于改写某个程序集内解析本机库导入规则的。
而 DllImportResolver
是一个接受三个参数而返回一个本机库句柄的委托:
type DllImportResolver = delegate of
libraryName: string *
assembly : Assembly *
searchPath : Nullable<DllImportSearchPath>
-> nativeint
说明文档有示例,那就简单了,很快就可以写出下面的代码:
- NativeLibrary.SetDllImportResolver(
- // PaddleOCREngine 所在的程序集
- typeof<PaddleOCREngine>.Assembly,
- fun libraryName assembly searchPath ->
- if libraryName = "PaddleOCR" then
- let path = Path.Combine(dllPath, libraryName + ".dll")
- // 加载本机库,返回句柄
- NativeLibrary.Load(path, assembly, searchPath)
- // 如果不是 PaddleOCR,则使用默认的加载规则
- else 0n)
- let engine = PaddleOCREngine(config);;
System.DllNotFoundException: Unable to load DLL 'D:\l\Desktop\sp\dll\ocr\PaddleOCR.dll' or one of its dependencies: 找不到指定的模块。 (0x8007007E)
at System.Runtime.InteropServices.NativeLibrary.LoadLibraryByName(String libraryName, Assembly assembly, Nullable`1 searchPath, Boolean throwOnError)
at ...
已因出错而停止
依然报错,但是输出改变了。于是我终于发现了问题所在:依赖库没有被复制过来。将依赖库复制过来后,程序终于运行起来了。
总结
SetDllImportResolver
可以用来改写某个程序集内解析本机库导入的规则。
对于 PaddleOCREngine
来说,修改 DLL 加载路径方法最好是修改 PaddleOCREngine.PaddleOCRdllPath
,因为它是通过修改环境变量实现的,可以让本机库也找得到依赖。如果是用 SetDllImportResolver
让程序在托管部分运行起来的话,在非托管那边也会报错:
System.Exception: Initialize err:
--------------------------------------
C++ Traceback (most recent call last):
--------------------------------------
Not support stack backtrace yet.
----------------------
Error Message Summary:
----------------------
PreconditionNotMetError: The third-party dynamic library (mklml.dll) that Paddle depends on is not configured correctly. (error code is 126)
Suggestions:
1. Check if the third-party dynamic library (e.g. CUDA, CUDNN) is installed correctly and its version is matched with paddlepaddle you installed.
2. Configure third-party dynamic library environment variables as follows:
- Linux: set LD_LIBRARY_PATH by `export LD_LIBRARY_PATH=...`
- Windows: set PATH by `set PATH=XXX; (at D:\MyWorks\Paddle\PaddleBuild\Paddle-v2.6\paddle\phi\backends\dynload\dynamic_loader.cc:312)
at PaddleOCRSharp.PaddleOCREngine..ctor(OCRModelConfig config, OCRParameter parameter)
at PaddleOCRSharp.PaddleOCREngine..ctor(OCRModelConfig config)
at FSI_0014.staticInitialization@() in d:\l\Desktop\sp\240929 ocr.fsx:line 27
at <StartupCode$FSI_0014>.$FSI_0014.main@()
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
已因出错而停止
SetDllImportResolver
仅在 .NET Core 3.1 和 .NET 5+ 中可用。 ↩︎
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」