主要记录一下调用方法和不同数据类型的传参和转换.
需要注意的: 在go中调用C的方法,基本上参数值/类型和返回类型都为uintptr(uint ptr 应该是无符号整型指针类型),具体可以看下边代码和注释
流程:
- 先编写C代码,然后生成动态dll
- 将dll放到需要调用的路径下(go代码可以指定路径)
- 编写go代码,测试运行结果.
需要了解的:
- 如何调用C的callback
- syscall.FreeLibrary()的使用时机
一.C代码示例
IInterface.h
c
#ifdef LAUNCH_H
#define LAUNCH_H
#define LAUNCH_C_API __declspec(dllexport)
#ifdef __cplusplus
extern "C" //告诉编译器是C语言代码
{
#endif
LAUNCH_C_API int __stdcall Init_c(int a, int b);
LAUNCH_C_API int __stdcall GetName_c(int a, int b);
LAUNCH_C_API int __stdcall ExpireTime();
#ifdef __cplusplus
}
#endif
#endif // LAUNCH_H
launch.c
c
#include "IInterface_C.h"
#include "stdio.h"
//函数的实现
__declspec(dllexport) int __stdcall Init_c(int a, int b)
{
printf("%d %d\n", a, b);
return a+b;
}
__declspec(dllexport) int __stdcall GetName_c(int a, int b)
{
printf("%d %d\n", a, b);
return 0;
}
int a = 1699637857;
//单纯返回一个常量
__declspec(dllexport) int __stdcall ExpireTime()
{
return a;
}
二.GO代码示例(不知道为什么GO代码没格式化???)
go
package main
import (
"fmt"
"os"
"syscall"
"time"
"unsafe"
)
// 调用win api
var (
// user32, _ = syscall.LoadLibrary("user32.dll")
// messageBox, _ = syscall.GetProcAddress(user32, "MessageBoxW")
)
func main() {
//加载dll
dll, err := syscall.LoadDLL("test.dll") //这里可以参数可以指定路径
if err != nil {
fmt.Println("dll未找到:", err)
return
}
//找C函数
//proc, err := dll.FindProc("Init_c") //C函数1
proc, err := dll.FindProc("ExpireTime") //C函数2
if err != nil {
fmt.Println("没有找到对应函数:", err)
return
}
//调用C函数,并传值
//call, _, err := proc.Call(TestNum1(1, 2), TestNum2(3, 4))
//if err != nil {
//fmt.Println("TestNum1 call 的err:", err)//一般err都不为空,此处err信息可以判断具体执行结果
//}
//此处call的类型可以直接和数值类型进行对比.
if call > 0 {
fmt.Println("TestNum1 call 的结果:", call)
}
//启一个go程测试第二个C函数
go func() {
for {
call, _, err := proc.Call(GetExpireTime()) //这里直接调用的一个空方法,实际上做法很多,还没有具体测试
if err != nil {
fmt.Println("GetExpireTime call 的err:", err)
}
if call > 0 {
fmt.Println("GetExpireTime call 的结果:", int64(call))
}
res := CalTime(int64(call)) //传入dll返回的unix时间戳做对比,返回类型为bool
if res == false {
SendMessageBox("过期提醒", "时间到了..") //这里自己封装了一个win api的原生弹框
os.Exit(0) //实际上弹框会阻塞进程退出.简单测试
}
fmt.Println("距离过期还有:", int64(call)-time.Now().Unix(), "秒")
time.Sleep(1 * time.Second)
}
}()
select {}
}
//------------------------------------------一些函数实现
//封装的winAPi方法,具体使用可以去msdn查询
//此处有个疑惑,有一些dll的调用需要释放,还没研究.
func SendMessageBox(title, msg string) {
user32 := syscall.NewLazyDLL("user32.dll")
msgBox := user32.NewProc("MessageBoxW")
msgBox.Call(IntPtr(0), strToPtr(msg), strToPtr(title), IntPtr(0))
}
// 计算时间
func CalTime(ExpireTime int64) bool {
if time.Now().Unix() >= ExpireTime {
fmt.Println("时间过期")
return false
}
fmt.Println("-----------")
return true
}
//字符串转指针,实际结果为*uint16
func strToPtr(s string) uintptr {
b, _ := syscall.UTF16PtrFromString(s)
return uintptr(unsafe.Pointer(b))
}
//方法名字起的不是很好,实际上是转换为uintptr类型
func IntPtr(i int) uintptr {
return uintptr(i)
}
// 正常数值
func TestNum1(x, y int) uintptr {
z := x + y
return uintptr(z)
}
// 正常数值
func TestNum2(x, y int) uintptr {
z := x + y
return uintptr(z)
}
// 返回字符串(本身字符串就是一个指针,并不是基础数据类型)
func testString(s string) uintptr {
b, _ := syscall.BytePtrFromString(s)
return uintptr(unsafe.Pointer(b))
}
// 返回数值指针
func testNumPtr(i *int32) uintptr {
return uintptr(unsafe.Pointer(i))
}
测试结果调用C函数:Init_c():
测试结果调用C函数:ExpireTime():