在系统级编程中,经常需要使用C/C++来编写模块给Go调用,目前官方支持这种调用,但是需要CGO做支撑。
这里研究了Golang与DLL进行数据交换的几种方式,(重点是:指针,结构体,回掉函数)并做以测试。
依赖条件:
- C接口形式的DLL(这里使用了VS2015编写)
- CGO依赖的GCC(这里使用Qt5.9自带的Minigw)
#include "stdafx.h"
#include <stdio.h>
#ifndef TM_ROBOT_API
#define TM_ROBOT_API extern "C" __declspec(dllexport)
#else
#define TM_ROBOT_API extern "C" __declspec(dllimport)
#endif
TM_ROBOT_API void fun1(int* pVal) {
char buff[128];
sprintf(buff, "fun1, val: %d -> 999\n", *pVal);
OutputDebugStringA(buff);
*pVal = 999;
}
struct MyStruct
{
int nVal1;
float nVal2;
};
typedef void(*CB_MY)(float nVal, float fVal);
TM_ROBOT_API void fun2(MyStruct* pVal) {
char buff[128];
sprintf(buff, "fun2, val: 1->%d, 2->%.4f -> 1,2\n", pVal->nVal1, pVal->nVal2);
OutputDebugStringA(buff);
pVal->nVal1 = 1;
pVal->nVal2 = 2.2;
}
TM_ROBOT_API void fun3(CB_MY pFun) {
char buff[128];
sprintf(buff, "fun3, val: %08X -> call it 10s\n", pFun);
OutputDebugStringA(buff);
for (int i=80;i<100;i++)
{
pFun(i*3.3, i*1.1);
Sleep(10);
}
}
extern "C" __declspec(dllexport)
2. Golang代码
package main
/*
// 依赖GCC
typedef struct _MyStruct
{
int nVal1;
float nVal2;
}MyStruct;
*/
import "C"
import (
"fmt"
"syscall"
"unsafe"
)
func cb_my(val1 float32, val2 float32) int32 {
fmt.Println("cb_my:", val1, val2)
return 0
}
func main() {
Handle := syscall.NewLazyDLL("gocTest.dll")
Fun3 := Handle.NewProc("fun3")
Fun2 := Handle.NewProc("fun2")
Fun1 := Handle.NewProc("fun1")
var aa int32 = 10 // var aa C.int = 10
Fun1.Call(uintptr(unsafe.Pointer(&aa)))
fmt.Println("########", aa)
var arg1 C.MyStruct // 使用Go的Struct会有问题
arg1.nVal1 = 11
arg1.nVal2 = 22.22
Fun2.Call(uintptr(unsafe.Pointer(&arg1)))
fmt.Println("************", arg1)
var fn = syscall.NewCallbackCDecl(cb_my) // 注意调用约定
Fun3.Call(fn)
fmt.Println("============")
}
3. 总结
- fun1做简单的测试就能通过,如果时float等其他类型,尽量使用CGO模块类型。
- fun2如果使用Golang的结构体,除了int32之外的参数都有问题,需要CGO模块类型。
- fun3特别注意NewCallbackCDecl/NewCallback与回掉函数的约定,否则会出很多异常。
其他高阶混合编程,这里不做涉及。
参考https://studygolang.com/articles/1424
http://lib.csdn.net/article/go/33766
类型转换对照表:
char --> C.char --> byte
signed char --> C.schar --> int8
unsigned char --> C.uchar --> uint8
short int --> C.short --> int16
short unsigned int --> C.ushort --> uint16
int --> C.int --> int
unsigned int --> C.uint --> uint32
long int --> C.long --> int32 or int64
long unsigned int --> C.ulong --> uint32 or uint64
long long int --> C.longlong --> int64
long long unsigned int --> C.ulonglong --> uint64
float --> C.float --> float32
double --> C.double --> float64
wchar_t --> C.wchar_t -->
void * -> unsafe.Pointer