VS2017开发Python C++扩展库并调试

1. Prerequisites

  1. VS2017或更高版本

  2. 安装好Python开发环境依赖

    • 打开Visual Studio install > 修改 > 选择Python开发,按图进行配置。

      image-20201203094106327

  3. 配置环境变量:此电脑 > 属性 > 高级系统设置 > 环境变量

    • 选择用户环境变量中的Path进行编辑,将上一步安装好的Python环境添加到用户环境变量的Path中。

      image-20201203095305937

  4. 选择新建,创建PYTHONHOME环境变量。

    image-20201203105645880

2. 创建Python工程

  1. 文件 > 新建 > 项目 > 已安装 > 其他语言 > Python > Python应用程序,设置好名称后选择确定,完成Python工程创建。

    image-20201203101611465

  2. 检查解决方案窗口中Python工程的Python环境是否为之前通过Visual Studio Installer安装的版本,如果不是,则右键选择Python环境 > 查看所有Python环境 > 选择合适的Python环境,然后点击将此作为新项目的默认环境

    image-20201203102127938

  3. 在Python脚本下添加以下代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    from itertools import islice
    from random import random
    from time import perf_counter
    COUNT = 500000 # Change this value depending on the speed of your computer
    DATA = list(islice(iter(lambda: (random() - 0.5) * 3.0, None), COUNT))
    e = 2.7182818284590452353602874713527
    def sinh(x):
    return (1 - (e ** (-2 * x))) / (2 * (e ** -x))
    def cosh(x):
    return (1 + (e ** (-2 * x))) / (2 * (e ** -x))
    def tanh(x):
    tanh_x = sinh(x) / cosh(x)
    return tanh_x
    def test(fn, name):
    start = perf_counter()
    result = fn(DATA)
    duration = perf_counter() - start
    print('{} took {:.3f} seconds\n\n'.format(name, duration))
    for d in result:
    assert -1 <= d <= 1, " incorrect values"
    if __name__ == "__main__":
    print('Running benchmarks with COUNT = {}'.format(COUNT))
    test(lambda d: [tanh(x) for x in d], '[tanh(x) for x in d] (Python implementation)')
  4. 选择调试 > 开始执行(不调试),运行Python脚本,正常情况下会输出如下结果。

    image-20201203102524519

3. 创建Python扩展库C++工程(CPython)

  1. 右键解决方案 > 添加 > 新建项目 > 已安装 > Visual C++ > Python > Python扩展模块,设置好名称后选择确定,完成Python工程创建。

    image-20201203104412930

  2. 右键C++工程(本文为superfastcode) > 属性,按照下表进行属性配置

