如何编译C++共享库,在Python中加载并运行它



我在C++中有一个函数,它接受整数集的列表,并在集之间有公共元素的地方合并集。代码从这里开始:基于公共元素组合成对的整数。

我的C++文件名为merge_sets.cpp,包含以下代码:

#include <algorithm>
#include <set>
#include <list>
#include <iostream>
std::list<std::set<int>> Merge(std::list<std::set<int>> values){
for( std::list<std::set<int>>::iterator iter = values.begin(); iter != values.end(); ++iter)
for(std::list<std::set<int>>::iterator niter(iter); ++niter != values.end();)
if(std::find_first_of(iter->begin(), iter->end(), niter->begin(), niter->end()) != iter->end())
{
iter->insert(niter->begin(), niter->end());
values.erase(niter);
niter = iter;
}
return values;
}
extern "C" {
std::list<std::set<int>> merge(std::list<std::set<int>> test){
return Merge(test);
}
}

我想创建一个.so共享库文件,并使用cdll将其加载到python中。LoadLibrary函数。我用以下命令创建.so文件:

g++ -c -fPIC merge_sets.cpp -Wextra -Wall -o merge_sets.o 
g++ -shared -Wl,-soname,merge_sets.so -o merge_sets.so merge_sets.o 

这成功地创建了merge_sets.so共享库。然后我尝试在python中加载库,如下所示:

from ctypes import cdll
lib = cdll.LoadLibrary("./merge_sets.so")

当我尝试运行.py脚本时,我会得到以下错误:

Traceback (most recent call last):
File "c:Userstjs_1DocumentsGit Repossingle-customer-viewtesting_python_cpp_bindings.py", line 3, in <module>
lib = cdll.LoadLibrary("./merge_sets.so")
File "C:Userstjs_1anaconda3envsSCV_envlibctypes__init__.py", line 452, in LoadLibrary
return self._dlltype(name)
File "C:Userstjs_1anaconda3envsSCV_envlibctypes__init__.py", line 374, in __init__
self._handle = _dlopen(self._name, mode)
FileNotFoundError: Could not find module 'C:Userstjs_1DocumentsGit Repossingle-customer-viewmerge_sets.so' (or one of its dependencies). Try using the full path with constructor syntax.

我怀疑这是C++脚本中依赖项的问题,因为当我将merge_sets函数转换为一个接受和integer并返回相同整数的toy函数,并且从脚本顶部删除所有#include语句时,代码运行良好。

编辑:

如果我将merge_sets更改为:

int Merge(int test){
return test;
}
extern "C" {
int merge(int test){
return Merge(test);
}
}

并重新编译到merge_sets.so,然后使用相同的Python脚本加载,就完全没有问题了。

作为测试,如果我将merge_sets.cpp更改为:

#include <algorithm>
#include <set>
#include <list>
int Merge(int test){
std::list<std::set<int>> values = {{1, 2}, {2, 4}, {8, 3}};
for( std::list<std::set<int>>::iterator iter = values.begin(); iter != values.end(); ++iter)
for(std::list<std::set<int>>::iterator niter(iter); ++niter != values.end();)
if(std::find_first_of(iter->begin(), iter->end(), niter->begin(), niter->end()) != iter->end())
{
iter->insert(niter->begin(), niter->end());
values.erase(niter);
niter = iter;
}
return test;
}
extern "C" {
int merge(int test){
return Merge(test);
}
}

我犯了和以前一样的错误。

您可以使用python c api。这是一个带有单个调用的简单扩展的示例。

