我目前在实现一个隐私保护联邦学习论文的代码,其中联邦学习的模型训练用python写的代码,隐私保护用c++写的(一种同态加密算法)代码。我的目标是将密码算法用于加密联邦学习每轮训练得到的梯度信息。因此,考虑python调用c++实现。
近一周都在度娘上搜索学习,发现介绍最多的python调用c++方法是将c++函数暴露c语言接口(供后续python调用),再将cpp文件编译为so动态链接库,python端用ctypes调用,这个方法最简单。然而实际实现的时候,遇到很多令人吐血的问题,还好最后终于调用成功了。这里详细记录一下我遇到的问题和解决方案,附参考。
首先了解到:
(1)对于python,ctypes只能load动态库;
(2)静态库可以被动态库链接,而后给python调用,但是静态库在编译过程中没有加–fPIC参数,就不可以被动态库链接;
(3)我的C++代码调用了NTL库和GMP库(这两个也是c++库),但是这两个库的静态库libntl.a和libgmpxx.a, libgmp.a在编译时都没用–fPIC参数编译,所有我在动态库链接静态库时报错“undefined symbol”。
解决方案:重新编译NTL和GMP库,生成带PIC的静态库文件
对于NTL库,修改其makefile文件中的CXXFLAGS如下:
# CXXFLAGS=-g -O2 # 原来的
CXXFLAGS = -g -fPIC # 修改后的
# Flags for the C++ compiler
然后在makefile文件夹路径下开启终端,输入make clobber(makefile中有提示,删去已有的.o文件等), 然后make,重新生成ntl.a (或者libntl.a) 文件,拷贝出来,备用。
$make clobber
$make
对于GMP库,我在官网下载的包里没有找到makefile文件,它是在configure时生成makefile
因此需要在安装时指定-fPIC。
GMP的安装:安装GMP 之前需要先安装m4 (不然会出错)
$sudo apt-get install m4
然后安装GMP,带上pic
$./configure --enable-cxx --with-pic //https://blog.csdn.net/icesnowjank/article/details/111171028
$make
$make check
$make install
我在make install时出错“cannot create directory Permission denied”,我记得第一次装的时候没这个问题。这个小问题可以参考 这里 的方案解决。
安装好后,终端会提示GMP安装到了哪里,一般在usr/local里,进入这个文件夹下,拷贝出libgmpxx.a和libgmp.a两个静态库,备用。
接下来,编译自己写的c++项目的静态库。
参考 这里(暴露接口函数,在代码中使用extern关键字将代码包裹起来,让编译器以C语言的方式编译), 这里(静态库生成、打包、解包等)和 这里(注意动态库生成顺序问题)。
具体地,在你的cpp或这h文件中写入形如这样的extern语句(为了方便展示,我直接复制了我的部分代码了):
extern "C" {
MKTestScheme mkTestScheme; // 定义一个自己写的MKTestScheme类 mkTestScheme
void testMKEncodeBatch(long logN, long logP, long logQ, long logq0, long logSlots, long slots2, long CLIENT, bool isComplex, long numAgg) {
mkTestScheme.testMKEncodeBatch(logN, logP, logQ, logq0, logSlots, slots2, CLIENT, isComplex, numAgg);
}
void testMKEncryptBatch(long logN, long logP, long logQ, long logq0, long logSlots, long slots2, long CLIENT, bool isComplex, long numAgg) {
mkTestScheme.testMKEncryptBatch(logN, logP, logQ, logq0, logSlots, slots2, CLIENT, isComplex, numAgg);
}
void testMKPackEncrypt(long logN, long logP, long logQ, long logq0, long logSlots, long slots2, long CLIENT, bool isComplex, long numAgg, long numClients) {
mkTestScheme.testMKPackEncrypt(logN, logP, logQ, logq0, logSlots, slots2, CLIENT, isComplex, numAgg, numClients);
}
void testMKPackEncryptADD() {
mkTestScheme.testMKPackEncryptADD();
}
}
我extern写在了cpp文件里。这里还要注意一点就是cpp对应的.h文件中所include的头文件需要写成public继承形式(其他的未暴露接口函数的cpp文件的.h文件也是一样操作):
#ifndef MKHE_MKTESTSCHEME_H_
#define MKHE_MKTESTSCHEME_H_
#include
#include
#include
#include "StringUtils.h"
#include "TimeUtils.h"
#include "MKScheme.h"
using namespace std;
using namespace NTL;
class MKTestScheme:public StringUtils, public TimeUtils, public MKScheme {
public:
static void testMKEncodeBatch(long logN, long logP, long logQ, long logq0, long logSlots, long slots2, long CLIENT, bool isComplex, long numAgg);
static void testMKEncryptBatch(long logN, long logP, long logQ, long logq0, long logSlots, long slots2, long CLIENT, bool isComplex, long numAgg);
static void testMKPackEncrypt(long logN, long logP, long logQ, long logq0, long logSlots, long slots2, long CLIENT, bool isComplex, long numAgg, long numClients);
static void testMKPackEncryptADD();
};
#endif
至此,开始编译自己写的c++项目的静态库:找到cpp文件所在文件夹,打开终端,输入:
$g++ -c -fPIC file1.cpp file2.cpp file3.cpp ... //将自己写的cpp文件全部编译为.o文件
$ar crU libmine.a *.o //所有.o文件打包为.a文件(可选,供后续移植解包使用o文件)
所有静态库打包为动态库
将上面生成的libntl.a, libgmpxx.a, libgmp.a, libmine.a 全部拷贝出来,放在新建的一个文件夹里,并在此文件夹下打开终端,执行下面命令,将所有静态库打包为动态库:
$ar x libntl.a //拆包
$ar x libgmpxx.a
$ar x libgmp.a
$ar x libmine.a
$ar crU libXXX.a *.o //所有.o文件打包为libXXX.a文件
$ranlib libXXX.a // 更新库的符号索引表
$ar x libXXX.a
$g++ -o libencryption.so -shared -fPIC *.o
最后一步啦,哈哈哈
拷贝动态库libencryption.so所在路径,进入python程序,新建py文件,写动态库的调用代码:
import ctypes
dll = ctypes.cdll.LoadLibrary
lib = dll('/home/xxx/HE_encryption/build/libencryption.so') // 括号里输入拷贝的路径
lib.testMKPackEncryptADD() // 调用自己写的c++代码中暴露的函数
运行成功,和在c++中执行结果一样,哈哈哈。接下来,我要去研究c++暴露接口函数的优化问题了。