C#导出本机Win32native dll

news/2024/11/24 9:44:38/

                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.ppmy.cn/news/1145470.html

相关文章

vue,前端打包项目、部署上线

前端项目是在本地的IDE开发的。流程是:开发》打包》上线到生产环境》使用。 vue脚手架只是开发过程中,协助开发的工具,当真正开发完了,脚手架不参与上线。 这时候要用到打包了。 打包后,可以生成,浏览器能够直接运行的网页>就是需要上线的源码! 打…

maven的pom.xml文件显示被删除

文章目录 1.问题情况2.问题分析3.问题解决 1.问题情况 2.问题分析 这些 pom.xml 文件被 maven 视为了忽略文件。 3.问题解决 路径:File --> Settings --> Build,Execution,Deployment --> Build Tools --> Maven --> Ignor…

对比纯软开与嵌入式硬件开发谁更好呢?

对比纯软开与嵌入式硬件开发谁更好呢? 你的纠结和犹豫是理解的,职业选择确实是一个重要的决策。我明白你在嵌入式和软件开发之间犹豫不决的原因。让我给你提供一些建议,帮助你做出更明智的决定。最近很多小伙伴找我,说想要一些嵌入…

【一周安全资讯1007】多项信息安全国家标准10月1日起实施;GitLab发布紧急安全补丁修复高危漏洞

要闻速览 1.以下信息安全国家标准10月1日起实施 2.GitLab发布紧急安全补丁修复高危漏洞 3.主流显卡全中招!GPU.zip侧信道攻击可泄漏敏感数据 4.MOVEit漏洞导致美国900所院校学生信息发生大规模泄露 5.法国太空和国防供应商Exail遭黑客攻击,泄露大量敏感…

S32K1xx的MBD工具箱加载及激活

1、安装Matlab,本次使用Matlab2022b 2、打开Matlab,加载含有MBD工具的目录,如下 3、双击第一个---安装,正常安装就可以 4、双击第二个---安装,正常安装就可以 5、找到MBD的安装位置如下 C:\Users\Administrator\App…

JavaScript中的深拷贝(deep copy)和浅拷贝(shallow copy)

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

2023年中国助消化药物行业现状分析:消化不良患者逐年上升,提升需求量[图]

助消化药物主要分为促胃动力药物、消化酶抑制剂、胃酸抑制药物和消食剂4种类型。促胃动力药物的作用机制是通过增强胃肠道平滑肌动力促进胃酸分泌,从而达到助消化的目的,临床常用药物包括多潘立酮、莫沙必利、西沙比利等。 助消化药物分类 资料来源&…