前言
本文介绍C++(vs2013)和 Python (3.7 32bit)编程的方式
文章作者在进行学习后,将整个过程进行记录,供大家参考,也备自己查询
一、安装python 和 设置visual studio 2013
1.在win10上安装python
1.0初遇问题:
本机环境 win10 + visual studio2013 + vs code
笔者下载了 python3.7后,安装到本机,并设置了系统的环境变量Path后(设置方法:此电脑–>右键单击->属性->关于->高级系统设置->“环境变量”)。在CMD命令行下,输入Python.exe. windows10依然提示要到Microsof App Store 下载最新版本的Python。此处怀疑windows 的cmd 对python命令进行了特殊处理
解决:
依然安装自己的python3.7 32bit 。通过设置来解决程序调用python.exe的问题(后面有提到)
1.1.下载python37 32.bit
笔者因环境要求,所以必须使用32bit. (注意visual studio 2013 编译的程序如果为32bit时 ,其对应的python也必须使用32bit的)
Python 下载(python.org)
开始安装,记录python的安装路径(例如:d:\Program Files (x86)\Python\Python37-32)
1.2 设置vs2013的环境
新建MFCApplication(EXE)
1.建立的向导: 单文档, MBCS(非unicode) , 静态依赖MFC
2.并将项目属性“VC++目录”设置如下
设置内容 | 详细 | 参考路径 |
---|---|---|
1.可执行文件目录 | python.exe(3.7 32bit)所在路径 | d:\Program Files (x86)\Python\Python37-32 |
2.包含文件目录 | python.h所在路径 | d:\Program Files (x86)\Python\Python37-32\include |
3.库目录 | python37.lib所在路径 | d:\Program Files (x86)\Python\Python37-32\libs (注意不是LIB文件夹) |
另外,还应设置系统的环境变量Path中增加 d:\Program Files (x86)\Python\Python37-32 这个目录
1.3 设置vs code
A.进入vs code ,新建callc.py文件。
B.在设置->Command Palette 中 输入 Python:Select Interpreter
C.选中Python 3.7.0 32-bit (可以注意到本机安装了多个python)
请大家注意可能每次启动vs code 都需要此步设置
二、C++ 调用 Python
1.简单原理
c++可以调用python的脚本,实际上是c++将python的解释器加入到了程序中(通过python37.dll).
2.配置c++所需要的库
设置附加依赖: python37.lib
3 代码
1.需要使用头文件 “python.h”
2.初始化函数 Py_Initialize
3.加载模块 PyImport_ImportModule
4.找到函数 PyObject_GetAttrString
5.设置参数Py_BuildValue
6.调用函数 PyObject_CallObject
7.返回值解析 PyArg_Parse
c++文件部分
#include "python.h"
void myMain()
{
// Python解释器初始化
Py_Initialize();
// 打开.py文件。注意参数是"callc"而不是"callc.py"
PyObject* pModule = PyImport_ImportModule("callc");
if (pModule)
{
// 读取文件中的函数,参数为函数名
PyObject* pFunc = PyObject_GetAttrString(pModule, "func1");
if (pFunc)
{
// 构建参数,包含两个double
PyObject* args = Py_BuildValue("(dd)", 1.5, 2.0);
// 调用函数得到返回值
PyObject* res = PyObject_CallObject(pFunc, args);
if (!res)
{
// 打印错误信息
PyErr_Print();
}
else
{
// 从PyObject中得到返回值
double dr;
PyArg_Parse(res, "d", &dr);
CString str;
str.Format("%f", dr);
AfxMessageBox(str);
}
}
}
// 关闭Python解释器
Py_Finalize();
}
python文件 callc.py
# 本文件名为 callc.py
def func1(a, b):
return a + b;
三 Python 调用 C++
0.新建MfcLibray DLL
visual studio 使用MFC DLL模板,新建一个DLL,为Python提供可调用函数
选项有: DLL, MBCS(非unicode) , 静态依赖MFC ,
1.ctyps 库
ctyps库提供了可以将python的值(数字/字符串等)进行打包传递给c++函数.(ctypes是python自带库)
2.实现python调用 c++
2.1 python部分
from ctypes import *
def testString(): # 测试字符串数据 试用ctypes.CDLL加载
dll = CDLL(r'D:\CodeTest\c_py\MFCApplication1\MFCApplication1\Release\MFCLibrary1.dll') # dll的全路径,也可以使用相对路径
string = "汉字"
dll.test2(c_char_p(string.encode('utf-8'))) # 将"汉字"这两个字先转换为utf8 然后使用c_char_p做成缓存传递给test2函数
2.2 c++部分
笔者现有c++程序是用MSBC(多字节)程序.所以从python传来的字符串需要经历两次转换: utf8(python) ->unicode (w_char)->多字节(char) .这样的转换试用ConvertUTF8ToANSI函数
如果c++程序是用unicode的,仅需要 utf8(python) ->unicode (w_char) ,这样的转换需要 UTF82Wide 这个函数
// 被python调用的函数,接受一个字符串.并试用MessageBox把字符串显示出来
extern "C" __declspec(dllexport) int test2(std::string test_string) //test_string 内容为utf8字符串
{
CString str;
ConvertUTF8ToANSI(test_string.c_str(), str); // 将python utf8字符串转 ansi 字符串
AfxMessageBox(str); //当前mfc DLL 是 MBCS(非Unicode)
return 0;
}
//下面介绍两种utf8字符串的转换函数
//1. Utf8 转 ansi
void ConvertUTF8ToANSI(const char* strUTF8, CString &strANSI)
{
int nLen = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCTSTR)strUTF8, -1, NULL, 0); //返回需要的unicode长度
WCHAR * wszANSI = new WCHAR[nLen + 1];
memset(wszANSI, 0, nLen * 2 + 2);
nLen = MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUTF8, -1, wszANSI, nLen); //把utf8转成unicode
nLen = WideCharToMultiByte(CP_ACP, 0, wszANSI, -1, NULL, 0, NULL, NULL); //得到要的ansi长度
char *szANSI = new char[nLen + 1];
memset(szANSI, 0, nLen + 1);
WideCharToMultiByte(CP_ACP, 0, wszANSI, -1, szANSI, nLen, NULL, NULL); //把unicode转成ansi
strANSI = szANSI;
delete wszANSI;
delete szANSI;
}
//2. Utf8 转 wide - 本文不使用,仅作对比讲解
std::wstring UTF82Wide(const std::string& strUTF8)
{
int nWide = ::MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), strUTF8.size(), NULL, 0);
std::unique_ptr<wchar_t[]> buffer(new wchar_t[nWide + 1]);
if (!buffer)
{
return L"";
}
::MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), strUTF8.size(), buffer.get(), nWide);
buffer[nWide] = L'\0';
return buffer.get();
}
2.3 重点讲解 “调用方式” 和 “导出”
1 ctypes.CDLL() 和 ctypes.WinDLL() 加载DLL方式
C++ 的函数现有两种调用约定:__cdecl 和 __stdcall
其对应的python ctypes有两种加载方法,如下
c++ 函数调用方式 | python ctypes加载方式 | |
---|---|---|
__cdecl | ctypes.CDLL(dll名字) | dll = CDLL(r’MFCLibrary1.dll’) |
__stdcall | ctypes.WinDLL(dll名字) |
- c++ 函数的调用声明方式
前两种为 __cdecl, 最后一种为 __stdcall
extern "C" __declspec(dllexport) int test2(std::string test_string); //c++默认 __cdecl py:ctypes.CDLL
extern "C" __declspec(dllexport) int __cdecl test3(std::string test_string); // __cdecl py:ctypes.CDLL
extern "C" __declspec(dllexport) int __stdcall test4(std::string test_string) //__stdcall py:ctypes.WinDLL
标志 | 作用 | |
---|---|---|
extern “C” | 是为了不改名,函数仅加 _ 下划线 | 必须使用,否则编译器会把函数改名. |
__declspec(dllexport) | 是为了导出函数,可以被外部找到 | 否则 dll 外部无法找到 |
__cdecl | vs 默认的调用约定 | 上述 test2 函数没有该标志,其实默认就如此,用cytpes.CDll加载 |
__stdcall | stdcall调用约定 | 需要使用 ctype.WinDll 加载dll 才可以 |
2.4 CDll调用方式
python 代码
from ctypes import *
def testString3():
dll = WinDLL(r'D:\CodeTest\c_py\MFCApplication1\MFCApplication1\Release\MFCLibrary1.dll')
string = "汉字3"
dll.test3(c_char_p(string.encode('utf-8')))
c++ 代码
extern "C" __declspec(dllexport) int __cdecl test3(std::string test_string) //此处也可以不写 __cdecl, c++默认 __cdecl
{
CString str;
ConvertUTF8ToANSI(test_string.c_str(), str);
AfxMessageBox(str);
return 0;
}
2.5 WinDll调用方式
python 代码
from ctypes import *
def testString4():
dll = WinDLL(r'D:\CodeTest\c_py\MFCApplication1\MFCApplication1\Release\MFCLibrary1.dll')
string = "汉字4"
dll.test4(c_char_p(string.encode('utf-8')))
c++ 代码
extern "C" __declspec(dllexport) int __stdcall test4(std::string test_string)
{
CString str;
ConvertUTF8ToANSI(test_string.c_str(), str);
AfxMessageBox(str);
return 0;
}
3 例子: python int float string 数组 传递
def testtest(): # 多种数据测试通过
dll = CDLL(r'D:\CodeTest\c_py\MFCApplication1\MFCApplication1\Release\MFCLibrary1.dll')
char_p_test = bytes("汉字","utf8")#汉字需用采用utf8编码
#int_arr4 = c_int*4
#int_arr = int_arr4()
int_arr = (c_int*4)() # 上面的简化写法
int_arr[0] = 1
int_arr[1] = 3
int_arr[2] = 5
int_arr[3] = 9
char_arr2 = c_char*2
char_arr22 = char_arr2*2
char_arr22a = char_arr22()
char_arr22a[0][0] = b'a'
char_arr22a[0][1]= b'b'
char_arr22a[1][0] = b'c'
char_arr22a[1][1] = b'd'
dll.test(9999,'a',char_p_test,int_arr,char_arr22a)
extern "C" __declspec(dllexport) int test(int int_test, char char_test, char *test_string, int int_arr[4], char char_arr2[2][2])
{
//因为c++程序试用mbcs 所以字符串需要转换为ANSI
CString str;
ConvertUTF8ToANSI(test_string, str); //如果程序本身试用unicode 则需要调用UTF82Wide函数 见2.2
AfxMessageBox(str);
//char_arr2 字符串数组中的每条字符串也需要进行ConvertUTF8ToANSI转换
}
4 例子 C++ 使用 python的返回的字符串 形成json数据格式
# testMod.py
def testJson(n): # 返回json字符串解析通过
data2 = json.dumps({'a': 'testA', 'b': 7}, sort_keys=True, indent=4, separators=(',', ': '))
xx='none'
return data2,xx # 需要返回两个,以保证字符串返回正确
#include "json/json.h"
void myMain()
{
// Python解释器初始化
Py_Initialize();
PyObject* pModule = PyImport_ImportModule("testMod");
if (pModule)
{
// 读取文件中的函数,参数为函数名
PyObject* pFunc = PyObject_GetAttrString(pModule, "testJson");
if (pFunc)
{
// 构建参数,包含1个int
PyObject* pArgs = Py_BuildValue("(i)", 0);
// 调用函数得到返回值
PyObject* res = PyObject_CallObject(pFunc, pArgs);
if (!res)
{
// 打印错误信息
PyErr_Print();
}
else
{
char *p = NULL;
char *none = NULL;
PyArg_ParseTuple(res, "s|s", &p,&none); //接收两个字符串
CString strAnsi;
Json::Value root;
Json::Reader reader;
if (!reader.parse(p, root))
{
Py_Finalize();
return;
}
CString ss = root["a"].asCString();
//可以正确返回 testA
}
}
}
// 关闭Python解释器
Py_Finalize();
}