使用c++调用python(一)

c调用python的原理大概是将python当做一个c++库来调用

window

导入头文件

将python的头文件复制到项目中,头文件在python安装目录中
image
在vs项目属性中的vc++目录的包含目录添加头文件路径
image

导入库文件(x64就导入64位的python,x86就导入32位的python,不能混在一起)

库文件在python安装目录的libs文件夹中
image
在vs项目属性中的vc++目录的库目录添加库文件所在文件路径
image
添加附加依赖项,没有python37_d.lib的把python37.lib复制一份重命名为python37_d.lib就行了
python37.lib
python3.lib
python37_d.lib
image

添加二进制文件

第一种方案

需要的二进制文件有 python3*.dll、python3.dll、DLLs、Lib文件夹(注意Lib中的site-packages文件的第三方python库按实际需要增减)
image

第二种方案

去python 官网下载内嵌包
image
image
image
解压后,里面所有文件都需要,然后同样的site-packages按实际需要增减

使用api调用python代码

initializer.h

#pragma once


class initializer {
public:
	initializer();
	~initializer();
private:
	static initializer initer;
};

initializer.cpp

#include "initializer.h"
#include <Python.h>
#ifdef _WIN32
#include <Windows.h>
#elif defined __APPLE__

#endif
#include "utils.h"
#include <vector>



initializer::initializer()
{
	

#ifdef _WIN32
#include <Windows.h>
		SetDllDirectory(TEXT("python37\\"));
#elif defined __APPLE__

#endif
	auto combinePaths = [](std::vector<std::string> paths,std::string sep=";") {
		std::string preparedPath = "";
		for (int i = 0; i < paths.size(); i++) {
			preparedPath += get_executable_dir()+paths[i];
			if (i != paths.size() - 1) {
				preparedPath += sep;
			}
		}
		return string2wstring(preparedPath);
	};

		Py_SetPythonHome(string2wstring(get_executable_dir()).c_str());
#ifdef _WIN32
		Py_SetPath(combinePaths({"","\\python37.zip","\\site-packages"}).c_str());
#elif defined __APPLE__
		Py_SetPath(combinePaths({"","/python37.zip","/site-packages","/lib-dynload"},":").c_str());
#endif
		Py_Initialize();

		PyRun_SimpleString("import sys");
		PyRun_SimpleString("print(sys.path)");

}

initializer::~initializer()
{
	
	Py_Finalize();
	
}

utils.h

#include <iostream>


std::string get_executable_dir();
std::string wstring2string(const std::wstring& ws);
std::wstring string2wstring(const std::string& s);

utils_win.cpp

#include "utils.h"
#include <Windows.h>
#include <comutil.h>
using namespace std;
#pragma comment(lib , "comsupp.lib")
#pragma comment(lib , "comsuppw.lib")
std::string get_executable_dir() {
	 std::string exeDir = "";
	char szExeName[MAX_PATH] = {0,};
	volatile int lastIndex = 0;
	::GetModuleFileNameA(NULL, szExeName, MAX_PATH);
	exeDir = szExeName;
	lastIndex = exeDir.find_last_of("\\");
	exeDir = exeDir.substr(0, lastIndex);
	return exeDir;
}
std::string wstring2string(const wstring& ws)
{
	_bstr_t t = ws.c_str();
	char* pchar = (char*)t;
	string result = pchar;
	return result;
}
std::wstring string2wstring(const string& s)
{
	_bstr_t t = s.c_str();
	wchar_t* pwchar = (wchar_t*)t;
	wstring result = pwchar;
	return result;
}

main.cpp

#include <iostream>
#include <Python.h>
#include "initializer.h"

using namespace std;
initializer  initer;

void call_main() {
	char* result;
	PyObject* pModule = PyImport_ImportModule("test");
	if (pModule == NULL) {
		PyErr_Print();
		cout << "module not found" << endl;
		return;
	}

	PyObject* pFunc = PyObject_GetAttrString(pModule, "main");
	if (!pFunc || !PyCallable_Check(pFunc)) {
		PyErr_Print();
		cout << "not found function init" << endl;
		return;
	}

	PyObject* pReturn = PyObject_CallObject(pFunc, NULL);
	PyErr_Print();
}
void inputArgs(int argc, char** argv) {

	if (argc > 0)
		for (int i = 0; i < argc; i++)
			PyRun_SimpleString((string("sys.argv.append(r'") + argv[i] + "')").c_str());
}
int main(int argc, char** argv)
{

	inputArgs(argc, argv);
	call_main();


	return 0;
}

编译出exe后在当前目录建立test.py
test.py

def main():
	print("hello world!")

将依赖的二进制放入exe当前目录即可执行成功

Macos

导入头文件

