使用C/C++寫Python模塊

最近看開源項目時學習了一下用C/C++寫python模塊,順便把學習進行一下總結,廢話少說直接開始:

環境:windows、python2.78、VS2010或MingW

1 創建VC工程

(1) 打開VC6.0或VS2008,然後File-->New-->Project-->Win32 DLL Project。建立一個Empty Project,比如testClass,一路確定。

(2) 之後向工程添加python頭文件目錄及庫文件目錄,如頭文件目錄:F:\python278\include,庫文件目錄:F:\python278\libs

(3) 添加一個C++或C源文件,如工程中有用到類,則添加的必須是C++文件,這裏直接添加main.cpp

#include <Python.h>
#include <iostream>
#include <sstream>
#include <structmember.h>
#include <windows.h>

using namespace std;  

typedef struct _CScore
{
    PyObject_HEAD
    char *m_szName;
    float m_dMath;
    float m_dEnglish;
}CScore;

static PyMemberDef CScore_DataMembers[] = {    //類/結構的數據成員的說明.
    {"m_szName",   T_STRING, offsetof(CScore, m_szName),   0, "The Name of instance"},
    {"m_dMath",    T_FLOAT,  offsetof(CScore, m_dMath),    0, "The Math score of instance."},
    {"m_dEnglish", T_FLOAT,  offsetof(CScore, m_dEnglish), 0, "The English score of instance."},
    {NULL, NULL, NULL, 0, NULL}
};

//////////////////////////////////////////////////////////////
// CScore類的所有內置、構造方法.
//
static void CScore_init(CScore* Self, PyObject* pArgs)        //構造方法.
{
    const char* Name = 0;
    if(!PyArg_ParseTuple(pArgs, "sff", &Name, &Self->m_dMath, &Self->m_dEnglish))
    {
        cout<<"Parse the argument FAILED! You should pass correct values!"<<endl;
        return ;
    }

    Self->m_szName = new char[strlen(Name) + 1];
    strcpy(Self->m_szName, Name);
}

static void CScore_Destruct(CScore* Self)                   //析構方法.
{
    if(Self->m_szName){
        delete [] Self->m_szName;                            //先釋放其字符指針對象.
    }
    OutputDebugString(TEXT("destroy!!!"));

    //如果還有PyObject*成員的話,要一併釋放之.
    //如:Py_XDECREF(Self->Member);
    Py_TYPE(Self)->tp_free((PyObject*)Self);                //釋放對象/實例.
}

static PyObject* CScore_Str(CScore* Self)                    //調用str/print時自動調用此函數.
{
    ostringstream OStr;
    OStr<<"Name    : "<<Self->m_szName<<endl
        <<"Math    : "<<Self->m_dMath<<endl
        <<"English : "<<Self->m_dEnglish<<endl;
    string Str = OStr.str();
    return Py_BuildValue("s", Str.c_str());
}

static PyObject* CScore_Repr(CScore* Self)            //調用repr內置函數時自動調用.
{
    return CScore_Str(Self);
}


////////////////////////////////////////////////////////////
// CScore類的所有Get方法.
//
static PyObject* CScore_GetName(CScore* Self)
{
    return Py_BuildValue("s", Self->m_szName);
}

static PyObject* CScore_GetMath(CScore* Self)
{
    return Py_BuildValue("f", Self->m_dMath);
}

static PyObject* CScore_GetEnglish(CScore* Self)
{
    return Py_BuildValue("f", Self->m_dEnglish);
}

////////////////////////////////////////////////////////////
// CScore類的所有Set方法.
//
static PyObject* CScore_SetMath(CScore* Self, PyObject* Argvs)
{
    Py_INCREF(Py_None);
    if(!PyArg_ParseTuple(Argvs, "f", &Self->m_dMath))
    {
        cout<<"Parse the argument FAILED! You should pass correct values!"<<endl;
        return Py_None;
    }

    return Py_None;
}

