C++类函数封装给python调用,大致分为三个部分,第一部分是把我们的C++类函数等封装成一个dll,即动态库。第二部分是生成一个绑定代码,就是用shiboken2根据我们需要封装暴露的文件,生成pythonC++代码。然后第三部分,就是根据第一和第二部分生成的库和代码,进一步封装成py库。然后python文件直接可以调用该库。具体代码如下,源码用的是python的官方源码。主要讲解每个部分的使用。
源码:
------------------icecream.cpp------------------------------------
#include "icecream.h"
Icecream::Icecream(const std::string &flavor) : m_flavor(flavor) {}
Icecream::~Icecream() {}
const std::string Icecream::getFlavor()
{
return m_flavor;
}
Icecream *Icecream::clone()
{
return new Icecream(*this);
}
-------------------------------------------icecream.h------------------------------------------------------
#ifndef ICECREAM_H
#define ICECREAM_H
#include <string>
#include "macros.h"
class BINDINGS_API Icecream
{
public:
Icecream(const std::string &flavor);
virtual Icecream *clone();
virtual ~Icecream();
virtual const std::string getFlavor();
private:
std::string m_flavor;
};
#endif // ICECREAM_H
--------------------------------------------truck.cpp------------------------------------------------------
#include <iostream>
#include <random>
#include "truck.h"
Truck::Truck(bool leaveOnDestruction) : m_leaveOnDestruction(leaveOnDestruction) {}
Truck::Truck(const Truck &other)
{
for (size_t i = 0; i < other.m_flavors.size(); ++i) {
addIcecreamFlavor(other.m_flavors[i]->clone());
}
}
Truck &Truck::operator=(const Truck &other)
{
if (this != &other) {
clearFlavors();
for (size_t i = 0; i < other.m_flavors.size(); ++i) {
addIcecreamFlavor(other.m_flavors[i]->clone());
}
}
return *this;
}
Truck::~Truck()
{
if (m_leaveOnDestruction)
leave();
clearFlavors();
}
void Truck::addIcecreamFlavor(Icecream *icecream)
{
m_flavors.push_back(icecream);
}
void Truck::printAvailableFlavors() const
{
std::cout << "It sells the following flavors: \n";
for (size_t i = 0; i < m_flavors.size(); ++ i) {
std::cout << " * " << m_flavors[i]->getFlavor() << '\n';
}
std::cout << '\n';
}
void Truck::arrive() const
{
std::cout << m_arrivalMessage;
}
void Truck::leave() const
{
std::cout << "The truck left the neighborhood.\n";
}
void Truck::setLeaveOnDestruction(bool value)
{
m_leaveOnDestruction = value;
}
void Truck::setArrivalMessage(const std::string &message)
{
m_arrivalMessage = message;
}
bool Truck::deliver() const
{
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist(1, 2);
std::cout << "The truck started delivering icecream to all the kids in the neighborhood.\n";
bool result = false;
if (dist(mt) == 2)
result = true;
return result;
}
void Truck::clearFlavors()
{
for (size_t i = 0; i < m_flavors.size(); ++i) {
delete m_flavors[i];
}
m_flavors.clear();
}
----------------------------------------------truck.h----------------------------------------------------
#ifndef TRUCK_H
#define TRUCK_H
#include <vector>
#include "icecream.h"
#include "macros.h"
class BINDINGS_API Truck {
public:
Truck(bool leaveOnDestruction = false);
Truck(const Truck &other);
Truck& operator=(const Truck &other);
~Truck();
void addIcecreamFlavor(Icecream *icecream);
void printAvailableFlavors() const;
bool deliver() const;
void arrive() const;
void leave() const;
void setLeaveOnDestruction(bool value);
void setArrivalMessage(const std::string &message);
private:
void clearFlavors();
bool m_leaveOnDestruction = false;
std::string m_arrivalMessage = "A new icecream truck has arrived!\n";
std::vector<Icecream *> m_flavors;
};
#endif // TRUCK_H
------------------------------------------bindings.h-----------------------------------------------------
#ifndef BINDINGS_H
#define BINDINGS_H
#include "icecream.h"
#include "truck.h"
#endif // BINDINGS_H
------------------------------------------------macros.h------------------------------------------------
#ifndef MACROS_H
#define MACROS_H
#if defined _WIN32 || defined __CYGWIN__
// Export symbols when creating .dll and .lib, and import them when using .lib.
#if BINDINGS_BUILD
#define BINDINGS_API __declspec(dllexport)
#else
#define BINDINGS_API __declspec(dllimport)
#endif
// Disable warnings about exporting STL types being a bad idea. Don't use this in production
// code.
#pragma warning( disable : 4251 )
#else
#define BINDINGS_API
#endif
#endif // MACROS_H
------------------------------------------bindings.xml------------------------------------------------
<?xml version="1.0"?>
<typesystem package="Universe">
<primitive-type name="bool"/>
<primitive-type name="std::string"/>
<object-type name="Icecream">
<!-- By default the ownership of an object created in Python is tied
to the Python name pointing to it. In order for the underlying
C++ object not to get deleted when the Python name goes out of
scope, we have to transfer ownership to C++.
-->
<modify-function signature="clone()">
<modify-argument index="0">
<define-ownership owner="c++"/>
</modify-argument>
</modify-function>
</object-type>
<value-type name="Truck">
<!-- Same ownership caveat applies here. -->
<modify-function signature="addIcecreamFlavor(Icecream*)">
<modify-argument index="1">
<define-ownership owner="c++"/>
</modify-argument>
</modify-function>
</value-type>
</typesystem>
--------------------------------以上为源码----------------------------------------------------
编译使用cmake:
CMakeLists.txt如下,(这是重点)
-----------------------------------------------------------------------------------------------
cmake_minimum_required(VERSION 3.1)
cmake_policy(VERSION 3.1)
# Enable policy to not use RPATH settings for install_name on macOS.
if(POLICY CMP0068)
cmake_policy(SET CMP0068 NEW)
endif()
# Consider changing the project name to something relevant for you.
project(SampleBinding)
# ================================ General configuration ======================================
# Set CPP standard to C++11 minimum.
set(CMAKE_CXX_STANDARD 11)
# The sample library for which we will create bindings. You can change the name to something
# relevant for your project.
set(sample_library "libuniverse")
#设置一个库的名称,sample_library相当libuniverse,为了下面名称统一,方便修改
# The name of the generated bindings module (as imported in Python). You can change the name
# to something relevant for your project.
set(bindings_library "Universe")
# The header file with all the types and functions for which bindings will be generated.
# Usually it simply includes other headers of the library you are creating bindings for.
set(wrapped_header ${CMAKE_SOURCE_DIR}/bindings.h)
#设置绑定文件,都是用一个变量来代替文件,不懂百度cmake set.
# The typesystem xml file which defines the relationships between the C++ types / functions
# and the corresponding Python equivalents.
set(typesystem_file ${CMAKE_SOURCE_DIR}/bindings.xml)
# Specify which C++ files will be generated by shiboken. This includes the module wrapper
# and a '.cpp' file per C++ type. These are needed for generating the module shared
# library.
set(generated_sources
${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/universe_module_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/icecream_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/truck_wrapper.cpp)
#这个是我们生产py库的时候需要的源文件,也就是用第二部分shiboken2生成
# ================================== Shiboken detection ======================================
# Use provided python interpreter if given.
if(NOT python_interpreter)
find_program(python_interpreter "python")
endif()
message(STATUS "Using python interpreter: ${python_interpreter}")
# Macro to get various pyside / python include / link flags and paths.
# Uses the not entirely supported utils/pyside2_config.py file.
macro(pyside2_config option output_var)
if(${ARGC} GREATER 2)
set(is_list ${ARGV2})
else()
set(is_list "")
endif()
execute_process(
COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py"
${option}
OUTPUT_VARIABLE ${output_var}
OUTPUT_STRIP_TRAILING_WHITESPACE)
if ("${${output_var}}" STREQUAL "")
message(FATAL_ERROR "Error: Calling pyside2_config.py ${option} returned no output.")
endif()
if(is_list)
string (REPLACE " " ";" ${output_var} "${${output_var}}")
endif()
endmacro()
# Query for the shiboken generator path, Python path, include paths and linker flags.
pyside2_config(--shiboken2-module-path shiboken2_module_path)
pyside2_config(--shiboken2-generator-path shiboken2_generator_path)
pyside2_config(--python-include-path python_include_dir)
pyside2_config(--shiboken2-generator-include-path shiboken_include_dir 1)
pyside2_config(--shiboken2-module-shared-libraries-cmake shiboken_shared_libraries 0)
pyside2_config(--python-link-flags-cmake python_linking_data 0)
set(shiboken_path "${shiboken2_generator_path}/shiboken2${CMAKE_EXECUTABLE_SUFFIX}")
if(NOT EXISTS ${shiboken_path})
message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}")
endif()
#以上这些只是为了找出一个库和一些需要包含的路径,理论上可以自己直接设置,
# ==================================== RPATH configuration ====================================
# =============================================================================================
# !!! (The section below is deployment related, so in a real world application you will want to
# take care of this properly with some custom script or tool).
# =============================================================================================
# Enable rpaths so that the built shared libraries find their dependencies.
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH ${shiboken2_module_path} ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================
# =============================== CMake target - sample_library ===============================
# Define the sample shared library for which we will create bindings.
set(${sample_library}_sources icecream.cpp truck.cpp)#设置动态库需要的源文件
add_library(${sample_library} SHARED ${${sample_library}_sources})#生成一个动态库
set_property(TARGET ${sample_library} PROPERTY PREFIX "")
# Needed mostly on Windows to export symbols, and create a .lib file, otherwise the binding
# library can't link to the sample library.
target_compile_definitions(${sample_library} PRIVATE BINDINGS_BUILD)
#宏定义变量BINDINGS_BUILD,因为封装库导出需要
# ====================== Shiboken target for generating binding C++ files ====================
#下面是设置生成器的路径
# Set up the options to pass to shiboken.
set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic
--enable-return-value-heuristic --use-isnull-as-nb_nonzero
--avoid-protected-hack
-I${CMAKE_SOURCE_DIR}
-T${CMAKE_SOURCE_DIR}
--output-directory=${CMAKE_CURRENT_BINARY_DIR}
)
set(generated_sources_dependencies ${wrapped_header} ${typesystem_file})
# Add custom target to run shiboken to generate the binding cpp files.
add_custom_command(OUTPUT ${generated_sources}
COMMAND ${shiboken_path}
${shiboken_options} ${wrapped_header} ${typesystem_file}
DEPENDS ${generated_sources_dependencies}
IMPLICIT_DEPENDS CXX ${wrapped_header}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Running generator for ${typesystem_file}.")
# =============================== CMake target - bindings_library =============================
# Set the cpp files which will be used for the bindings library.
set(${bindings_library}_sources ${generated_sources})
# Define and build the bindings library.
add_library(${bindings_library} MODULE ${${bindings_library}_sources})
# Apply relevant include and link flags.
target_include_directories(${bindings_library} PRIVATE ${python_include_dir})
target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir})
target_include_directories(${bindings_library} PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries})
target_link_libraries(${bindings_library} PRIVATE ${sample_library})
# Adjust the name of generated module.
set_property(TARGET ${bindings_library} PROPERTY PREFIX "")
set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME
"${bindings_library}${PYTHON_EXTENSION_SUFFIX}")
if(WIN32)
set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd")
endif()
# Make sure the linker doesn't complain about not finding Python symbols on macOS.
if(APPLE)
set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)
# Find and link to the python import library only on Windows.
# On Linux and macOS, the undefined symbols will get resolved by the dynamic linker
# (the symbols will be picked up in the Python executable).
if (WIN32)
list(GET python_linking_data 0 python_libdir)
list(GET python_linking_data 1 python_lib)
find_library(python_link_flags ${python_lib} PATHS ${python_libdir} HINTS ${python_libdir})
target_link_libraries(${bindings_library} PRIVATE ${python_link_flags})
endif()
# ================================= Dubious deployment section ================================
if(WIN32)
# =========================================================================================
# !!! (The section below is deployment related, so in a real world application you will
# want to take care of this properly (this is simply to eliminate errors that users usually
# encounter.
# =========================================================================================
# Circumvent some "#pragma comment(lib)"s in "include/pyconfig.h" which might force to link
# against a wrong python shared library.
set(python_versions_list 3 32 33 34 35 36 37 38)
set(python_additional_link_flags "")
foreach(ver ${python_versions_list})
set(python_additional_link_flags
"${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}_d.lib\"")
set(python_additional_link_flags
"${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}.lib\"")
endforeach()
set_target_properties(${bindings_library}
PROPERTIES LINK_FLAGS "${python_additional_link_flags}")
# Add custom target to hard-link shiboken shared libraries into the build folder, so that
# the user doesn't have to set the PATH manually to point to the PySide2 package.
foreach(library_path ${shiboken_shared_libraries})
string(REGEX REPLACE ".lib$" ".dll" library_path ${library_path})
get_filename_component(base_name ${library_path} NAME)
file(TO_NATIVE_PATH ${library_path} source_path)
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${base_name}" dest_path)
add_custom_command(OUTPUT "${base_name}"
COMMAND mklink /H "${dest_path}" "${source_path}"
DEPENDS ${library_path}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Creating hardlink to shiboken shared library ${base_name}")
# Fake target that depends on the previous one, but has special ALL keyword, which means
# it will always be executed.
add_custom_target("fake_${base_name}" ALL DEPENDS ${base_name})
endforeach()
# =========================================================================================
# !!! End of dubious section.
# =========================================================================================
endif()
# =============================================================================================
# !!! (The section below is deployment related, so in a real world application you will want to
# take care of this properly with some custom script or tool).
# =============================================================================================
# Install the library and the bindings module into the source folder near the main.py file, so
# that the Python interpeter successfully imports the used module.
install(TARGETS ${bindings_library} ${sample_library}
LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}
RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}
)
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================
-------------------------------------------------------------------------------------------------------------
怎么执行;把上面的文件放在一个文件夹上,然后在该文件夹上新建一个空的文件夹build,进入到build文件夹下,打开cmd命令行。然后cmake -H.. -B. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
生成一个makefile,继续执行nmake,就能生成我们需要的库,把生成的库一个.dll,一个.pyd,可能还有其余pyside,shiboken放一起,或者你设置的环境变量都能找到也没问题。
然后写一个py调用
-----------------------------------------------------------------------------------------
from __future__ import print_function
"""An example showcasing how to use bindings for a custom non-Qt C++ library"""
from Universe import Icecream, Truck
class VanillaChocolateIcecream(Icecream):
def __init__(self, flavor=""):
super(VanillaChocolateIcecream, self).__init__(flavor)
def clone(self):
return VanillaChocolateIcecream(self.getFlavor())
def getFlavor(self):
return "vanilla sprinked with chocolate"
class VanillaChocolateCherryIcecream(VanillaChocolateIcecream):
def __init__(self, flavor=""):
super(VanillaChocolateIcecream, self).__init__(flavor)
def clone(self):
return VanillaChocolateCherryIcecream(self.getFlavor())
def getFlavor(self):
base_flavor = super(VanillaChocolateCherryIcecream, self).getFlavor()
return base_flavor + " and a cherry"
if __name__ == '__main__':
leave_on_destruction = True
truck = Truck(leave_on_destruction)
flavors = ["vanilla", "chocolate", "strawberry"]
for f in flavors:
icecream = Icecream(f)
truck.addIcecreamFlavor(icecream)
truck.addIcecreamFlavor(VanillaChocolateIcecream())
truck.addIcecreamFlavor(VanillaChocolateCherryIcecream())
truck.arrive()
truck.printAvailableFlavors()
result = truck.deliver()
if result:
print("All the kids got some icecream!")
else:
print("Aww, someone didn't get the flavor they wanted...")
if not result:
special_truck = Truck(truck)
del truck
print("")
special_truck.setArrivalMessage("A new SPECIAL icecream truck has arrived!\n")
special_truck.arrive()
special_truck.addIcecreamFlavor(Icecream("SPECIAL *magical* icecream"))
special_truck.printAvailableFlavors()
special_truck.deliver()
print("Now everyone got the flavor they wanted!")
special_truck.leave()
--------------------------------------------------------------------------------------------------
在制作过程中可能遇到很多问题:
问题1:遇到一些编译器问题,环境变量设置不对,调用不到自己需要的编译器,这个一般是出现在生成makefile的过程中出现,找出你电脑中安装的vs等编译器中vcvars32.bat,这种批量处理的脚本,在控制台执行就会帮助你配置一些库和exe的调用。
问题2:解析xml或者是.h时候出错。.h问题一般是没有在cmake中设置包含,或者是没有包含相关文件夹。
xml问题可能会找不到shiboken,那就是可能电脑没有安装shiboken2_generator的生成器。
生成过程中需要暴露的东西,需要在xml说明。bindings.h包含相关头文件.
https://download.qt.io/official_releases/QtForPython/shiboken2-generator/
这个链接是生成器的下载路径,选择对于版本。
xml头绑定的一些规则:
http://pyside.github.io/docs/api-extractor/typesystem.html
生成器的一些参数设置:http://pyside.github.io/docs/shiboken/
上面需要安装的东西:最基本的,pyside2,shiboken,cmake,shiboken2_generator,vs或者其他编译器.
上面例子可在你安装的D:\PyThon3.5\Lib\site-packages\PySide2\examples\samplebinding