[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)   
已因出错而停止                               

  1. SetDllImportResolver 仅在 .NET Core 3.1 和 .NET 5+ 中可用。 ↩︎

posted @   ijklmnop  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示