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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java的amazonaws接口出现无法执行http请求:管道中断

java使用amazonaws的接口上传文件到minio出现以下异常: com.amazonaws.SdkClientException: Unable to execute HTTP request: Broken pipe (Write failed) at com.amazonaws.http.AmazonHttpClient R e q u e s t E x e c u t o r . h a n d l e R e t r y a b l e…

【广州华锐互动】灭火器使用VR教学系统应用于高校消防演练有什么好处?

在科技发展的大潮中,虚拟现实(VR)技术以其独特的沉浸式体验赢得了各个领域的青睐,其中包括教育和培训。在高校消防演练中,VR也成为了一种新的消防教育方式。 由广州华锐互动开发的VR消防演练系统,就包含了校…

神经网络(MLP多层感知器)

分类 神经网络可以分为多种不同的类型,下面列举一些常见的神经网络类型: 前馈神经网络(Feedforward Neural Network):前馈神经网络是最基本的神经网络类型,也是深度学习中最常见的神经网络类型。它由若干个…

【工具软件】mediamtx——网页、vue3项目中播放 rtsp 视频流(支持265转码)

声明 本文只做 mediamtx 的使用实操,请务必参考下面的博客,,我也参考下面的大佬博客,感谢唯一602的无私分享: 在web页面中直接播放rtsp视频流,重点推荐:mediamtx,不仅仅是rtsp mediamtx 介绍 …

C++ 与基本数据类型:整型、布尔型与字符型

文章目录 参考描述数据类型基本数据类型与复合数据类型静态数据类型 整形数据类型有符号整型数据类型无符号整型数据类型符号位 最少内存空间概念确定大小sizeof 运算符 进制C 中的不同进制数值表示cout 与进制转化影响范围二进制 后缀字面量整型字面量的默认数据类型主动权整型…

代码随想录算法训练营第六十天 | 单调栈 part 1 | 739. 每日温度、496.下一个更大元素 I

目录 739. 每日温度思路代码 496.下一个更大元素 I思路代码 739. 每日温度 Leetcode 思路 维持一个单调递增的栈,向栈逐一pushtemperatures里的index。 如果当前遍历的元素大于栈顶元素,这意味着 栈顶元素的 右边的最大的元素就是 当前遍历的元素&…

【网络安全】网络安全的最后一道防线——“密码”

网络安全的最后一道防线——“密码” 前言超星学习通泄露1.7亿条信息事件武汉市地震监测中心遭境外网络攻击事件 一、密码起源1、 古代密码2、近代密码3、现代密码4、量子密码 二、商密专栏推荐三、如何利用密码保护账号安全?1、账号安全的三大危险?&…

修炼k8s+flink+hdfs+dlink(四:k8s(一)概念)

一:概念 1. 概述 1.1 kubernetes对象. k8s对象包含俩个嵌套对象字段。 spec(规约):期望状态 status(状态):当前状态 当创建对象的时候,会按照spec的状态进行创建,如果…

Kotlin的作用域函数 let、also、with、run、apply

作用域函数主要有下面这几种,apply ,with 、run 、let 、以及 also 。这些函数非常类似,它们的主要区别: 引⽤上下⽂对象的⽅式 (this / it)返回值 他们在开发中的使用场景主要有两个,一是非空…

javascript中map和filter的区别与联系

javascript中map和filter的区别与联系如何获取对象数组中某个值 javascript中map和filter的区别与联系 在 JavaScript 中,map 和 filter 是两个常用的数组方法,用于对数组进行转换和过滤操作。它们的区别和联系如下: 功能不同: m…

【C++11算法】is_sorted、is_sorted_until

文章目录 前言一、is_sorted函数1.1 is_sorted是什么1.2 函数原型1.3 示例代码1二、is_sorted_until2.1 is_sorted_until是什么?2.2 函数原型2.3示例代码2 总结 前言 在C11标准中,引入了一系列强大的算法函数,用于处理容器和序列。这些算法函…

将网站域名访问从http升级到https(腾讯云/阿里云)

文章目录 1.前提说明2.服务器安装 docker 与 nginx2.1 安装 docker🍀 基于 centos 的安装🍀 基于ubuntu 2.2 配置阿里云国内加速器🍀 找到相应页面🍀 创建 docker 目录🍀 创建 daemon.json 文件🍀 重新加载…

MATLAB算法实战应用案例精讲-【图像处理】SLAM技术详解(最终篇)

目录 前言 知识储备 点云数据 传感器 3D视觉方案 几个高频面试题目

Linux命令(99)之rz

linux命令之rz 1.rz介绍 linux命令rz是用来把文件从windows等平台上传到Linux上 2.rz用法 rz [参数] file rz参数 参数说明-b使用binary的方式上传,不解释字符为ascii-y相同文件名,覆盖-E相同文件名,不会将其覆盖,而是会在所上…

小程序如何设置各种时间参数

在小程序管理员后台->基本设置处,可以设置各种时间。例如待支付提醒时间、待支付取消时间、自动发货时间、自动收货时间、自动评价时间等等。下面具体解释一下各个时间的意思。 1. 待支付提醒时间:在用户下单后,如果一段时间内没有完成支付…

一些概念梳理

MinGW // 适用于32位和64位Windows的GCC和LLVM的完整运行时环境 A complete runtime environment for GCC & LLVM for 32 and 64 bit Windows.MinGW,a contraction of "Minimalist GNU for Windows", is a minimalist development environment for native Micro…

Ajax跨域访问,访问成功但一直走error不走success的的问题解决

Ajax跨域访问,访问成功但一直走error不走success的的问题解决 通过搜索各种资料,终于解决啦,废话不多说了,还是老规矩直接上代码: 我这里用了jsonp,有想了解的点击 : jsonp 前端代码: $.ajax({type:post…

03黑马店评-添加商户缓存和商户类型的缓存到Redis

商户查询缓存 什么是缓存 实际开发过程中数据量可以达到几千万,缓存可以作为避震器防止过高的数据访问猛冲系统,避免系统内的操作线程无法及时处理信息而瘫痪 缓存(Cache)就是数据交换的缓冲区(储存临时数据的地方),我们俗称的"缓存"实际就是缓冲区内的数据(一般从…

flask python 设置定时任务 flask 周期性执行任务方案

flask 通常使用 flask_apscheduler 框架设计定时任务,flask_apscheduler 功能很全面,能按设定的时间规则执行任务,可以持久化到各类数据库(mysql,redis,mongodb),实现对定时任务增、…

Docker与Serverless计算的集成: Docker容器如何与Serverless计算结合。

文章目录 1. Docker容器的可移植性2. Serverless计算的自动伸缩性3. 使用Serverless与Docker容器a. 自托管Serverless平台b. 使用容器服务 4. 使用案例:图像处理服务5. 结论 🎈个人主页:程序员 小侯 🎐CSDN新晋作者 🎉…