[P/Invoke] 使用 `SetDllImportResolver`[^1] 改写 `DllImport` 的库解析规则

news/2024/9/30 0:33:58

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/66374.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Vscode配置Python环境 Pytorch模块和sklearn模块的下载

Vscode配置Python环境 && Pytorch和sklearn模块安装教程 1.下载python解释器首先在python官网下一个Python解释器点击如下的就可以下载了2.python解释器安装 安装过程如下:双击exe文件安装安装成功3.下载VscodeVisual Studio 官网4.配置Vscode点击Vscode来到这个界面V…

Winform中实现拖动 Windows 边缘来调整其大小

Winform中实现无边框窗体只需要设置一个属性FormBorderStyle = FormBorderStyle.None;即可,或者在设计器中直接设置。无边框表单的结果是丢失了标题栏和控制框(最小化、最大值和关闭按钮)。如果没有标题栏,则无法拖动和移动窗口。如果没有边框,则无法拖动 Windows 边缘来调…

《Programming from the Ground Up》阅读笔记:p117-p146

《Programming from the Ground Up》学习第8天,p117-p146总结,总计30页。 一、技术总结 1.共享函数用法示例 (1)不使用共享函数 linux.s: # filename:linux.s# system call numbers(按数字大小排列,方便查看) .equ SYS_READ, 0 .equ SYS_WRITE, 1 .equ SYS_OPEN, 2 .equ SY…

《Python 基础篇》六:面向对象

Python 中的面向对象。Author: ACatSmiling Since: 2024-09-27什么是对象 对象:是内存中专门用来存储数据的一块区域。 对象中可以存放各种数据,比如:数字、布尔值、代码。 对象由三部分组成:对象的标识(id) 对象的类型(type) 对象的值(value)面向对象(oop) Python…

9.23课堂作业

我所选择的主题是安全教育。在校园内外,我们经常听到的、看到的一些不安全事故频繁发生。尽管在校园内,也会有无端横祸向我们飞来,血的教训让我们懂得,校园安全与师生密切相关,关系到学生能否健康成长,完成学业。关系到老师能否在个宁静安全的环境中教书育人。校园安全是…

streamlit

示例代码import streamlit as st import pandas as pd from pathlib import Path@st.cache_data def load_data_from_csv(file_path):return pd.read_csv(file_path)if __name__ == __main__:file_path = Path(__file__).parent.parent / resources / data.csvdata = load_data…

PlantSimulation的socket交互之TCP

PlantSimulation的socket交互之TCP1.python的socket TCP客户端建立 其实可以任选python或plantsimulation作为客户端,博主因研究需要,将python设为客户端。plant设为服务器。1 """2 Created on Sat December 14 21:00:00 20213 @author: Zhang Litong- Nanjin…

2024-2025-1 20241419《计算机基础与程序设计》第一周学习总结

课程 要求 目标:基于VirtualBox虚拟机安装Ubuntu 作业正文:基于VirtualBox虚拟机安装Ubuntu 教材学习内容总结 1.计算系统:由软件、硬件及其管理的数据组成的用于解决问题以及与其所处环境进行交互的一种动态实体。 2.计算系统的分层:计算系统的各个具体组成部分。 3.抽象:…