// theExtension.cpp
//
/////////////////////////////////////////////////////////////////
//
// Primary module for extension.
// 
/////////////////////////////////////////////////////////////////
// Numpy uses a variable PyArray_API to define API methods; this is initialised by calling import_array.
// By default this initialises to a static variable, so is only available in the current module. 
// Defining PY_ARRAY_UNIQUE_SYMBOL indicates that the variable should be not be declared
// as static, so it can be linked to from other .cpp files. Other .cpp files must define NO_IMPORT_ARRAY
// which means PyArray_API is declared as extern, hence it is not defined multiple times.
//
#define PY_ARRAY_UNIQUE_SYMBOL VectorAdd_ARRAY_API
#define NPY_NO_DEPRECATED_API NPY_1_17_API_VERSION
#include <arrayobject.h>
/////////////////////////////////////////////////////////////////
const char* g_szBasicAdd = ""
"basic_add(output, a, b)                                           n"
"                                                                  n"
"Adds two numpy arrays and writes result to output `output = a + b`n"
"                                                                  n"
"Returns:                                                          n"
"- None                                                            n";
PyObject* basic_add(PyObject*, PyObject* args)
{
PyObject* pPyA;
PyObject* pPyB;
PyObject* pPyO;
if(!PyArg_ParseTuple(args, "OOO", &pPyO, &pPyA, &pPyB)) // object references are borrowed so do not decrement
{
PyErr_SetString(PyExc_ValueError, "Failed to parse input parameters.");
return nullptr;
}
PyArrayObject* pPyArrayA = reinterpret_cast<PyArrayObject*>(PyArray_FROM_OTF(pPyA, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY));
if(pPyArrayA == nullptr)
{
PyErr_SetString(PyExc_ValueError, "Failed to cast a.");
return nullptr;
}
size_t iSizeA = static_cast<size_t>(PyArray_SIZE(pPyArrayA));
const double* pcdA = static_cast<const double*>(PyArray_DATA(pPyArrayA));
PyArrayObject* pPyArrayB = reinterpret_cast<PyArrayObject*>(PyArray_FROM_OTF(pPyB, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY));
if(pPyArrayB == nullptr)
{
PyErr_SetString(PyExc_ValueError, "Failed to cast b.");
return nullptr;
}
size_t iSizeB = static_cast<size_t>(PyArray_SIZE(pPyArrayB));
const double* pcdB = static_cast<const double*>(PyArray_DATA(pPyArrayB));
PyArrayObject* pPyArrayO = reinterpret_cast<PyArrayObject*>(PyArray_FROM_OTF(pPyO, NPY_DOUBLE, NPY_ARRAY_OUT_ARRAY));
if(pPyArrayO == nullptr)
{
PyErr_SetString(PyExc_ValueError, "Failed to cast o.");
return nullptr;
}
size_t iSizeO = static_cast<size_t>(PyArray_SIZE(pPyArrayO));
double* pdO = static_cast<double*>(PyArray_DATA(pPyArrayO));
if(iSizeO != iSizeA || iSizeO != iSizeB)
{
PyErr_SetString(PyExc_ValueError, "Failed input arrays different sizes.");
return nullptr;
}
for(size_t i = 0; i < iSizeO; ++i)
{
pdO[i] = pcdA[i] + pcdB[i];
}
Py_INCREF(Py_None);
return Py_None;
}
/////////////////////////////////////////////////////////////////
static PyMethodDef VectorAdd_Methods[] = {
// The first property is the name exposed to Python, the second is the C++
// function name that contains the implementation.
{ "basic_add",   static_cast<PyCFunction>(basic_add), METH_VARARGS, g_szBasicAdd},
// Terminate the array with an object containing nulls.
{ nullptr, nullptr, 0, nullptr }
};

/////////////////////////////////////////////////////////////////
static PyModuleDef VectorAdd_Module = {
PyModuleDef_HEAD_INIT,
"VectorAdd",                      // Module name to use with Python import statements
"C++ module demonstrating C Api", // Module description
0,                                //
VectorAdd_Methods                 // Structure that defines the methods of the module
};
/////////////////////////////////////////////////////////////////
static bool m_bInitNumpyAPI = false;
void* _initNumpyAPI()
{
if(!m_bInitNumpyAPI)
{
import_array();
m_bInitNumpyAPI = true;
}
return NULL;
}
/////////////////////////////////////////////////////////////////
PyMODINIT_FUNC PyInit_VectorAdd()
{
_initNumpyAPI();
return PyModule_Create(&VectorAdd_Module);
}
////////////////////////////////////////////////////////////////

在Windows中,您可以在Visual Studio中构建DLL。您需要将$(gc-3-10)include;$(gc-3-10)Libsite-packagesnumpycoreincludenumpy添加到Include Directories中,其中$(gc-3-10)是您的python env的目录;然后将$(gc-3-10)Libsite-packagesnumpycorelib;$(gc-3-10)libs;设置为您的Library Directories,然后使用设置为Dynamic LibraryConfiguration Type和设置为.pydTarget File Extension进行构建;我从来没有试过在Windows中使用gcc构建python扩展。

在linux上,你可以用gcc构建一个共享的.so库,比如:

#!/bin/bash
# file: build.sh
(
cd "$(dirname "$0")"
g++-11 -fPIC 
-I ~/anaconda3/envs/py38/include/python3.8 
-I ~/anaconda3/envs/py38/lib/python3.8/site-packages/numpy/core/include/numpy 
-L"$(echo ~/anaconda3/envs/py38/lib)" 
-shared 
-o ../Lib/MyLib.so 
./theExtension.cpp 
-lpython3.8 
-O3 
-std=c++20 
-lz
)

一旦你构建了这个,将.so/.pyd目录添加到PYTHONPATH中,然后你就可以在python中使用它,比如:

import numpy as np
from VectorAdd import basic_add
a = np.array([1., 2., 3., 4.])
b = np.array([2., 4., 6, 8.])
o = np.zeros(4, dtype=np.float64)
basic_add(o, a, b)

最新更新