c++ python 交互之 swig
工作中准備用python 作為腳本語言來實現一些工作於是就研究 可以和c++ 交互的腳本語言
本來一開始用的lua 但是 lua本身API接口很少 要么自己需要重復封裝代碼 要么c++ 導出
太多地方需要重復造輪子。之所以選擇python 是因為python的包多 直接 import 即可食用
好了 廢話不多說直接進入正題
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 生成的代碼與商業和非商業項目兼容。
swig 項目下載
http://www.swig.org/download.html
下載以后你將得到一個壓縮包 swig-4.0.1.tar.gz
我下載的是4.0版本的
swig 文檔介紹的很詳細了
大部分可以在文檔中找到答案
下載后解壓壓縮包得到如下結構的目錄
其中 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 動態庫項目
開始寫你的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 目錄路徑
以及需要在 我們的項目 右鍵屬性 -> 鏈接器 -> 附加庫目錄 中添加 python 的lib庫目錄路徑
還需要在 屬性 -> 鏈接器 -> 輸出文件 中修改 輸出文件名 格式如下_模塊名.pyd
下面我們還需要對我們的 xxxxx.i
的接口文件設置編譯生成命令
在你的Visual Studio 項目中 選中 xxxxx.i
文件右鍵屬性 -> 配置屬性 -> 常規 -> 項類型 選中 自定義生成工具 然后確定 在自定義生成工具 -> 常規 -> 命令行 添加 swig.exe -c++ -python "%(FullPath)"
輸出 中 添加 $(InputName)_wrap.cxx
然后 選中 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")