当前位置: 首页 > news >正文

C#导出本机Win32native dll

                C# 使用 "3f/DllExport" 工具导出C风格的本机函数


[文 / 张赐荣]

首先,让我们来了解一下什么是争渡读屏软件,以及什么是争渡文本预处理API。争渡读屏软件是一款屏幕朗读软件,用于协助视力障碍人士操作电脑。
争渡文本预处理API是一种让插件可以修改读屏朗读的文本的接口,它是一个32位的动态链接库(DLL),文件名为ZDTextPreprocess.dll,放在"争渡读屏安装目录\addins"下。读屏会优先加载这个DLL文件,然后调用其中的两个函数:Init和TextPreprocess。
"Init"函数是用来初始化插件的,它没有参数,只返回一个整数值,表示当前插件的版本号。这个版本号应该和文档中的版本号一致,目前是1。
"TextPreprocess"函数是用来对文本进行预处理的,它有两个参数:oldString和newString。oldString是一个宽字符指针,指向读屏即将朗读的原始文本。newString也是一个宽字符指针,指向插件修改后的新文本,它的缓冲区大小为40960字节,如果超出了这个大小,需要截断多余的部分。这个函数返回一个整数值,表示新文本的长度。如果发生了错误,或者没有替换文本,就返回0。
插件开发者可以根据自己的需求,编写TextPreprocess函数的逻辑,实现对读屏朗读文本的定制化修改。例如,可以替换一些特殊符号,或者添加一些注释等。
接下来,让我们来看看如何用C#来编写争渡文本预处理插件。我们需要使用一个叫"3f/DllExport"的工具,它可以让.NET程序集导出本机风格的函数。这样我们就可以用C#来编写类似于C语言生成的DLL一样的插件了。
"3f/DllExport"是一个开源项目,在GitHub上可以找到它的源代码和文档。它是在"Unmanaged Exports"基础上发展而来的。"Unmanaged Exports"是一个早期的尝试,它也可以让.NET程序集导出函数,但是它有很多问题,而且已经很久没有更新了。
"3f/DllExport"的工作原理是修改了.NET生成的DLL,在其中插入了本机DLL的头以及导出函数表、重定位函数表信息等。这样,它就像一个普通的本机DLL一样,可以被任何支持调用本机DLL的语言或平台使用。当然,它本质上并不是真正的本机DLL,它的执行代码依然是IL(中间语言)而不是本机二进制代码,所以本身还是要依赖.NET运行时。只是因为现在有了导出函数表,并且加入了类似于普通DLL的头信息,看起来就与普通C语言导出的DLL相似。
这与最新的.NET 7 Native AOT不一样,那个是真正地编译为了非托管本机代码,可以脱离.NET运行时。但是.NET 7 Native AOT不支持X86(32位),而易语言只支持X86的DLL。所以对于我们来说,并没有什么用处。
我不知道为什么微软官方没有去做这样的支持,反而还需要第三方工具来修改。
不过,既然有了这样一个好用的工具,就不用再抱怨了。我们可以利用它来实现我们想要的功能,而不用再去学习和使用C++或其他语言。可以用C#的强大功能和丰富的库,来编写更多的文本预处理效果。
那么"3f/DllExport"怎么使用呢?下面我就以编写争渡文本预处理插件为例,给大家做一个简单的教程。
首先,我们需要下载"3f/DllExport"这个工具。我们可以前往它的GitHub页面,找到它的最新版本,目前是"v1.7.4 (Latest on Jan 3, 2021)",文件名应该是"offline.DllExport.1.7.4.29858.c1cc52f.zip"。我们下载并解压这个文件,可以看到里面有很多文件和文件夹。
接着,我们需要打开Visual Studio 2019,新建一个.NET Framework类库项目,名字叫"TextPreprocessAddon"。然后关闭Visual Studio,将刚才解压的文件全部复制到我们的解决方案文件夹下(也就是与*.sln同目录的文件夹)。然后运行目录中的"DllExport.bat"进行配置。打开后有很多配置和选项,我们暂时不用管,勾选"Installed"复选框,然后点击"Apply"应用设置。它会修改我们的项目配置文件,让它支持导出函数。
配置DllExport就基本完成了,接下去就是编写代码了。导出本机函数的方法是在代码中,使用[DllExport]特性来标记静态方法。"DllExport"特性支持自定义导出函数的名字、调用约定等。与["DllImport"]特性有些类似。注意,如果不指定调用约定,默认导出的是"__cdecl",由于Windows API和易语言只支持stdcall方式导出,所以调用约定我们选择"WINAPI"。DllExport也支持"MarshalAs"特性,可以用这个特性告诉.NET如何将.NET托管类型转换为本机非托管类型。
C#代码:
----------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace TextPreprocessAddon
{
    public static class TextPreprocessAddon
    {
        [DllExport("Init",CallingConvention.Winapi)]
         public static int Init ()
        {
            return (1);
        }

        [DllExport("TextPreprocess",CallingConvention.Winapi)]
        public static int TextPreprocess (IntPtr oldTxtPtr,IntPtr newTxtPtr)
        {
            try
            {
                string oldText = Marshal.PtrToStringUni(oldTxtPtr);
                string newText = oldText.Replace("女孩","男孩");
                byte[] tempBytes = Encoding.Unicode.GetBytes(newText+"\0");
                Marshal.Copy(tempBytes,0,newTxtPtr,tempBytes.Length);
                return (newText.Length);
            }
            catch (Exception ex)
            {
                return (0);
            }
        }
    }
}
----------

