GO语言中只有固定长度的数组,动态数组对应的是切片,但是切片与C++中的动态数组不一致,其结构比较复杂,无法与C++利用动态分配内存获得的数组相对应,所以将切片作为参数时,C++不能用指针或者数组进行接收,而需要用GoSlice结构体进行接收。
一、首先讲解一下如何用GO编译dll文件,有以下需要注意的几点:
- 导入“C”包,即:import “C”
- 导出函数的首字母必须大写,而且函数声明的上方必须有//export functionName的注释语句,其中functionName就是你要导出的函数名称。
- 编译时采用命令:go build -buildmode=c-shared -o yourDll.dll yourDll.go,其中yourDll.dll就是你生成的dll文件的名称,yourDll.go就是待编译的源代码
例如编写如下GO代码
package main
import "C"
import "fmt"
//terminal command :go build -buildmode=c-shared -o mdArray.dll main.go, 最后的main.go可以不写
//export mtArrayParamExample
func mtArrayParamExample(len1 int, len2 int, len3 int, array []int) { //resultArray must initilized outside
fmt.Println("mtArrayParamExample called")
//为三维数组赋值,实际上是用一维数组模拟三维数组
for i := 0; i < len1; i++ {
for j := 0; j < len2; j++ {
for k := 0; k < len3; k++ {
array[i*len1+j*len2+k] = i + j + k
}
}
}
//输出数组
for i := 0; i < len1; i++ {
for j := 0; j < len2; j++ {
fmt.Printf("array value in go: row %d col %d: ", i, j)
for k := 0; k < len3; k++ {
fmt.Printf("%d ", array[i*len1+j*len2+k])
}
fmt.Printf("\n")
}
}
}
//export ArrayParamExample
func ArrayParamExample(length int, array []int) { // the slice of 'array' has initialized outside
fmt.Println("ArrayParamExample called")
//为数组赋值
for i := 0; i < length; i++ {
array[i] = i
fmt.Printf("array value in go: %d\n", array[i])
}
}
func main() {
}
经过上述步骤就会获得dll文件以及相对应的C++头文件。
二、GO编译dll的调用
如果你的dll文件是64位(或32位)的,则调用程序也必须是64位(或32位)的。刚刚生成的头文件有如下内容
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
//这里是GO语言中数据类型与C++数据类型之间的对应关系,在你的调用程序中可以将这些代码放入头文件,不用全部拷贝,用到哪些拷贝哪些就可以,也可以直接把这个头文件纳入你的项目
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;//这行注释掉,会引起错误
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;//这行注释掉,会引起错误
typedef double _Complex GoComplex128;//这行注释掉,会引起错误
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
//GO语言中的引用类型的数据结构,可以看到切片是一个结构体
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
//导出函数的声明
extern void mtArrayParamExample(GoInt p0, GoInt p1, GoInt p2, GoSlice p3);
extern void ArrayParamExample(GoInt p0, GoSlice p1);
#ifdef __cplusplus
}
#endif
编写你的C++程序代码如下
#include <Windows.h>
#include <stdio.h>
using namespace std;
//下面的定义是从go生成dll时生成的头文件中拷贝过来的,这样你就不需要include GO生成的头文件
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef float GoFloat32;
typedef double GoFloat64;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
typedef void (*funcmtArrayParamExample)(GoInt p0, GoInt p1, GoInt p2, GoSlice p3);//通过函数指针调用go的dll函数
typedef void (*funcArrayParamExample)(GoInt p0, GoSlice p1);//通过函数指针调用go的dll函数
int main() {
DWORD dwError = 0;
HMODULE h = LoadLibrary("yourDll.dll");
if (NULL == h || INVALID_HANDLE_VALUE == h)
{
dwError = GetLastError();
return -1;
}
//调用ArrayParamExample为外部传入的一维数组赋值
funcArrayParamExample ArrayParamExample = (funcArrayParamExample)GetProcAddress(h, "ArrayParamExample");
if (ArrayParamExample)
{
GoInt length = 5;
GoSlice array;//声明切片结构体,并且必须在dll外部对切片进行初始化,一定要分配内存
array.cap = length;
array.len = length;
array.data = malloc(sizeof(GoInt)*length);
ArrayParamExample(length, array);//GO的dll的导出函数
for (int k = 0; k < length; k++)
{
printf("array value in C++: %d \n", ((GoInt*)array.data)[k]);
}
free(array.data);
printf("\n");
}
//如果需要使用多维数组,目前没有找到办法直接使用,只能利用一维数组实现多维数组的功能
//调用mtArrayParamExample为外部传入的多维(实际上用一维数组模拟的多维数组)数组赋值
funcmtArrayParamExample mtArrayParamExample = (funcmtArrayParamExample)GetProcAddress(h, "mtArrayParamExample");
if (mtArrayParamExample)
{
GoInt row = 5;//第一维长度
GoInt col = 3;//第二维长度
GoInt length = 2;//第三维长度
GoSlice array;//声明切片结构体,并且必须在dll外部对切片进行初始化,一定要分配内存
array.cap = row*col*length;
array.len = row*col*length;
array.data = malloc(sizeof(GoInt)*array.len);
mtArrayParamExample(row, col, length,array);//GO的dll的导出函数
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("array value in C++: row %d, col %d: ", i, j);
for (int k = 0; k < length; k++)
{
printf(" %d ", ((GoInt*)array.data)[i * row + j * col + k]);
}
printf("\n");
}
}
free(array.data);
}
getchar();
FreeLibrary(h);
return 0;
}
编译运行,结果如下
这样就实现了C++调用GO编译的dll时动态数组的传参问题