c++ python 交互之 swig

c++ python 交互之 swig

工做中准备用python 做为脚本语言来实现一些工做因而就研究 能够和c++ 交互的脚本语言
原本一开始用的lua 可是 lua自己API接口不多 要么本身须要重复封装代码 要么c++ 导出
太多地方须要重复造轮子。之因此选择python 是由于python的包多 直接 import 便可食用
好了 废话很少说直接进入正题html

swig 是什么?

SWIG 是一种软件开发工具,将用 C 编写的程序与各类高级编程语言C++。SWIG 用于不一样类型的目标语言,包括常见的脚本语言,如 Javascript、Perl、PHP、Python、Tcl 和 Ruby。支持的语言列表还包括非脚本语言,如 C#、D、Go 语言、Java(包括 Android、Lua、OCaml、Octave、Scilab 和 R)。还支持几个解释和编译的方案实现(吉勒、MzScheme/Racket)。SWIG 最经常使用于建立高级解释或编译编程环境、用户界面,并做为测试和原型设计 C/C++软件的工具。SWIG 一般用于解析 C/C++接口,并生成上述目标语言调用 C/C++代码所需的"粘合代码"。SWIG 还能够以 XML 的形式导出其解析树。SWIG 是自由软件,SWIG 生成的代码与商业和非商业项目兼容。python

swig 项目下载

http://www.swig.org/download.htmlc++

下载之后你将获得一个压缩包 swig-4.0.1.tar.gz 我下载的是4.0版本的编程

swig 文档介绍的很详细了
大部分能够在文档中找到答案
下载后解压压缩包获得以下结构的目录
3.pngwindows

其中 swig.exe 就是咱们须要用到的
首先把这个目录添加的系统环境变量 中 的path中去
而后能够进入 目录Examples\
下能够看到 不少语言的使用例子
这里咱们进入python 目录能够看到各类c++ 生成python的例子
进入python 目录下后 进入class 目录能够看到 一个很简单 的 c++ 类导出成python 模块的例子编程语言

用 Visual Studio 打开 example.dsp 便可 这里有用的 就两种文件
一种是 你的c++ 源文件 头文件 另一种就是 xxxxx.i的接口文件 这个是 给swig 使用的函数

swig简单的使用

下面我就简单的用c++给 python 封装一个文件目录监控的例子 来说吧
首先打开 你的 Visual Studio 新建一个项目 选择win32项目 建立一个 win32 动态库项目
4.png
开始写你的c++ 代码
个人代码以下 FileDirectoryMonitor.h工具

#ifndef __FILEDIRECTORYMONITOR_H__
#define __FILEDIRECTORYMONITOR_H__
#include <string>
#include <memory>
#include <thread>
#include <windows.h>
class POV :public OVERLAPPED
{
public:
    POV()
    {
        Internal = InternalHigh = 0;
        Offset = OffsetHigh = 0;
        hEvent = NULL;
        m_bufferSize = 1024;
        m_pData = new BYTE[m_bufferSize];
        memset(m_pData, 0, m_bufferSize);
    }
    ~POV() {
        if (m_pData) {
            delete[] m_pData;
            m_pData = nullptr;
        }
    }
public:
    LPVOID m_pData;
    DWORD m_bufferSize;
};
class Callback
{
public:
    Callback() {};
    virtual ~Callback() {};
    virtual void run(const char* filename) {};
};
class FileDirectoryMonitor
{
public:
    FileDirectoryMonitor();
    virtual ~FileDirectoryMonitor();
    bool SetMonitorPath(const std::string& strPath, Callback* callBack);
    void start();
private:
    std::unique_ptr<std::thread> mMonitorThread;
    HANDLE hFile;
    HANDLE hIoCmp;
    POV* povData;
    DWORD mCompletionKey;
    Callback* mCallBack;
    void MonitorThreadProc();
    bool mRuning;
};
#endif  //__FILEDIRECTORYMONITOR_H__

cpp 代码文件 FileDirectoryMonitor.cpp开发工具

#include "FileDirectoryMonitor.h"
#include <locale>
#include <codecvt>
FileDirectoryMonitor::FileDirectoryMonitor()
    :hFile(INVALID_HANDLE_VALUE)
    ,hIoCmp(INVALID_HANDLE_VALUE)
    ,mCompletionKey(1)
    ,mCallBack(nullptr)
    ,mRuning(true)
    ,povData(nullptr)
{
}

FileDirectoryMonitor::~FileDirectoryMonitor()
{
    if (mMonitorThread)
    {
        if (mMonitorThread->joinable())
        {
            mRuning = false;
            mMonitorThread->join();
            mMonitorThread.reset();
            mMonitorThread = nullptr;
        }
    }
    if (povData)
    {
        delete povData;
        povData = nullptr;
    }
}

