概念
对于一些上了年纪的项目,重构耗时耗力太大动干戈,不重构又问题重重。举个简单的例子,我司有一些项目是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.h
和libbiz-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.py
和bizBinary_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编写一个通用底座,让不同的语言使用这个底座,实现统一的输出和功能,减少工作量。