[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+ 中可用。 ↩︎