头文件在python安装目录中include/python3.7m中(以python3.7为例)
image
在Header Search Paths中添加头文件路径
image

导入库文件

库文件Python
image

修改install_name

由于Python的install name是固定的路径,我们程序加载的时候会去这个绝对路径找Python,还好install_name_tool可以修改
有两种方式,一是修改Python的 install name,二是修改可执行文件的库加载路径
修改Python的 install name

install_name_tool -id @rpath/Python Python

修改可执行文件的库加载路径

install_name_tool -change  /Library/Frameworks/Python.framework/Versions/3.7/Python @loader_path/Python  meidaDownloader

推荐使用第二种,最好不要修改Python库自带的Python文件

xcode将库文件导入进来
image

添加二进制文件

添加基础动态库

image
将上图使用红线标出的库和lib-dynload都复制出来,并且把那些库都放在lib-dynload里
这些动态库并不能用,因为它们和Python一样install_name是绝对路径,需要修改

czl@chenzhouliang lib % otool -L libssl.1.1.dylib   
libssl.1.1.dylib:
	/Library/Frameworks/Python.framework/Versions/3.7/lib/libssl.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
	/Library/Frameworks/Python.framework/Versions/3.7/lib/libcrypto.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

比如上面这个,libssl.1.1.dylib自己的install name 不需要改 ,但是第二条依赖的libcrypto.1.1.dylib路径要改成相对路径

czl@chenzhouliang lib % otool -L libssl.1.1.dylib
libssl.1.1.dylib:
	@rpath/libssl.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
	@loader_path/libcrypto.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

改成这样即可
可是动态库太多了怎么办,可以使用脚本批量替换

import os,json,sys,re
def execCmd(cmd):
    r = os.popen(cmd)
    text = r.readlines()
    r.close()
    return text

def adjust_installName(filename,old,new):
    GET_LIB_INFO = "otool -L %s"
    REGEX = "\t/Library/Frameworks/Python.framework/Versions/3.7/lib/(.+?) \(compatibility "
    CMD = "install_name_tool -change  %s%s %s%s  %s"
    # 获取信息
    text = execCmd(GET_LIB_INFO % filename)

    # 获取名称
    names = []
    for t in text:
        m_obj = re.search(REGEX,t)
        if m_obj is not None:
            LibName = m_obj.group(1)
            names.append(LibName)

    # 构造命令字符串
    for name in  names:
        cmd = CMD % (old,name,new,name,filename)
        print("修改%s的install name %s" % (filename,cmd))
        execCmd(cmd)

if __name__ == "__main__":
    target_dir="/Users/czl/Desktop/st/cPythonDemo/sdk/mac/bin/lib-dynload"
    old = "/Library/Frameworks/Python.framework/Versions/3.7/lib/"
    new = "@loader_path/"

    # 通过文件夹获取文件名
    g = os.walk(target_dir)
    filenames = []
    for path,dir_list,file_list in g:
        for file_name in file_list:
            filenames.append(os.path.join(path, file_name))
    for filename in filenames:
        try:
            adjust_installName(filename,old,new)
        except Exception:
            pass

target_dir是动态库所在文件夹,old是原来的install name(不要带文件名),new 是新的intall name

添加内置python库

image
上图中所显示的文件夹中除了lib-dynload、site-packages都压缩起来,并命名为python37.zip

添加第三方库

image
将site-packages复制出来,里面的包按实际情况删减

最后大概所有的二进制如下图所示:
image

使用api调用python代码

代码和上面差不多,但是要把utils_win.cpp 改成 utils_mac.cpp
utils_mac.cpp

#include <string>
#include <locale>
#include <codecvt>
#include "utils.h"
#include <CoreFoundation/CFBundle.h>

std::string get_executable_dir()
{

  CFURLRef resourceURL = CFBundleCopyExecutableURL(CFBundleGetMainBundle());
    std::string bunddlePath;
    char resourcePath[PATH_MAX];
  if (CFURLGetFileSystemRepresentation(resourceURL, true,
                                       (UInt8 *)resourcePath,
                                       PATH_MAX))
  {
    if (resourceURL != NULL)
    {
      CFRelease(resourceURL);
    }
      bunddlePath = resourcePath;
      bunddlePath = bunddlePath.substr(0,bunddlePath.find_last_of("/"));
      
    return std::string(bunddlePath);
  }
    return "";
}


  // convert wstring to string 
   std::string wstring2string(const std::wstring& input)
  {
    //std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
    std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    return converter.to_bytes(input);
  }

    // convert string to wstring
   std::wstring string2wstring(const std::string& input)
  {
    std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    return converter.from_bytes(input);
  }

最后附上源码:https://github.com/2963663242/cPythonDemo

posted @ 2022-10-12 01:47  乘舟凉  阅读(811)  评论(0编辑  收藏  举报