项目 属性
配置 所有配置
平添 x64
常规 目标文件名 $(ProjectName)
C/C++ > 预处理器 预处理器定义 添加Py_LIMITED_API,只有使用CPython才添加,使用PyBind11时,应去掉此选项。
  1. superfastcode.c中添加如下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    #include <Python.h>
    #include <Windows.h>
    #include <math.h>
    const double e = 2.7182818284590452353602874713527;
    double sinh_impl(double x) {
    return (1 - pow(e, (-2 * x))) / (2 * pow(e, -x));
    }
    double cosh_impl(double x) {
    return (1 + pow(e, (-2 * x))) / (2 * pow(e, -x));
    }
    /**
    * @brief 计算双曲正切值
    */
    // tanh_impl(x)说明文档
    PyDoc_STRVAR(superfastcode_tanh_impl_doc, "tanh_impl(x) tanh function");
    // tanh_impl(x)方法实现
    PyObject* tanh_impl(PyObject *self, PyObject* args) {
    double x = PyFloat_AsDouble(args);
    double tanh_x = sinh_impl(x) / cosh_impl(x);
    return PyFloat_FromDouble(tanh_x);
    }
    /**
    * List of functions to add to superfastcode in exec_superfastcode().
    */
    static PyMethodDef superfastcode_functions[] = {
    { "fast_tanh", (PyCFunction)tanh_impl, METH_O, superfastcode_tanh_impl_doc},
    { NULL, NULL, 0, NULL } /* marks end of array */
    };
    /*
    * Initialize superfastcode. May be called multiple times, so avoid
    * using static state.
    */
    int exec_superfastcode(PyObject *module) {
    PyModule_AddFunctions(module, superfastcode_functions);
    PyModule_AddStringConstant(module, "__author__", "AILEE");
    PyModule_AddStringConstant(module, "__version__", "1.0.0");
    PyModule_AddIntConstant(module, "year", 2020);
    return 0; /* success */
    }
    /*
    * Documentation for superfastcode.
    */
    PyDoc_STRVAR(superfastcode_doc, "The superfastcode module");
    static PyModuleDef_Slot superfastcode_slots[] = {
    { Py_mod_exec, exec_superfastcode },
    { 0, NULL }
    };
    /**
    * @brief 模块定义
    */
    static PyModuleDef superfastcode_def = {
    PyModuleDef_HEAD_INIT,
    "superfastcode", /* 模块名 */
    superfastcode_doc, /* 模块说明 通过help */
    0, /* m_size: 模块空间,子解释器用,-1为不使用*/
    NULL, /* m_methods */
    superfastcode_slots,
    NULL, /* m_traverse */
    NULL, /* m_clear */
    NULL, /* m_free */
    };
    /**
    * @brief 扩展库入口函数,PyInit_+模块名,固定格式。
    */
    PyMODINIT_FUNC PyInit_superfastcode() {
    // 模块创建函数
    // 当使用该方式创建模块时,应使用PyModuleDef_Slot,并将m_size设置为非负值,
    // 启用子解释器。 否则报错。
    // Traceback (most recent call last):
    // File "E:\Code-of-C++\Python_Ext\test_python\test_python\test_python.py", line 5, in <module>
    // from superfastcode import fast_tanh
    // SystemError: module superfastcode: m_size may not be negative for multi-phase initialization
    //
    return PyModuleDef_Init(&superfastcode_def);
    // 当使用该方式创建模块时,应将模块定义内容修改如下,
    // static PyModuleDef superfastcode_def = {
    // PyModuleDef_HEAD_INIT,
    // "superfastcode",
    // superfastcode_doc,
    // -1, /* m_size 模块空间,子解释器用,-1不使用 */
    // superfastcode_functions, /* m_methods 模块函数*/
    // NULL, /* m_slots */
    // NULL, /* m_traverse */
    // NULL, /* m_clear */
    // NULL, /* m_free */
    // };
    // 否则报错:
    // Traceback (most recent call last):
    // File "E:\Code-of-C++\Python_Ext\test_python\test_python\test_python.py", line 5, in <module>
    // from superfastcode import fast_tanh
    // SystemError: module superfastcode: PyModule_Create is incompatible with m_slots
    // return PyModule_Create(&superfastcode_def);
    }
  2. 配置成Release模式,选择生成 > 生成解决方案(快捷键:F7)

  3. Python调用刚生成的模块,进行测试。

    • 右键解决方案窗口中Python工程的引用 > 添加引用,勾选superfastcode

      image-20201203163152276

    • test_python.py脚本里添加如下程序,调用superfastcode包。

      1
      2
      from superfastcode import fast_tanh
      test(lambda d: [fast_tanh(x) for x in d], '[fast_tanh(x) for x in d] (CPython C++ extension)')
    • 单击调试 > 开始执行(不调试),运行Python代码。出现如下结果即为成功。

      image-20201203163835534

4. 联合调试C++代码

  1. 解决方案窗口中,右键Python工程(test_python) > 属性 > 调试,勾选启用本机代码调试,Ctrl+S保存。

    image-20201203165052440

  2. 配置为Debug模式,在C++代码中设置好断点,选择调试 > 开始调试,(快捷键:F5)如图所示。

    image-20201203165635622

