环境
- gcc MinGW-W64 8.1.0
- go version go1.11 windows/amd64
- visual studio 2015 professional(可选)
关于go的环境参考博客《vscode配置go编译环境》
C包装C++项目
cgo很好的实现了c项目与go的交互,但是当项目需要引入C++时,比如STL(Standard Template Library)标准模板库,go不能读取C++的模板库。
go间接调用C++有两种方式:
- 通过swig包装C++项目生成go的包,参考我的博客:《通过swig实现go调用c++程序》
- 通过C语言包装C++项目
一个C包装的C++的例子
在整个工程里多加一个纯C的.h和.cpp文件
看图
图中我的工程项目是JP20,我在其中的一个类中增加两个用于测试的函数PrintHello和open。一个void类型,一个int类型,其中int类型具有返回值可以方便后续进行参数传递。
//jp20.cpp
void PrintHello() {
printf("hello in c_idxjp\n");
}
int open(int param) {
printf("open in c,para:%d\n",param);
return param;
}
//jp20wrapper.h
#ifndef _JP20_WRAPPER_H
#define _JP20_WRAPPER_H
typedef struct jpWrapper jpWrapper;
#ifdef __cplusplus
//#ifndef _STDBOOL_H
//#define _STDBOOL_H
//#define _Bool bool
//#endif
extern"C" {
#endif
jpWrapper *GetInstance(void);
__declspec(dllexport) void PrintHello(jpWrapper* pjp);
__declspec(dllexport) int open(jpWrapper* pjp,int para);
void ReleseInstance(jpWrapper* pInstance);
#ifdef __cplusplus
};
#endif
#endif
在jp20wrapper.cpp文件中实现.h中的方法
#include "jp20Wrapper.h"
#include "jp20.h"
#ifdef __cplusplus
extern"C" {
#endif
struct jpWrapper
{
CIdxJP idx_jp;
};
jpWrapper *GetInstance(void) {
return new jpWrapper;
}
void ReleseInstance(struct jpWrapper* pInstance) {
delete pInstance;
pInstance=0;
}
void PrintHello(jpWrapper* pjp) {
pjp->idx_jp.PrintHello();
printf("hello in c_wrapper_idxjp\n");
}
int open(jpWrapper* pjp,int para) {
printf("open in c_wrapper,para:%d\n",para);
return pjp->idx_jp.open(para);
}
#ifdef __cplusplus
};
#endif
再来看一下jp20wrapper类,这个类是为了封装整个项目。
- 用结构体封装C++项目中的类,写在结构体中易于拓展,可以方便以后将其他类封装进去;
- 对结构体进行实例化GetInstance以及释放ReleseInstance;
- 封装两个函数,并导出dll。
dll的导出有两种方式:函数名前加上declspec(dllexport)和写入def文件,这里我用了第一种方式
测试导出函数
在vc自带的dumpbin.exe中进行测试
vs\vc\bin\dumpbin.exe -exports "full-path\JPTree.dll"
上图中可以看到两个导出函数。
Go调用DLL
开始测试的时候犯了一个经验主义错误,bug类型:exit status 3221225781
这是因为之前一直试图用go调用c的静态链接库,静态链接库是这样写的,但是动态链接库是完全不同的方式。
package main
import (
"fmt"
"syscall"
)
var D = syscall.NewLazyDLL("JPTree.dll")
func main() {
fmt.Println("hello word")
D := syscall.NewLazyDLL("JPTree.dll")
DLL_PrintH := D.NewProc("PrintHello")
DLL_PrintH.Call()
DLL_Open := D.NewProc("open")
ret,_,err := DLL_Open.Call(uintptr(10), uintptr(20))
fmt.Println("ret:",ret)
if err != nil {
e := err.(syscall.Errno)
println(err.Error(), "errno =", e)
}
}
首先,动态链接库只需要将.dll文件与go的文件置于同一目录下即可,不需要整个C的工程。
引入动态链接库,官网上的说明有三种方式:
- LoadDLL loads the named DLL file into memory;
- MustLoadDLL is like LoadDLL but panics if load operation fails;
- LazyDLL is subject to the same DLL preloading attacks as documented on LoadDLL.( Use LazyDLL in golang.org/x/sys/windows for a secure way to load system DLLs.)
因为看到了secure,所以我选择用LazyDLL方式加载dll文件。
加载dll成功后通过NewProc获得每一个函数,并且通过Call函数实现参数传递。
实现http请求
Go语言有”net/http”等package,非常容易实现响应http请求。
package main
import (
"fmt"
"strconv"
"syscall"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
var D = syscall.NewLazyDLL("JPTree.dll")
func main() {
fmt.Println("hello word")
router := httprouter.New()
router.POST("/hello", Hello)
router.POST("/open/:num", Open)
log.Fatal(http.ListenAndServe(":8080", router))
}
func Hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
DLL_PrintH := D.NewProc("PrintHello")
DLL_PrintH.Call()
fmt.Fprint(w, "hello world")
}
func Open(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
DLL_Open := D.NewProc("open")
str := p.ByName("num")
fmt.Fprint(w, str)
fmt.Fprint(w, "\n--------------\n")
n, _ := strconv.Atoi(str)
ret, _, err := DLL_Open.Call(uintptr(10), uintptr(n))
if err != nil {
e := err.(syscall.Errno)
println(err.Error(), "errno =", e)
}
fmt.Fprint(w, uintptr(ret))
}
- 首先引用http服务相关包:”net/http”, “github.com/julienschmidt/httprouter”,该包可以在github上看到相关介绍;
- 将加载dll设置为全局变量。全局变量的声明在函数体外,并且全局变量的命名首字母应为大写;
- 设置两个处理器函数,分别调用dll里面的两个导出函数;
- 测试服务。这里我用了postman工具来测试服务。