上面的代码是用C#编写的争渡文本预处理插件,它可以将原始文本中的"女孩"替换成"男孩"。导出函数的实现过程如下:
使用[DllExport]特性来标记静态方法,指定导出函数的名称和调用约定。
使用Marshal类中的方法来转换指针和字符串类型,以及复制字节数组到内存空间。
使用字符串类型中的方法来替换文本中的内容,以及添加空字符。
返回新文本的长度,或者在发生错误时返回0。

然后编译这个项目,可以在"release"文件夹中找到X86的文件夹,进去就有导出的DLL了。

这样,我们就用C#编写了一个争渡文本预处理插件,它可以将原始文本中的"女孩"替换成"男孩",并将修改后的文本送给读屏朗读。我们可以将这个DLL文件放在争渡读屏安装目录\addins\下,然后启动争渡读屏软件,选择我们的插件,就可以听到效果了。

使用Python调用上面导出的DLL,实现Python调用C#代码。

import ctypes
# 加载 dll(完整路径)
my_lib = ctypes.WinDLL('d:\TextPreprocessAddon.dll')
# 定义函数原型
my_lib.Init.restype = ctypes.c_int
# 调用函数
int_result = my_lib.Init()
print("Result:", int_result)
# 调用 TextPreprocess 函数,并获取返回值
old_string = "这个小女孩长得很漂亮。"
new_string = ctypes.create_unicode_buffer(65536) # 创建一个 Unicode 缓冲区
result = my_lib.TextPreprocess(ctypes.c_wchar_p(old_string), new_string)
if result > 0:
    print("New String:", new_string.value)
else:
    print("Old String:", old_string)
input()

首先,我们需要导入ctypes模块,它是Python的一个标准库,可以让我们调用本机DLL中的函数。
然后,使用ctypes.WinDLL函数,加载我们的DLL文件,注意要指定完整的路径。这个函数会返回一个对象,我们把它赋值给my_lib变量,以便后面使用。
接着,使用my_lib.Init.restype属性,定义Init函数的返回类型为整数类型。这样我们就可以正确地接收Init函数的返回值了。
然后,直接调用my_lib.Init()函数,它会返回一个整数值,表示当前插件的版本号。我们把它赋值给int_result变量,并打印出来。
接着,我们准备调用TextPreprocess函数,它有两个参数:oldTxtPtr和newTxtPtr。oldTxtPtr是一个指向读屏即将朗读的原始文本的指针,newTxtPtr是一个指向插件修改后的新文本的指针。
为了传递这两个参数,我们需要做一些转换。首先,我们定义一个字符串变量old_string,赋值为"这个小女孩长得很漂亮。"。这是我们想要修改的原始文本。
然后,使用ctypes.c_wchar_p函数,将old_string转换为一个宽字符指针类型的对象。这样就可以作为oldTxtPtr参数传递给TextPreprocess函数了。
接着,我们使用ctypes.create_unicode_buffer函数,创建一个Unicode缓冲区类型的对象。这个函数需要一个参数,表示缓冲区的大小。我们给它传递65536,表示缓冲区可以存储65536个字节。这个缓冲区就可以作为newTxtPtr参数传递给TextPreprocess函数了。
然后,调用my_lib.TextPreprocess函数,并传递oldTxtPtr和newTxtPtr两个参数。这个函数会返回一个整数值,表示新文本的长度。我们把它赋值给result变量,并判断是否大于0。
如果result大于0,表示插件成功地修改了文本,并将新文本存储在newTxtPtr指向的缓冲区中。我们可以使用new_string.value属性,获取缓冲区中的字符串值,并打印出来。
如果result等于0,表示插件没有修改文本,或者发生了错误。我们就直接打印old_string变量的值。

