Go 语言 + aardio 快速开发图形化桌面软件,简单生成独立 EXE
liebian365 2024-11-18 14:19 8 浏览 0 评论
一、aardio 调用 Go 编写的 DLL
Go 写的 DLL 小轻快无依赖,是一个极大的优势。而 aardio 又可以方便地内存加载 Go 写的 DLL,生成独立 EXE 文件。
首先执行下面的 aardio 代码编译 Go 源码生成 DLL 文件。aardio 会自动配置好编译环境。
import golang;
var go = golang();
go.main = /**********
package main
import "C" //启用 CGO
//下面这句注释指令导出 DLL 函数
//export Add
func Add(a int32,b int32) int32{
//aardio 中整数默认为 int32 类型,小数默认为 double 类型
return a + b;
}
//初始化函数,可以重复写多个
func init() {}
//必须写个空的入口函数,实际不会执行
func main() {}
**********/
//编译 Go 源码生成同名 DLL 文件
go.buildShared("/.go/start.go");
Go 代码有一个好处,几句代码就是一个完整的程序。
要想用 Go 编译 DLL,首先要导入 C 库启用 cgo 。
import "C"
这句代码前面的注释被称为 cgo 前导注释,用于指定 C 编译器指令、C语言代码。所以不要在这里写其他普通注释(可以为空行)。
Go 注释用途不小,例如函数前面的 export 注释被用来声明 DLL 导出函数。
//export Add
func Add(a int32,b int32) int32{
//aardio 中整数默认为 int32 类型,小数默认为 double 类型
return a + b;
}
其实 aardio 中的注释也有一些特殊用途:例如注释赋值给变量可用于表示复杂的字符串值( 上面的 Go 源码就是放在注释里赋值为字符串 ),aardio 还可以利用注释中的 import 语句引用库到发布程序但又并不实际加载(用于 fastcgi.exe 这种需要后期按需加载库的程序 )。
下面我们用 aardio 调用上面的 DLL:
/*
$操作符将 DLL 编译到内存(发布后不需要外部 DLL 文件)。
注意 cgo 生成的 DLL 要指定 cdecl 调用约定。
*/
var dll = raw.loadDll(#34;/.go/start.dll","start.dll","cdecl");
//然后就可以直接调用 DLL 函数了
var c = dll.Add(2,3);
aardio 调用 DLL 的语法对别简单,一般不需要声明。
如果要先声明 DLL 的导出函数,这样写:
//加载 DLL,参数 @2 要指定 DLL 共享名避免重复加载(文末解释原因)
var dll = raw.loadDll(#34;/.go/start.dll","start.dll","cdecl");
//声明 API,明确指定参数与返回值类型
var add = dll.api("Add","int(int a,int b)" );
var c = add(2,3);
用起来都是很简单的。
下面我们用 aardio 写个图形界面调用 Go 代码。
先新建一个 aardio 空白工程。
然后从『界面控件』拖放文本框、按钮到窗体设计器:
双击按钮切换到代码视图,编写代码如下:
//加载 DLL
var dll = raw.loadDll("/.go/start.dll","cdecl");
//点击按钮触发事件
mainForm.btnGo.oncommand = function(id,event){
//获取控件文本,并转换为数值
var a,b = tonumber(mainForm.editX.text),tonumber(mainForm.editY.text);
//调用 Go 函数
var c = dll.Add();
//显示函数返回值
mainForm.edit.text = c;
}
aardio 写图形界面很轻松,再看个例子:
二、aardio + Go 操作静态结构体
有些编程语言操作结构体很麻烦,但 aardio ,Go 操作静态结构体( struct )都很方便。
首先用 aardio 代码调用 Go 编译一个 DLL:
import console.int;
import golang;
var go = golang();
go.main = /**********
package main
import "C"
import "unsafe"
import "fmt"
//声明结构体
type Point struct {
x int
y int
}
//export SetPoint
func SetPoint(p uintptr) {
// aardio 结构体转换为 Go 结构体
point := (*Point)(unsafe.Pointer(p))
point.x = 1
point.y = 2
fmt.Println( "在 Go 中打印结构体:",point );
}
func main() {}
**********/
go.buildShared("/.go/TestStruct.go");
然后 aardio 调用 DLL 的代码如下:
import console.int;
//加载 Go 写的 DLL
var goDll = raw.loadDll("/.go/TestStruct.dll",,"cdecl");
//声明静态结构体
class Point {
int x;
int y;
}
//创建结构体
var point = Point();
//调用 Go 函数,传结构体(结构体总是传址)
goDll.SetPoint(point);
//打印结构体
console.dumpJson(point);
//结构体就是表(table),也可以这样直接写
goDll.SetPoint({
int x = 1;
int y = 2;
});
aardio,C 语言,cgo,Go 静态类型对应关系如下:
aardio | C 语言 | cgo | Go |
BYTE | char | C.char | byte, bool |
byte | singed char | C.schar | int8 |
BYTE | unsigned char | C.uchar | uint8, byte |
word | short | C.short | int16 |
WORD | unsigned short | C.ushort | uint16 |
int | int | C.int | int32, rune |
INT | unsigned int | C.uint | uint32 |
int | long | C.long | int32 |
INT | unsigned long | C.ulong | uint32 |
long | long long | C.longlong | int64 |
LONG | unsigned long long | C.ulonglong | uint64 |
float | float | C.float | float32 |
double | double | C.double | float64 |
INT | size_t | C.size_t | uint |
pointer | void * | unsafe.Pointer |
注意这里指的是 aardio 静态类型(主要用于 DLL 接口编程)。在 aardio 里大写的整数类型名都表示无符号数(只有正值,没有负值)。
三、aardio + Go 操作 JSON
Go 操作 JSON 很溜,这个要利用一下。
不过想拿 Go 的字符串指针有些麻烦,Go 原则上不让你干这事,默认对输出的指针有严格的检查。如果按常规的方法调用 cgo 传字符串指针,这是有些繁琐的。
这里需要一点小技巧。
Go 的 DLL 里导出函数如下:
//export TestStringPtr
func TestStringPtr(str *string) {
fmt.Printf("Go 通过 *string 收到 aardio 字符串: %s!\n", *str) ;
*str = "这是新的字符串";
}
是不是变简单了?!
然后在 aardio 这样调:
import golang.string;
var goStr = golang.string("这是 aardio 字符串,UTF-8 编码");
//在 Go 里这个参数应当声明为 *string 指针类型(aardio 结构体总是传址)
goDll.TestStringPtr(goStr);//不要在 Go 中保存 aardio 传过去的字符串
在得到 goStr 以后要立即调用 tostring( goStr ) 转换为 aardio 字符串(自动释放 Go 的内存指针)。原理我可以看 golang.string 的源码,简单粗暴能用就行。
传字符串方便了,传 JSON 也就简单了。
下面先用 aardio 调用 Go 写一个 DLL:
import golang;
var go = golang();
go.main = /**********
package main
import "C"
import (
"time"
"aardio"
)
/*
Go 结构的 JSON 字段要大写首字母,
每个字段可以在类型名后面额外添加 tag 字符串声明在 JSON 中的字段名。
*/
type QueryParam struct {
Service string `json:"service"`
Domain string `json:"domain"`
Timeout time.Duration `json:"timeout"`
}
//export Query
func Query(json *string) {
//创建结构体
var queryParam = QueryParam{}
/*
解析 JSON 到结构体,
aardio.JsonParam() 返回函数对象用于更新 JSON。
defer 语句用于推迟到函数退出前调用。
*/
defer aardio.JsonParam(json, &queryParam)()
//读取结构体的值,修改结构体的值,aardio 可以自动获取新值
queryParam.Domain = queryParam.Domain + "|www.aardio.com"
}
func main(){}
**********/
go.buildShared("/.go/jsonTest.go");
在 Go 语言里只要下面这一句:
defer aardio.JsonParam(json, &queryParam)()
就可以将 aardio 传过来的 JSON 解析为结构体,然后可以修改结构体,并且在函数退出前自动更新 aardio 里的 JSON 。
然后看 aardio 调用代码:
import console.int;
import golang.string;
//加载 DLL
var dll = raw.loadDll("/.go/jsonTest.dll",,"cdecl");
//参数不是字符串、buffer、null 时会自动转换为 JSON 字符串
var jsonParam = golang.string({
service = "_services._dns-sd._udp";
domain = "local";
timeout = 1000;
})
//调用 Go 函数
dll.Query( jsonParam );
//获取 Go 修改后的对象
var goObject = jsonParam.value;
//查看对象的字段值,已经被 Go 修改了
console.dumpJson( goObject.domain )
Go 语言只要简单地通过 JSON 就可以获取、更新 aardio 里的对象。
整个代码量都很少。
Go 是一个有趣的编程语言。所以 aardio + Go 有很多有趣的用法,例如aardio 自带范例里的:
aardio.call
aardio.callPtr
aardio.callJson
关于这些今天先跳过,下面先讲重点。
四、aardio 调用 Go 编写的 EXE
用 EXE 代替 DLL 作为运行模块是如今非常流行的一个方式。
不同的 EXE 运行在不同的进程,这种多进程交互的方式首先是非常稳定。一个 EXE 就算崩溃了也不会影响到另一个进程。其次跨进程调用可以兼容 32位、64 位 EXE,代码不需要任何改动。
我之前发布了一个很有意思的扩展库:
process.util`
这个扩展库用到的:
ProcessUtilRpc.dll
实际上就是用 Go 语言写的一个 EXE 程序,只不过后缀名是 DLL ( 后缀名无关紧要,可以随便改 )。
下面我们详细讲解 aardio 如何调用 Go 写的 EXE。
首先在 aardio 中运行下面的 Go 代码生成一个 EXE 程序。没有安装 Go 环境都没有关系,aardio 会自动安装。没有任何复杂步骤。
//导入支持库
import golang;
//创建 Go 编译器
var go = golang();
//编写 Go 源码
go.main = /**********
package main
//导入模块
import (
"net/rpc"
"aardio/jsonrpc"
)
//定义结构体
type Calculator struct{}
//定义下面的函数参数结构
type Args struct {
X, Y int
}
//定义允许 aardio 调用的远程函数
func (t *Calculator) Add(args *Args, reply *int) error {
*reply = args.X + args.Y
return nil
}
//EXE 主启动函数
func main() {
//创建 RPC(远程函数调用) 服务端
server := rpc.NewServer()
//导出允许客户端调用的对象
server.Register( new(Calculator) )
//运行服务端
jsonrpc.Run(server)
}
**********/
//生成 EXE 文件
go.buildStrip("/goRpc.go");
改用 go.buildStrip64 可以生成 64 位 EXE( aardio 都可以调用 ) 。
生成的 goRpc.exe 负责运行 RPC (远程函数调用)服务端。
所有 Go 导出的 RPC 函数都必须有 2 个参数:
1、args 参数接收 aardio 的调用参数。
2、reply 参数用于保存函数返回值。
Go 函数的返回值必须是 error 对象名 nil ,返回 nil 表示没有发生错误。
下面用 aardio 调用上面的 Go 程序:
import process.rpc.jsonClient;
//启动 Go 服务端
var go = process.rpc.jsonClient("/goRpc.exe");
//调用 Go 函数
var reply = go.Calculator.Add({
X = 2;
Y = 3;
} )
//获取函数返回值
var result = reply[["result"]];
代码非常简单。
reply 是服务端函数返回的响应对象。调用失败则 reply.error 为错误信息。调用成功则远程函数返回值放在 reply.result 里。
双 [[]] 是 aardio 的直接下标操作符,当写为 reply[["result"]] 时,即使 reply 是 null 或任何不包含 result 的对象都不会报错而是返回 null 值。
借用上面第一个例子里的窗体界面:
双击按钮切换到代码视图,编写代码如下:
import process.rpc.jsonClient;
//创建远程函数调用客户端
var go = process.rpc.jsonClient("/goRpc.exe");
//点击按钮触发事件
mainForm.btnGo.oncommand = function(id,event){
//调用 Go 函数
var reply = go.Calculator.Add({
X = tonumber(mainForm.editX.text);
Y = tonumber(mainForm.editY.text);
} )
//获取函数返回值
mainForm.editReply.text = reply[["result"]];
}
按 F5 运行就能看到效果了。
当然可以将 goRpc.exe 改名为 goRpc.dll ,后缀名无关紧要。
如果不想软件带个 goRpc.exe 文件,可以在 aardio 发布生成 EXE 后弹出的对话框上点击『转换为独立 EXE 』。
五、aardio , Go 语言通过 COM 接口交互
这是我今天刚写的一个例子。
下面用 Go 创建项目,自动安装 go-ole 模块,然后编写一个 DLL:
import console.int;
import golang;
//参数 @1 指定工作目录,默认为 "/"
var go = golang("/go")
go.setGoProxy("https://mirrors.aliyun.com/goproxy/,direct");
//初始化 GO 项目
go.mod("init golang/dispDemo")
//安装第三方模块
go.get("github.com/go-ole/go-ole")
go.main = /**********
package main
import (
"C"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
"fmt"
)
//export TestDispatch
func TestDispatch(dispatchIn uintptr) uintptr {
//这里不需要初始化 OLE,aardio 自动支持这些
// 获取传入的 IDispatch 指针
dispatch := (*ole.IDispatch)(unsafe.Pointer(dispatchIn))
// 调用 dispatch 对象的方法
result := oleutil.MustCallMethod(dispatch, "Add", 1, 2)
defer result.Clear()
// 假设 Add 方法返回一个数值,可以这样获取返回值
// value := result.Value() // 返回 interface{}
// valueInt := result.ToInt() // 返回 int
// valueFloat := result.ToFloat() // 返回 float64
// valueString := result.ToString() // 返回 string
// 打印结果(假设返回一个数值)
fmt.Println("Result:", result.Value())
// 创建新的 IDispatch 对象
clsid, err := ole.CLSIDFromProgID("Scripting.Dictionary")
if err != nil {
panic(err)
}
unknown, err := ole.CreateInstance(clsid, nil)
if err != nil {
panic(err)
}
defer unknown.Release()
//这里增加引用计数
newDispatch, err := unknown.QueryInterface(ole.IID_IDispatch)
if err != nil {
panic(err)
}
// 返回新的 IDispatch 对象的指针(不必释放引用计数,由 aardio 接收时释放)
return uintptr(unsafe.Pointer(newDispatch))
}
func main() {
// 需要有一个空的 main 函数以满足 go build
}
**********/
go.buildShared("/dispDemo.go");
下面在 aardio 里调用上面的 DLL:
//调用 DLL
import console.int;
console.open();
//内存加载 DLL,请先编译 Go 代码生成 DLL
var dll = raw.loadDll(#34;/dispDemo.dll",,"cdecl");
//aardio 对象转换为 COM 对象(COM 接口会自动转换,原生 DLL 接口要调用 com.ImplInterface )
import com;
var disp = com.ImplInterface(
//任意表对象或函数都可以转换为 COM 对象(IDispatch 接口对象)
Add = function(a,b){
console.log("Add 函数被 Go 语言调用了");
return a + b;
}
);
//调用 Go 函数
var pDisp = dll.TestDispatchP(disp);
//将 Go 函数返回的 IDispatch 指针转换为 COM 对象
var comObj = com.QueryObjectR(pDisp);//转换同时释放一次引用计数
//操作 COM 对象
comObj.Add("key","value");
comObj.Add("key2","value2");
//遍历 COM 对象
for index,key in com.each(comObj) {
//输出字典的键值
console.log( key,comObj.Item(key) )
}
console.log(ptr)
aardio 操作 COM 对象很方便,不需要额外的封装。
aardio 最常用的表对象自动兼容 COM 接口,在 COM 接口函数里会自动转换为 IDispatch 接口。
但是在 DLL 函数里要明确调用
com.ImplInterface
函数创建 COM 接口对象,例如:
var disp = com.ImplInterface(
//任意表对象或函数都可以转换为 COM 对象(IDispatch 接口对象)
Add = function(a,b){
console.log("Add 函数被 Go 语言调用了");
return a + b;
}
);
disp 对象传入 Go 函数就是一个 IDispatch 接口指针,go-ole 操作 IDispatch 指针就很方便:
// 获取传入的 IDispatch 指针
dispatch := (*ole.IDispatch)(unsafe.Pointer(dispatchIn))
// 调用 dispatch 对象的方法
result := oleutil.MustCallMethod(dispatch, "Add", 1, 2)
六、Go 编写 DLL 注意事项
相比 C/C++写的 DLL,Go 写的 DLL 有几个需要特别注意的地方:
1、在主线程加载 Go 写的 DLL,保持 DLL 对象不被释放(避免第二次加载同一 DLL )。其他线程加载同一 DLL 就只会增加引用计数, 不会重复加载。
2、如果用 $ 操作符,从内存加载 Go 写的 DLL,就必须在第二个参数中指定共享名称,这样 aardio 也不会重复加载内存 DLL,只会增加引用计数。
var dll = raw.loadDll(#34;/.go/start.dll","start.dll","cdecl");
3、加载 DLL 的主线程不要退出太快,除了测试,实际开发其实也不太可能这样干,谁会写个软件只有几句代码呢。真要这样干加个 sleep 语句延时一下( 实际上就是等 Go 初始化完成,但 Go 没有提供一个等待初始化或销毁完成的机制 )。
否则,重复加载相同 DLL,退出加载线程太快,Go 有一定机率会崩溃。这是 Go 语言的锅,与 aardio 没有关系。其他编程语言写的 DLL 也没有这问题。
其实没太太影响,稍加注意就能规避问题。
不求完美,很多事情就简单。
- 上一篇:C#调用solidworks
- 下一篇:计算IE的旋转角度与滤镜详解
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)