bool FileDirectoryMonitor::SetMonitorPath(const std::string& strPath, Callback* callBack)
{
    bool bResult = false;
    hFile  = CreateFileA(strPath.c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
    if (INVALID_HANDLE_VALUE == hFile) {
        return bResult;
    }
    if (!callBack)
    {
        return bResult;
    }
    if (mCallBack)
    {
        delete mCallBack;
        mCallBack = nullptr;
    }
    mCallBack = callBack;
    hIoCmp = CreateIoCompletionPort(hFile, NULL, mCompletionKey, NULL);
    if (!hIoCmp)
    {
        CloseHandle(hFile);
        return bResult;
    }
    povData = new POV();
    DWORD dwCbyte = 0;
    BOOL isSucceed = ReadDirectoryChangesW(hFile, povData->m_pData
        , povData->m_bufferSize
        , FALSE
        , FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE
        , &dwCbyte, (LPOVERLAPPED)povData
        , NULL);
    if (isSucceed)
    {
        bResult = true;
    }
    return bResult;
}

void FileDirectoryMonitor::start()
{
    mMonitorThread = std::make_unique<std::thread>(&FileDirectoryMonitor::MonitorThreadProc, this);
    mMonitorThread->join();
}

void FileDirectoryMonitor::MonitorThreadProc()
{
    POV* OverApp = nullptr;
    DWORD lpNumberOfBytesTransferred = NULL;
    ULONG_PTR lpCompletionKey = NULL;
    DWORD dwCbyte = 0;
    while (mRuning)
    {
        BOOL isOK = GetQueuedCompletionStatus(hIoCmp, &lpNumberOfBytesTransferred, &lpCompletionKey, (LPOVERLAPPED*)&OverApp, INFINITE);
        if (isOK)
        {
            //printf("1111\n");
            if (!OverApp) {
                break;
            }
            if (OverApp->m_pData)
            {
                //printf("2222\n");
                DWORD dwNextOffset = 0;
                auto pnotify = (FILE_NOTIFY_INFORMATION*)(OverApp->m_pData);
                do
                {
                    //printf("3333\n");
                    dwNextOffset = pnotify->NextEntryOffset;
                    if (pnotify->FileNameLength && pnotify->Action == FILE_ACTION_ADDED)
                    {
                        std::wstring filename;
                        filename.assign(pnotify->FileName, pnotify->FileNameLength);
                        //printf("4444\n");
                        if (mCallBack)
                        {
                            //printf("5555\n");
                            std::string strFileName = "zh-CN";
                            typedef std::codecvt_byname<wchar_t, char, std::mbstate_t> F;
                            static std::wstring_convert<F> strCnv(new F(strFileName));
                            strFileName = strCnv.to_bytes(filename);
                            //printf("6666 \t %s\n",strFileName.c_str());
                            mCallBack->run(strFileName.c_str());
                            //printf("7777\n");
                        }
                    }
                    if (dwNextOffset != 0)
                    {
                        pnotify = (FILE_NOTIFY_INFORMATION*)((BYTE*)pnotify + dwNextOffset);
                    }
                } while (dwNextOffset != 0);
            }
        }
        memset(OverApp->m_pData, 0, OverApp->m_bufferSize);
        if (!ReadDirectoryChangesW(hFile, OverApp->m_pData
            , OverApp->m_bufferSize
            , FALSE
            , FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE
            , &dwCbyte, (LPOVERLAPPED)OverApp
            , NULL)) 
        {
            break;
        }
    }
}

在 Visual Studio 新建一个 xxx.i 的文件
这个是这个项目的 Directory.i 文件测试

%module(directors="1") Directory
%include "std_string.i"
%{
#include "FileDirectoryMonitor.h"
%}
%feature("director") Callback;
%include "FileDirectoryMonitor.h"

下面咱们来简单介绍下 %module 模块名 这个模块名是 在python中你 import 模块名 的名字
至于为何我这个 里面为何会在 %module后面 带了括号 这个是由于 %module 支持带一些配置信息 我这个带的配置信息就是 导出回调函数模块的 具体能够参考 swig 文档中的 回调函数讲解
%{

include "FileDirectoryMonitor.h" // 这里是你须要引用的 头文件

%}

至于%include "FileDirectoryMonitor.h" //是给 swig 生成python 模块的 引入的 能够参考文档

%feature("director") Callback; // 导出一个回调函数类 可让python 的类继承 这样你就可在python 中 继承这个 类 来重载实现 你的 回调函数了

细节讲解

想生成这个项目你必须安装了 python 环境 须要在 系统环境变量中 添加 PATHON_INCLUDE python 的include 目录路径 以及PYTHON_LIB python 的lib 路径

上面的作好了 须要在 咱们的项目 右键属性 -> C/C++ -> 常规 -> 附加包含目录 中添加 的include 目录路径
5.png

以及须要在 咱们的项目 右键属性 -> 连接器 -> 附加库目录 中添加 python 的lib库目录路径
还须要在 属性 -> 连接器 -> 输出文件 中修改 输出文件名 格式以下_模块名.pyd
6.png

7.png

下面咱们还须要对咱们的 xxxxx.i 的接口文件设置编译生成命令

在你的Visual Studio 项目中 选中 xxxxx.i 文件右键属性 -> 配置属性 -> 常规 -> 项类型 选中 自定义生成工具 而后肯定 在自定义生成工具 -> 常规 -> 命令行 添加 swig.exe -c++ -python "%(FullPath)"
输出 中 添加 $(InputName)_wrap.cxx
9.png

8.png
而后 选中 xxxxx.i 右键编译 编译后没错误 就把 `xxxxxx_wrap.cxx`` 文件添加到项目中

而后去生成就能够看到 在你项目当前目录下 看到 _模块名.pyd 以及 模块名.py 这个时候就能够在 当前目录下 写一个 测试的 python脚本测试你的 代码了

个人测试代码 run.py

import Directory
class pycallback(Directory.Callback):
    def __init(self):
        print("1111")
    def run(self,filename):
        #print(1111,type(filename))
        print(filename)

Monitor = Directory.FileDirectoryMonitor()
back = pycallback()
bresult = Monitor.SetMonitorPath("d:\\pdf\\",back)
print(bresult)
Monitor.start()
print("end")