通过这段Python代码,我们就可以看到争渡文本预处理插件的效果了。它会将原始文本中的"女孩"替换成"男孩",并将修改后的文本送给读屏朗读。

你看,用C#导出本机DLL给Python是多么美好的事情啊!我们不仅可以利用C#的强大功能和丰富的库来编写更多的文本预处理效果,还可以让Python等其他语言方便地调用我们的插件。这样就实现了跨语言和跨平台的互操作性。

这只是一个简单的导出函数例子。

"3f/DllExport"的最大优点是可以让C#也能导出本机风格的函数,供其他语言或平台调用,从而实现跨语言和跨平台的互操作性。这个库的出现,解决了C#的一大短板,就是不能像C++一样生成普通的动态链接库。
        "3f/DllExport"工具非常强大,支持与"ilmerge"配合使用,如果你的程序集引用了第三方库,它可以配合ILMerge将其合并为一个DLL。也支持风送复杂类型,比如struct、array等。进阶玩法大家就自己去研究吧,本文就写到这里。

参考资料:

https://github.com/3F/DllExport/
https://github.com/dotnet/ILMerge/
https://en.wikipedia.org/wiki/Assembly_(CLI)
https://en.wikipedia.org/wiki/Dynamic-link_library

相关文章:

C#导出本机Win32native dll

C# 使用 "3f/DllExport" 工具导出C风格的本机函数 [文 / 张赐荣] 首先,让我们来了解一下什么是争渡读屏软件,以及什么是争渡文本预处理API。争渡读屏软件是一款屏幕朗读软件,用于协助视力障碍人士操作电脑。 争渡文本预处理API是一…...

express-generator快速构建node后端项目

express-generator是express官方团队开发者准备的一个快速生成工具,可以非常快速的生成一个基于express开发的框架基础应用。 npm安装 npm install express-generator -g初始化应用 express my_node_test 创建了一个名为 my_node_test 的express骨架项目通过 Exp…...

视频监控系统/视频汇聚平台EasyCVR如何反向代理进行后端保活?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…...

金融信创黄金三年:小程序生态+跨端技术框架构建

小程序应用场景生态的发展,受益于开源技术的发展,以及响应快速开发的实际业务需求,一些跨端框架如:Electron、wxPython、FinClip、Tauri、Flutter等发展也非常迅速,小程序生态跨端技术框架,不仅能满足自有超…...

这短短 6 行代码你能数出几个bug?

前言:本文仅仅只是分享笔者一年前见到的诡异代码,大家可以看看乐子,随便数一数一共有多少个bug,这数bug多少还是要点水平的 在初学编程的时候,写的第一个代码大多都是 hello world,可是就算是 hello world…...

【毕设选题】深度学习 机器视觉 车位识别车道线检测 - python opencv

0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过…...

不同数据类型在单片机内存中占多少字节?

文章目录 前言一、不同编译器二、C51* 指针型 三、sizeof结构体联合体 前言 在C语言中,数据类型指的是用于声明不同类型的变量或者函数的一个广泛的系统。变量的类型决定了变量存储占用的空间 一、不同编译器 类型16位编译器大小32位编译器大小64位编译器大小char…...

安卓LinearLayout让控件居中的办法

在控件属性上&#xff0c;是处理不了的。必须是在LinearLayout处理&#xff1a; 垂直居中 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width&…...

uniapp下拉刷新

为什么要使用uniapp的下拉刷新呢 跨平台兼容性&#xff1a; Uniapp 允许你一次编写代码&#xff0c;然后在多个平台&#xff08;如微信小程序、H5、iOS 和 Android 等&#xff09;上运行。使用 Uniapp 的下拉刷新功能&#xff0c;可以确保在不同平台上都能提供一致的用户体验&a…...

【工作记录】css3 grid布局笔记

概述 Grid 布局即网格布局&#xff0c;是一种新的 CSS 布局模型&#xff0c;比较擅长将一个页面划分为几个主要区域&#xff0c;以及定义这些区域的大小、位置、层次等关系。号称是最强大的的 CSS 布局方案&#xff0c;是目前唯一一种 CSS 二维布局 参数配置说明 属性说明可…...

区块链技术-比特币数据结构

背景 随着近几年区块链技术的迅速发展&#xff0c;越来越多的行业正在将区块链技术应用到实际中去。例如&#xff0c;金融、物流、交易所等行业都开始尝试使用区块链技术来替代传统技术。伴随着区块链迅速发展的期间&#xff0c;诞生了比特币&#xff08;BTC&#xff09;、以太…...

