1、cgo环境搭建       

       初入go坑,记录一次工作中需要封装windows api 的过程。既然是go调用C++那么首先要配置cgo的环境了。要使用CGO特性,需要安装C/C++构建工具链,在macOS和Linux下是要安装GCC,在windows下是需要安装MinGW工具。同时需要保证环境变量CGO_ENABLED被设置为1,这表示CGO是被启用的状态。在本地构建时CGO_ENABLED默认是启用的,当交叉构建时CGO默认是禁止的。比如要交叉构建ARM环境运行的Go程序,需要手工设置好C/C++交叉构建的工具链,同时开启CGO_ENABLED环境变量。然后通过import "C"语句启用CGO特性。

        由于mingw官方下载地址被墙,网上很多版本有问题,有的不能引用C库,有的找不到系统函数导出lib,MSys2在配置好清华大学的镜像地址后,工具包升级安装飞快。

2、cgo基础知识点

     a) C编译器配置

#cgo CFLAGS: -I./number

     b) C++ 编译器参数配置   

#cgo CXXFLAGS: -std=c++11

     c) 引入第三方库

#cgo LDFLAGS: -L${SRCDIR}/number -lnumber

       C头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径

    d) C导出函数头文件

//#include "number.h"

   e) Go语言中数值类型和C语言数据类型对应关系

go语言调用c print go语言调用win32api_#endif

  go中类型导出到C中

go语言调用c print go语言调用win32api_go语言调用c print_02

f) cgo内存模型

   1 C语言空间的内存是稳定的,只要不是被人为提前释放,那么在Go语言空间可以放心大胆地使用。

   2 在CGO调用的C语言函数返回前,cgo保证传入的Go语言内存在此期间不会发生移动,C语言函数可以大胆地使用Go语言的内存。

e) vscode 中要注意

   vscode中偶发改变C中函数实现,但go调用的仍然是原实现,此时执行指令go clean -cache可清除mingw编译中间产物。

3、简单的示例

     演示go调用windows api 杀掉进程。

     a)首先看一下目录结构。

                                

go语言调用c print go语言调用win32api_Go_03

    b) demo.h中导出C接口

#if !defined _DEMO_H_
#define _DEMO_H_

#ifdef __cplusplus
extern "C" { //导出C接口
#endif

int KillPID(unsigned int pid, char* srvName);//注意函数签名中不要带有C++的元素

#ifdef __cplusplus
}
#endif

#endif

   c)demo.cpp中的函数实现

#include "demo.h"
#include <Windows.h>
#include <Psapi.h>
#include <errno.h>
#include <stdio.h>
#include "_cgo_export.h"//go中的导出函数的声明,此文件自动构建

void WriteLog(const char* log){
    _GoString_  gs;//go中的string类型导出到_cgo_export.h文件中,类似的还有切片
    gs.p = log;
    gs.n = strlen(log);
    writeInfoLogln(gs);//go中导出打印日志的方法
}
int KillPID(unsigned int pid, char* srvName)
{
    char log[300];
    int bRet = 0;
    HANDLE proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
    if (proc)
    {
        bRet = TerminateProcess(proc, 2);
        CloseHandle(proc);
    }else{
         errno = GetLastError();
         return bRet;
    }
    sprintf(log, "Killing srv:%s", srvName);
    WriteLog(log);
    bRet = 1;
    return bRet;
}

   d) demo.go 中导出go方法,cgo编译参数配置

package demo

/*
#cgo LDFLAGS: -static -lpsapi -lstdc++ 
//注意这里引用的是mingw 的libpsapi.a,千万不要引用windows sdk下的Psapi.Lib,虽然最终调用的都是
//系统的psapi.dll,但函数导出符号不一样,编译不会通过!!!另外,使用mingw尽量使用-static静态链
//接C++库,不然应用运行时需要libstdc++-6.dll
#include "demo.h"
#include <stdlib.h>
*/
import "C"
import (
	"fmt"
	"unsafe"
)

/**
 * @description: 强杀一个服务
 * @param {pid:进程编号, serviceName 进程名称}
 * @return:
	r:操作结果e
	 err:异常
*/
func KillPID(pid int32, serviceName string) (ret uint32, err error) {
	c_serviceName := C.CString(serviceName)//go中开辟的内存传给c是安全的,c函数返回前地址不会变化
	defer C.free(unsafe.Pointer(c_serviceName))

	r, err := C.KillPID(C.uint(pid), c_serviceName)
	if err != nil {
		err = fmt.Errorf("KillPID failed errno:%s!", err)
	}
	ret = uint32(r)
	return ret, err
}

//导出到C中需要此注释
//export writeInfoLogln
func writeInfoLogln(log string) {
	fmt.Println(log)
}

  e)demo_test.go单元测试

package demo_test

import (
	"demo"
	"testing"
)

func Test(t *testing.T) {
	demo.KillPID(3408, "SGTool.exe")//杀掉搜狗输入法工具
}

   f)测试结果

                         

go语言调用c print go语言调用win32api_#include_04