static PyObject* CScore_SetEnglish(CScore* Self, PyObject* Argvs)
{
    Py_INCREF(Py_None);
    if(!PyArg_ParseTuple(Argvs, "f", &Self->m_dEnglish))
    {
        cout<<"Parse the argument FAILED! You should pass correct values!"<<endl;
        return Py_None;
    }

    return Py_None;
}

static PyObject* CScore_PrintInfo(CScore* Self)
{
    cout<<"The scores as follows:"<<endl
        <<"=============================="<<endl
        <<"Name    : "<<Self->m_szName<<endl
        <<"Math    : "<<Self->m_dMath<<endl
        <<"English : "<<Self->m_dEnglish<<endl
        <<"=============================="<<endl;

    Py_XINCREF(Py_None);
    return Py_None;
}

static PyMethodDef CScore_MethodMembers[] =      //類的所有成員函數結構列表.
{
    {"GetName",    (PyCFunction)CScore_GetName, METH_NOARGS,     "Get the name of instance."},
    {"GetMath",    (PyCFunction)CScore_GetMath, METH_NOARGS,     "Get the math score of instance."},
    {"GetEnglish", (PyCFunction)CScore_GetEnglish, METH_NOARGS,  "Get the english score of isntance."},

    {"SetMath",    (PyCFunction)CScore_SetMath, METH_VARARGS,    "Set the math score of instance."},
    {"SetEnglish", (PyCFunction)CScore_SetEnglish, METH_VARARGS, "Set the english of instance."},

    {"PrintInfo",  (PyCFunction)CScore_PrintInfo, METH_NOARGS,   "Print all information of instance."},

    {NULL, NULL, NULL, NULL}
};

////////////////////////////////////////////////////////////
// 類/結構的所有成員、內置屬性的說明信息.
//
static PyTypeObject CScore_ClassInfo =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "Module.MyCppClass",            //可以通過__class__獲得這個字符串. CPP可以用類.__name__獲取.
    sizeof(CScore),                 //類/結構的長度.調用PyObject_New時需要知道其大小.
    0,
    (destructor)CScore_Destruct,    //類的析構函數.
    0,
    0,
    0,
    0,
    (reprfunc)CScore_Repr,          //repr 內置函數調用。
    0,
    0,
    0,
    0,
    0,
    (reprfunc)CScore_Str,          //Str/print內置函數調用.
    0,
    0,
    0,
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,                 //如果沒有提供方法的話,爲Py_TPFLAGS_DEFAULE
    "MyCppClass Objects---Extensioned by C++!",                   //__doc__,類/結構的DocString.
    0,
    0,
    0,
    0,
    0,
    0,
    CScore_MethodMembers,        //類的所有方法集合.
    CScore_DataMembers,          //類的所有數據成員集合.
    0,
    0,
    0,
    0,
    0,
    0,
    (initproc)CScore_init,      //類的構造函數.
    0,
};

////////////////////////////////////////////////////////////
// 此模塊的說明信息. 由於我用的python2所以直接把該部分進行註釋
//
/*static PyModuleDef ModuleInfo =
{
    PyModuleDef_HEAD_INIT,
    "My C++ Class Module",               //模塊的內置名--__name__.
    "This Module Created By C++--extension a class to Python!",                 //模塊的DocString.__doc__
    -1,
    NULL, NULL, NULL, NULL, NULL
};*/

int add(int x, int y)
{
    return x+y;
}

static PyObject* W_add(PyObject* self, PyObject* args) {
    int x;
    int y;
    if(!PyArg_ParseTuple(args, "i|i", &x, &y)) {
        return NULL;
    } else {
        return Py_BuildValue("i", add(x, y));
    }
}

static PyMethodDef module_methods[] = {{"add", W_add, METH_VARARGS, "a function from C"},{NULL, NULL, 0, NULL}};