5. 发布安装扩展包

  1. 解决方案窗口中,右键C++工程(superfastcode) > 添加 > 新建项 > 已安装 > Visual C++ > 实用工具 > 文本文件,将名称修改为setup.py,选择添加完成。

    image-20201203170912923

  2. setup.py中添加如下代码。

    1
    2
    3
    4
    5
    6
    7
    8
    from distutils.core import setup, Extension, DEBUG
    sfc_module = Extension('superfastcode', sources = ['superfastcode.c'])
    setup(name = 'superfastcode', version = '1.0',
    description = 'Python Package with superfastcode C++ extension',
    ext_modules = [sfc_module]
    )
  3. 打开PowerShell(cmd也可以,PowerShell操作更方便),切换到superfastcode目录,本文如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    PS E:\Code-of-C++\Python_Ext\test_python\superfastcode> ls
    目录: E:\Code-of-C++\Python_Ext\test_python\superfastcode
    Mode LastWriteTime Length Name
    ---- ------------- ------ ----
    d----- 2020/12/3 11:03 x64
    -a---- 2020/12/3 17:10 278 setup.py
    -a---- 2020/12/3 16:25 1981 superfastcode.c
    -a---- 2020/12/3 15:42 13609 superfastcode.vcxproj
    -a---- 2020/12/3 15:20 963 superfastcode.vcxproj.filters
    -a---- 2020/12/3 10:44 165 superfastcode.vcxproj.user
  4. 执行python.exe setup.py install命令,无报错即为成功,如下为结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    PS E:\Code-of-C++\Python_Ext\test_python\superfastcode> python.exe .\setup.py install
    running install
    running build
    running build_ext
    building 'superfastcode' extension
    d:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\bin\HostX86\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD "-ID:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\include" "-ID:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\include" "-Id:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\ATLMFC\include" "-Id:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\include" "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" /Tcsuperfastcode.c /Fobuild\temp.win-amd64-3.6\Release\superfastcode.obj
    superfastcode.c
    creating E:\Code-of-C++\Python_Ext\test_python\superfastcode\build\lib.win-amd64-3.6
    d:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\bin\HostX86\x64\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO "/LIBPATH:D:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\libs" "/LIBPATH:D:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\PCbuild\amd64" "/LIBPATH:d:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\ATLMFC\lib\x64" "/LIBPATH:d:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\lib\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\lib\um\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\ucrt\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\um\x64" /EXPORT:PyInit_superfastcode build\temp.win-amd64-3.6\Release\superfastcode.obj /OUT:build\lib.win-amd64-3.6\superfastcode.cp36-win_amd64.pyd /IMPLIB:build\temp.win-amd64-3.6\Release\superfastcode.cp36-win_amd64.lib
    正在创建库 build\temp.win-amd64-3.6\Release\superfastcode.cp36-win_amd64.lib 和对象 build\temp.win-amd64-3.6\Release\superfastcode.cp36-win_amd64.exp
    正在生成代码
    已完成代码的生成
    running install_lib
    copying build\lib.win-amd64-3.6\superfastcode.cp36-win_amd64.pyd -> D:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\Lib\site-packages
    running install_egg_info
    Removing D:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\Lib\site-packages\superfastcode-1.0-py3.6.egg-info
    Writing D:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\Lib\site-packages\superfastcode-1.0-py3.6.egg-info
  5. 验证安装。输入python.exe,运行python环境,输入如下代码:

    1
    2
    3
    import superfastcode
    superfastcode.fast_tanh(0.5)
    exit()

    得到如下结果,即为成功。

    1
    2
    3
    4
    5
    6
    7
    PS E:\Code-of-C++\Python_Ext\test_python\superfastcode> python.exe
    Python 3.6.6 (v3.6.6:4cf1f54eb7, Jun 27 2018, 03:37:03) [MSC v.1900 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import superfastcode
    >>> superfastcode.fast_tanh(0.5)
    0.4621171572600098
    >>> exit()

6. PyBind11

参考微软官方文档

参考文献

-------------本文结束感谢您的阅读-------------
分享