SpringBoot结合dev-tool 实现IDEA项目热部署

什么是热部署&#xff1f; 应用正在运行的时候升级功能, 不需要重新启动应用对于Java应用程序来说, 热部署就是在运行时更新Java类文件 通俗的来讲&#xff0c;应用在运行状态下&#xff0c;修改项目源码后&#xff0c;不用重启应用&#xff0c;会把编译的内容部署到服务器上…...

flink中使用外部定时器实现定时刷新

背景&#xff1a; 我们经常会使用到比如数据库中的配置表信息&#xff0c;而我们不希望每次都去查询db&#xff0c;那么我们就想定时把db配置表的数据定时加载到flink的本地内存中&#xff0c;那么如何实现呢&#xff1f; 外部定时器定时加载实现 1.在open函数中进行定时器的…...

Spring Cloud Pipelines 入门实践

文章目录 1. 前言2. Spring Cloud Pipelines 是做什么的2.1. 预定义的流程2.2. 集成测试和契约测试2.3.部署策略 4. Spring Cloud Pipelines的使用示例4.1. 创建一个Spring Boot应用4.2. 将代码托管到GitHub仓库4.3. 添加Spring Cloud Pipelines依赖4.4. 配置Spring Cloud Pipe…...

G1 GC详解及设置

一、概述 G1 GC&#xff0c;全称Garbage-First Garbage Collector&#xff0c;在JDK1.7中引入了G1 GC&#xff0c;从JAVA 9开始&#xff0c;G1 GC是默认的GC算法。通过-XX:UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器&#xff0c;分区既可以是年轻代也可以是老…...

GitHub详细教程

将代码推送到GitHub仓库涉及一系列的步骤。以下是详细的步骤说明&#xff1a; 创建一个新的仓库&#xff08;如果还没有的话&#xff09;&#xff1a; 访问 GitHub。登录您的帐户。点击页面右上角的图标&#xff0c;然后选择“New repository”。填写仓库名称、描述等信息&…...

【小沐学Python】Python实现Web图表功能(Dash)

文章目录 1、简介2、安装3、功能示例3.1 Hello World3.2 连接到数据3.3 可视化数据3.4 控件和回调3.5 设置应用的样式3.5.1 HTML and CSS3.5.2 Dash Design Kit (DDK)3.5.3 Dash Bootstrap Components3.5.4 Dash Mantine Components 4、更多示例4.1 Basic Dashboard4.2 Using C…...

【RabbitMQ】docker rabbitmq集群 docker搭建rabbitmq集群

docker rabbitmq集群 docker搭建rabbitmq集群 RabbitMQ提供了两种常用的集群模式 1.普通集群模式 2.镜像集群模式 普通集群模式只能同步主节点上的交换机和队列信息&#xff0c;但对于队列中的消息不做同步&#xff0c;主节点宕机也不能进行切换&#xff08;故障转移&#xff…...

Linux 网络驱动实验

本文章对Linux 网络驱动实验中的设备树进行介绍&#xff0c;Linux网络驱动程序比较复杂&#xff0c;只要学会应用。 1、I.MX6ULL 网络外设设备树 I.MX6ULL 有两个 10/100M 的网络 MAC 外设&#xff0c;因此 I.MX6ULL 网络驱动主要就是这两个网络 MAC 外设的驱动。 fec1…...

访问Apache Tomcat的虚拟主机管理页面

介绍 通过Tomcat Host Manager应用可以创建、删除、管理Tomcat内的虚拟主机&#xff08;virtual hosts&#xff09;。该应用是Tomcat安装的一部分&#xff0c;默认在<Tomcat安装目录>/webapps/host-manager&#xff1a; 配置用户名、密码、角色 要访问Host Manager应…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…...

StarRocks 全面向量化执行引擎深度解析

StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计&#xff0c;相比传统行式处理引擎&#xff08;如MySQL&#xff09;&#xff0c;性能可提升 5-10倍。以下是分层拆解&#xff1a; 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…...

未授权访问事件频发,我们应当如何应对?

在当下&#xff0c;数据已成为企业和组织的核心资产&#xff0c;是推动业务发展、决策制定以及创新的关键驱动力。然而&#xff0c;未授权访问这一隐匿的安全威胁&#xff0c;正如同高悬的达摩克利斯之剑&#xff0c;时刻威胁着数据的安全&#xff0c;一旦触发&#xff0c;便可…...