PyMODINIT_FUNC inittestClass(void)       //模塊外部名稱爲--CppClass
{
    PyObject* pReturn = 0;
    CScore_ClassInfo.tp_new = PyType_GenericNew;       //此類的new內置函數—建立對象.

    /////////////////////////////////////////////////////
    // 完成對象類型的初始化—包括添加其繼承特性等等。
    // 如果成功,則返回0,否則返回-1並拋出異常.
    if(PyType_Ready(&CScore_ClassInfo) < 0)
        return;

    pReturn = Py_InitModule3("testClass", module_methods, "Example module that creates an extension type.");
    if(pReturn == NULL)
        return;
    Py_INCREF(&CScore_ClassInfo);
    PyModule_AddObject(pReturn, "CScore", (PyObject*)&CScore_ClassInfo); //將這個類加入到模塊的Dictionary中.
    return;
}
main.cpp

之後修改VC工程導出文件後綴名爲pyd

 

2 使用testClass模塊

編譯工程生成testClass.pyd模塊文件,進入到導出文件目錄,並啓動python

導入testClass模塊並查看其詳細信息

模塊詳細使用

其他編譯方式

下面介紹另一種用Python Script來生成.pyd文件的方法,新建一個Python腳本——CreatePyd.py,其內容如下:

from distutils.core import setup, Extension
ModuleInfo = Extension("testClass", sources = [r"main.cpp"])
setup(name             = "testClass",
      version          = "1.0",
      description      = "This module created by C++ weiwei.Zhao",
      author           = 'weiwei.zhao',
      author_email     = '[email protected]',
      license          = "You can copy this program to anywhere.",
      url              = "http://zhaoweiwei.top",
      long_description = '''This is really just a demo!''',
      platforms        = "Windows",
      ext_modules      = [ModuleInfo]
     )
CreatePyd.py

內容解釋:先導入所需的Python模塊,然後用Extension函數關聯一個Cpp源文件一個要生成的模塊名——注意:沒有.pyd後綴。然後調用setup函數生成一個名字name爲testClass的模塊,版本version爲1.0,描述description,作者信息author,作者郵箱author_email,還其平臺platforms等等有用的信息!

最後調用:python CreateDLL.py build

經過編譯後,就會新生成一個build目錄,在build/lib.win32-2.7下你可以找到testClass.pyd文件。

需要注意的是編譯腳本默認使用VS編譯器,所以電腦上要安裝VS,如果電腦上沒有安裝VS而是有MingW環境,則類似的可以使用MingW環境中gcc進行編譯:

注意:爲了使相關程序都能順利找見,上圖是在MingW的Shell命令行,而不是普通的windows Command命令行。

4 模塊部署安裝

一般來說,setup.py參數說明

#python setup.py build      # 編譯

#python setup.py install     # 安裝

#python setup.py sdist      # 生成壓縮包(zip/tar.gz)

#python setup.py bdist_wininst   #生成NT平臺安裝包(.exe)

#python setup.py bdist_rpm  #生成rpm包

或者直接"bdist 包格式",格式如下:

#python setup.py bdist --help-formats

   --formats=rpm       RPM distribution

   --formats=gztar     gzip'ed tar file

   --formats=bztar     bzip2'ed tar file

   --formats=ztar     compressed tar file

   --formats=tar       tar file

   --formats=wininst   Windows executable installer

   --formats=zip       ZIP file

下圖說明了windows安裝程序生成過程,在dist目錄下有windows的安裝程序testClass-1.0.win32-py2.7.exe

需要說明的是如果生成的testClass.pyd依賴於其他庫文件,如在我的環境下用MingW生成的testClass.pyd要依賴libgcc_s_dw2-1.dll和libstdc++-6.dll兩個文件,生成安裝包時需把這兩個文件和testClass.pyd放到一起,如放到build/lib.win32-2.7目錄下,之後生成的安裝文件testClass-1.0.win32-py2.7.exe會包含着兩個文件,如果不這樣做在導入testClass時會導致導入失敗,提示類似:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: DLL load failed: 找不到指定的程序。

結束語

以上的總結參考了部分網友的博文,主要如下,一併感謝:

http://blog.csdn.net/arnozhang12/article/details/5409155

http://blog.csdn.net/carolzhang8406/article/details/6925745

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章