概念

对于一些上了年纪的项目,重构耗时耗力太大动干戈,不重构又问题重重。举个简单的例子,我司有一些项目是0几年的项目,还是用的perl进行的开发,并且由于其业务特殊(需要在服务器执行脚本),大量脚本素材,重构困难。
出于运维的需要,公司要统一使用的开发语言(后端主要是使用go和java,以及少量的python),并且perl语言开发太难找,接手的大多是半路临时学的,容易给项目埋雷。后来又要同时支持python脚本(相当于perl用python重写),改一个东西需要两个项目一起改,实在是容易出问题。
出于灵光一闪,go可以使用cgo生成c的动态库,而swig刚好可以把c动态库编译为不同语言可以使用的包,于是,考虑到可以使用如下的方式对老旧项目进行优化。
go通过cgo编写动态链接库,再通过swig将动态链接库封装成python、perl可以使用的包,这样就可以实现对旧项目的模块逐步替换。当不熟悉的语言上的代码少了,后续接手的时候运维起来就轻松多了。
下面按照流程讲解整个流程的实现。

cgo部分

cgo部分一般为项目底座,提供所有项目基础的功能,包括配置、nacos、日志等服务。cgo入门可以看这个:https://pkg.go.dev/cmd/cgo
这里就写一个简单的go项目,

package main //注意必须是main包

// #include <stdio.h>
import "C"
import "fmt"

//export hello
func hello() {
	fmt.Println("hello biz-binary")
}

//export add
func add(a, b int) int {
	return a + b
}
func main() { //不能省略

}

这里导出了两个方法:hello和add,
编译指令(注意导出的so名字必须为libxxx.so,xxx为模块名称):

go build -buildmode=c-shared -o libbiz-binary.so

这个会得到两个文件:libbiz-binary.hlibbiz-binary.so,这两个是接下来需要使用的。

swig

swig将刚才导出的库和头文件封装成对应的python和perl的库。
先准备一个.i文件,告诉swig需要导出哪些内容:

%module bizBinary
%{
#include "libbiz-binary.h"
%}
%include "libbiz-binary.h"

bizBinary是包名,另外有很多具体的细节需要自行参考swig的.i文件编写逻辑

编译成python文件

执行指令swig -python bizBinary.i,可以得到bizBinary.pybizBinary_wrap.c两个文件,前者主要是之后导入的时候的代码提示,后者是一会封装c动态库为python包的文件。

编译

编译之前先进入文件夹/usr/local/include/看下是否有python的文件夹,如果没有需要通过pip3 install python3-dev来进行安装。
执行 gcc -c -fpic bizBinary_wrap.c -I/usr/local/include/python3.8,再执行gcc -shared bizBinary_wrap.o -L. -lbiz-binary -o _bizBinary.so即可完成编译

测试

完成编译之后,可以通过这个代码进行测试

import bizBinary
print(bizBinary.hello())
print("2 + 3 =",bizBinary.add(2,3))

这个时候可能会报

Traceback (most recent call last):
  File "pyt.py", line 1, in <module>
    import bizBinary
  File "/home/yuxf/go/src/unitechs.com/biz-binary/t/bizBinary.py", line 12, in <module>
    import _bizBinary
ModuleNotFoundError: No module named '_bizBinary'

这是因为在linux系统下,默认只会在环境变量LD_LIBRARY_PATH配置的文件夹下面找动态链接库,所以需要配置环境变量。
临时解决方案:执行export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/yuxf/go/src/biz-binary/(注意路径为绝对路径),再执行就可以了。
如果要长期解决方案,这个可以用python的打包工具(比如distutils)将这个包打包成一个pip可安装包,具体教程自行百度(暂时我还不知道…)。一般用不上,除非要发到pip源上去。

编译成perl文件

思路和python的差不多,只是执行的指令变成了这三条:

swig -perl5 bizBinary.i
	gcc -c -fPIC bizBinary_wrap.c -I/usr/lib/x86_64-linux-gnu/perl/5.28/CORE -Dbool=char  -Doff64_t=__off64_t
	gcc -shared bizBinary_wrap.o -L. -lbiz-binary -o bizBinary.so

注意:因为编译的时候可能会出现类似于这种:

/usr/lib/x86_64-linux-gnu/perl/5.28/CORE/perl.h:2494:22: error: unknown type name ‘off64_t’; did you mean ‘off_t’?
 #       define Off_t off64_t

如果出现这种找不到名字的,多半得加宏,即第二条指令后面的:-Dbool=char -Doff64_t=__off64_t 如果出现其他编译问题,去找个c/c++大佬帮忙看看,我也是小白(不是小白我干嘛用cgo库,直接用c动手了)

后记

主要是公司的特殊业务需求和历史包袱导致的这种妥协手段,但是我觉得部分思路是可以通用的,即,使用go编写一个通用底座,让不同的语言使用这个底座,实现统一的输出和功能,减少工作量。