C++调用Python(conda)混合编程实现拾遗
对于C与Python的混合编程这一手段站内许多老师已经总结过了本篇博客主要参考https://blog.csdn.net/Anzerwhite/article/details/142625045#%3A~%3Atext%3DPython%E3%80%81PyB在前者基础上补充供各位同仁参考。采用VS2022为IDE编写C程序用Conda隔离Python环境将Conda环境下的include地址键入【附加包含目录】或者【包含目录】将Conda环境下的libs地址键入【附加库目录】或者【库目录】并将Python对应版本的.lib文件键入附加依赖项。如果键入静态链接文件的绝对地址可不填写【库目录】。如果运行时动态时出现动态链接库报错需要将对应python版本的dll文件置于可执行文件同级文件夹。DEBUG模式下的混合编程如果出现查找不到PythonXX_d.lib的LINK报错应是_DEBUG宏作用下指向错误名称的静态链接库我的解决思路是在链接.lib文件与CRT头文件cstdlib、crtdbg.h时暂时将取消_DEBUG的预编译动作#ifdef _DEBUG #define _DEBUG_SAVED #undef _DEBUG #endif #include cstdlib #include crtdbg.h #include Python.h #ifdef _DEBUG_SAVED #define _DEBUG 1 #undef _DEBUG_SAVED #endifPython path configuration报错解决方式C端控制台在Py_Initialize();时常见报错Python path configuration: PYTHONHOME (not set) PYTHONPATH (not set) program name python isolated 0 environment 1 user site 1 import site 1 sys._base_executable E:\\POS_v2d1\\POS_S\\x64\\Debug\\POS_P.exe sys.base_prefix sys.base_exec_prefix sys.executable E:\\POS_v2d1\\POS_S\\x64\\Debug\\POS_P.exe sys.prefix sys.exec_prefix sys.path [ E:\\POS_v2d1\\POS_S\\x64\\Debug\\python38.zip, .\\DLLs, .\\lib, E:\\POS_v2d1\\POS_S\\x64\\Debug, ] Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding Python runtime state: core initialized ModuleNotFoundError: No module named encodings此时应在初始化时指定环境地址//解决Python path configuration报错 Py_SetPythonHome(LE:/Miniconda/envs/env_SAM); Py_Initialize(); PyObject* sysPath PySys_GetObject(path); PyObject* pPath PyUnicode_FromString(R(E:\POS_v2d1\POS_S\POS_P)); PyObject* pPath_packages PyUnicode_FromString(R(E:\Miniconda\envs\env_SAM\Lib\site-packages)); PyList_Append(sysPath, pPath); Py_DECREF(pPath); PyList_Append(sysPath, pPath_packages); Py_DECREF(pPath_packages);ImportError报错解决方式如果函数执行失败运行下面语句查看详细报错PyErr_Print();如ImportError: DLL load failed while importing XXX: 找不到指定的模块。.pyd效用等同于.dll对于一部分.pyd文件导入失败错误将其对应的.dll文件放置在.exe同级文件夹下可解决。采用Conda环境其一般在环境\\Library\\bin然而对于一些图像库比如openCV或者PIL这种方式可能会不起作用那么就要硬编码方式在初始化.pyd运行环境之前指定.dll寻找的路径以我的路径为例SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_USER_DIRS); AddDllDirectory(LE:/Miniconda/envs/env_SAM);对于第一句中的宏VS会建议导入libloaderapi.h但是本人在导入libloaderapi.h之后出现奇怪的错误libloaderapi.h被windows.h包含直接导入后者不会出现问题。Python中变量的生命周期以我的程序为例子#include SAM.hpp namespace SAM_PYTHON { SAM_c::SAM_c() { SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_USER_DIRS); AddDllDirectory(LE:/Miniconda/envs/env_SAM); Py_SetPythonHome(LE:/Miniconda/envs/env_SAM); Py_Initialize(); PyObject* sysPath PySys_GetObject(path); PyObject* pPath PyUnicode_FromString(R(E:\POS_v2d1\POS_S\POS_P)); PyObject* pPath_packages PyUnicode_FromString(R(E:\Miniconda\envs\env_SAM\Lib\site-packages)); PyList_Append(sysPath, pPath); Py_DECREF(pPath); PyList_Append(sysPath, pPath_packages); Py_DECREF(pPath_packages); this-pModule PyImport_ImportModule(SAM); PyObject* pFunc PyObject_GetAttrString(this-pModule, init_model); if (pFunc PyCallable_Check(pFunc)) { PyObject* pValue PyObject_CallObject(pFunc, nullptr); if (pValue ! nullptr) { Py_DECREF(pValue); } Py_DECREF(pFunc); std::cout SAM has been loaded. std::endl; } } PyObject* SAM_c::getpModule() { return this-pModule; } }from segment_anything import SamPredictor, sam_model_registry import numpy as np import cv2 _predictor None _ckpt rE:\SBT\sam_vit_b_01ec64.pth def init_model(): global _predictor if _predictor is None: sam sam_model_registry[vit_b](checkpoint_ckpt) _predictor SamPredictor(sam) return True def _get_predictor(): if not init_model(): raise RuntimeError(init_model failed) return _predictor def fun2(x, y): img cv2.imread(rE:\POS_v2d1\POS_S\POS_P\sc.jpg) if img is None: raise RuntimeError(Failed to read sc.jpg) predictor _get_predictor() #...以下省略Python中用global标记的全局变量在初始化Python解释器之后卸载对应模块或关闭解释器之前被存在内存中。因为SAM的模型比较大所以我想仅加载一次随后需要调用模型时直接从全局变量读取。在C的构造函数中调用init_model之后调用fun2等。详细文档见https://docs.python.org/zh-cn/3.12/c-api/intro.html