精华内容
下载资源
问答
  • Qt中关于JavaScript/QML和C++混合编程

    千次阅读 2014-08-03 14:26:44
    QML中关于JavaScript和C++混合调用的个人整体理解小结 声明:本文很多内容都是个人学习和尝试而得出的结果,难免会有疏漏,如有不正确或不严谨之处,还请指正。 我使用的环境是: Qt 5.2.1 (Clang 5.0 ...

    Qt中关于JavaScript/QML和C++混合编程


    声明:本文很多内容都是个人学习和尝试而得出的结果,难免会有疏漏和粗枝大叶,如有不正确或不严谨之处,还请指正。


    感谢soloist的悉心教导和耐心讲解,以及在工作和学习中对我鼓励与帮助。


    我使用的环境是: Qt 5.2.1 (Clang 5.0 (Apple), 64 bit),Qt Creator 3.1.1 (opensource)。


    QML(Qt Markup Language):http://qt-project.org/doc/qt-5/qmlapplications.html

    The Meta-Object System : http://qt-project.org/doc/qt-5/metaobjects.html

    ABI(Application binary interface):http://en.wikipedia.org/wiki/Application_binary_interface

    FFI(Foreign function interface):http://en.wikipedia.org/wiki/Foreign_function_call

     vc如何返回函数结果及压栈参数:http://blog.csdn.net/soloist/article/details/1267147


    1、前传

     “不同语言之间进行混合编程是计算机领域一个火热的话题,一些软件技术和工具,如SWIG (Simplified Wrapper and Interface Generator)、COM(Component Object Model) 的目的就是为了解决不同语言之间的互通性。”

            “对于不同语言之间的互操作,主要解决三大问题:

    (1)数据类型的识别(包括数据的存储格式,访问方式)。

    (2)数据类型的转换(类型系统如何互通)。

    (3)对象生命周期管理。”

            “说到不同编程语言的混合调用,就涉及到两种语言之间数据类型的互通,方法的调用,本质上就是要求两种语言能够互相通信,打个比方,这就好比两个国家之间的语言进行沟通,例如阳阳会讲日语,龙龙会讲法语,那么他们怎么才能进行沟通呢?一种方式是他们采用一种双方都能听懂的英语来沟通,另一种方式就是阳阳学会了说法语,龙龙学会了说日语。”

            “回到混合编程的话题,与此类似,一种方式就是两种语言采用一种都支持的中间语言(如xml)进行通信,这种方式的优点是语言间的耦合性较低,不需要对另一门语言的实现细节有了解,缺点是通过中间语言进行通信所付出的代价;另一种方式就是充分了解另一门语言,包括数据类型,数据结构的组织,方法的调用形式等,这种方式的优点是互操作的性能高,缺点是需要底层实现繁琐。”

            “说到为什么要进行js与c++的混合开发,原因是他们各有自己的优缺点。js的对象模型比较灵活,支持对象属性的动态创建和删除,支持闭包,但是性能不如c++高,而且局限于浏览器,否则需要有js引擎的支持。c++的优点就是性能比较高,但是是一门静态类型语言,灵活性不足。结合二者的优点,我们可以将逻辑代码部分采用js来实现,大量计算部分采用c++来实现。”



    2、Qt的对象模型的介绍

            C++的静态特性不够灵活,为了满足GUI编程对运行时高效性和灵活性的要求,Qt对标准C++对象模型进行了扩展(http://qt-project.org/doc/qt-5/object.html),这些扩展都基于了Qt中两个十分重要的两个概念:

    (1)QObject

    (2)Meta-Object


            QObject是所有Qt对象的基类,也是Qt对象模型的核心,扩展的大部分能力都是基于QObject实现。Qt 元对象系统(Meta-Object System)负责Qt中基于signals和slots的对象通信机制,运行期类型信息以及Qt的属性(Q_PROPERTY)系统,对于QObject以及它的子类所创建的对象,都会为其创建一个QMetaObject实例,该实例中存储了所有的关于该对象的元信息(属性的类型,方法的签名信息等),这些都由Qt所实现的Meta-Object Compiler(moc)来提供。


            moc会为每一个包含Q_OBJECT宏的类创建一个新的C++源文件,以“moc_”打头,该文件中包含类的元信息,该文件一般会被单独编译并与原类文件链接使用,也可以被#include到原类文件中使用。



    关于moc,Qt官方文档中的介绍如下:

    The meta-object system is based on three things:

    (1)The QObject class provides a base class for objects that can take advantage of the meta-object system.

    (2)The Q_OBJECT macro inside the private section of the class declaration is used to enable meta-object features, such as dynamic properties, signals, and slots.

    (3)The Meta-Object Compiler (moc) supplies each QObject subclass with the necessary code to implement meta-object features.


            了解了这些,我们也就知道了Qt中Javascript和C++的混合调用采用的是前面所提到的第二种方式进行通信,而不是通过中间语言。


    3、JS调用C++


            我想这就是FFI中所说的,“In most cases, a FFI is defined by a "higher-level" language, so that it may employ services defined and implemented in a lower level language, typically a systems language like C or C++. ”


    Qt中,JS访问C++有两种方式:

    (1)一种是将自定义的C++类型注册到QML的类型系统中。


    class MyClass : public QObject//注意:必须要求是QObject派生的类
    {
        Q_OBJECT //为了使用Meta-Object System,这个宏是必须的
        Q_PROPERTY(int name READ name WRITE setName)//定义可以在js中被查询和修改的属性
    public:
        Q_INVOKABLE void foo();//定义可以在js中访问的方法
        int name();
        void setName(int n);
    private:
        int m_name;
    };
    


    qmlRegisterType<MyClass>(“mytype”, 1, 0, “MyClass”);//将MyClass注册到QML的类型系统中,以便在qml中使用该类型。


    //a.直接在qml文件中创建元素
    import QtQuick 2.2
    import mytype 1.0
    Window{
        id:mywindow
    	MyClass{
        		id:myObj
        		name:“longlong”
        		Component.onComplete:{
    		console.log(myObj.name);
        		}
    	};
    
    }
    
    
    //b.JS中动态创建对象
    var myObj = Qt.createQmlObject('import QtQuick 2.2;  import my 1.0;MyClass{name:“yangyang”}’,mywindow, “dynamicMyClassCreate");
    
    //c.将MyClass写入一个单独的qml文件,动态创建对象
    
    //myclass.qml
    import QtQuick 2.2
    import mytype 1.0
    
    MyClass{
    	name:”longlong-yangyang”
    }
    var myObj = Qt.createComponent("myclass.qml");
    



    (2)另一种是将c++中定义的对象放到js的全局变量中。


    QQmlApplicationEngine engine;
    MyClass object;//在C++代码中定义对象object
    engine.rootContext()->setContextProperty("myObj", &object);//将object挂到QML engine的上下文中,这样在任何QML文件中,都可以通过myObj.property和myObj.method()的方式访问myObj了。不过这种方式的性能是比较低。


    “Note: Since all expressions evaluated in QML are evaluated in a particular context, if the context is modified, all bindings in that context will be re-evaluated. Thus, context properties should be used with care outside of application initialization, as this may lead to decreased application performance.”——http://qt-project.org/doc/qt-5/qtqml-cppintegration-contextproperties.html


            Javascript访问c++属于脚本语言对静态语言的调用范畴,那么涉及到属性的访问,方法的调用,要想做到这些事情js需要考虑哪些问题呢?

            首先,js要想要访问属性,需要了解c++的类型,数据的存储结构,已经如何得到数据并转换数据类型;另外,js想要调用c++中的方法,需要考虑如何找到方法的实现,如何像方法中传递参数,如何跳转到方法中去执行c++的方法,如何从c++方法中返回到js中继续执行。这里我们来了解一下lua中调用c过程的一个例子:

    a.c文件中定义了add方法,如下:

        //a.c
        int add(int a, int b){
            return a+b;
        }

    为了能让lua中调用到c函数,lua要求提供一个wrap文件,对add函数进行包装,这里定义到wrap_a.c文件中,如下:

    //wrap_a.c,这里还是c文件
    	int wrap_add(LuaState* L){
    		int tmp1 = Lua_checkValueInt(L,1);
    		int tmp2 = Lua_checkValueInt(L,2);
    		int result = add(tmp1, tmp2);
    		Lua_pushValue(L,result);
    		return 1;
    	}


             Lua规定,wrap函数的参数是LuaState*这样一个参数,这里就是lua中实现的c的调用过程,在调用wrap函数时,会分配一个LuaState(可以理解为一个栈),将参数放到LuaState中(压栈过程),在wrap_add函数中,从LuaState取到参数并检查类型,最后将返回值压栈并返回返回值个数(Lua中支持多个返回值)。


            这样我们在Lua中就可以以myModule.add(1,2);的方式调用c的接口(在这之前还需要建立一张表,如table = {“add”,wrap_add},来告诉lua解释器函数名的映射)。当然,内部需要lua的运行时支持对c函数的调用,才能调用到wrap_add接口(关于这个过程涉及到更底层的abi层面的东西)。


            了解Lua对c的调用,我们再回过头来思考Qt中Javascript对C++调用这个问题,我们考虑一下前面所提到的两个问题:

    (1)数据类型的识别(包括数据的存储格式,访问方式)。

    (2)数据类型的转换(类型系统如何互通)。

    对象是采用C++中的类创建的,也是按照C++的数据存储格式存储的,JavaScript要想去访问,就必须要了解这一切,才能够认识C++中的数据,进而才能做数据类型转换的事情。那么JavaScript如何才能知道这些呢?答案只有一个:Meta-Object System。想想为了让JS能够认识C++的类型,我们做了什么?类必须由QObject继承而来,类中必须使用Q_OBJECT宏,如果想要在QML中使用该类型还必须调用qmlRegisterType进行类型注册,这一切都是为了让JS引擎更懂我们定义的类型,也就是让JS引擎知道我们定义类型的元信息,有了元信息,Qt就可以帮我们做很多像Lua要求c做的事情,而这些都不需要用户来操心了。

    4、C++ 调用 JS

    我想这又是FFI中所说的,“Many FFIs also provide the means for the called language to invoke services in the host language as well.”

    Qt中,C++创建JS的对象的方式如下:   

    // Using QQmlComponent
       QQmlEngine engine;
       QQmlComponent component(&engine,
       QUrl::fromLocalFile(“MyItem.qml”));//若要动态创建,可以使用 QUrl::setData接口
       QObject *object = component.create();
       ...
       delete object;


    //读写属性
    rx2 = object->property(“qmlObjProperty");
    object->setProperty("qmlObjProperty", wx2);


    //调用方法

     QMetaObject::invokeMethod(object, "myMethodInt",
                            Q_RETURN_ARG(QVariant, returnedValue),
                            Q_ARG(QVariant,100));

    C++去操作JavaScript,也需要关心类似的事情:类型的识别,类型的转换。

    QML中的对象的类型分为两种:

    (1)一种是C++中定义的类型,注册到QML中。这种对象的数据实际是按照C++的格式布局,这种对象从JavaScript中传到C++中,C++只要做一下指针类型转换,就可以像操作C++对象一样使用了。

    (2)另一种是JavaScript类型的对象。这种类型的对象,不能直接转换为C++对象使用,Qt中提供一种类型QJSValue,用于包装JavaScript类型的对象。


    如果我们并不知道从QML中创建的对象是上面的哪一种,或者我们就不关心它是哪一种,我们就可以按照上面代码的方式去统一操作,这里的细节都被封装在了Component中,而不需要用户去操心这些。但是,这样我们也必须遵照Component创建对象的操作规则,属性访问只能通过getter和setter接口,方法调用只能通过invokeMethod接口。这里需要说明的是,这里需要JS提供一些支持,例如根据属性名找到对应的属性和方法,从外部去invoke一个方法等(Qt从Qt 5.2版本后采用Qt自己实现的V4JS引擎,之前使用v8引擎)。




    5、补充:关于JavaScript调用C++定义函数的参数传递

            关于Qt中JavaScript和C++混合调用参数的内部的自动类型转换,可以参考:http://qt-project.org/doc/qt-5/qtqml-cppintegration-data.html,这里我们只探讨js中定义的对象作为参数传递给c++时的情况。


    调用方式:

    cOjb.foo(param);//其中cObj为C++类型的对象(如上文描述,该对象可能是在c代码中创建然后挂在js的全局上下文中供js访问,也可能是在js中创建)


    C++中函数定义方式:

    void MyClass::foo(<Type> cParam){
    //do something
    }


    分为以下几种情况:

    js中定义的对象作为函数参数传递给c++函数

    C++函数形参Type:QObject *

    C++函数形参Type:QJSValue

    C++函数形参Type:QVariantMap

    js中实参param类型:QML Base Type(我只试了Qt.rect)

    不支持,QObject * = 0;

    支持,通过propert方法获取属性

    不支持,QVariantMap为空

    js中实参param类型param:JS字面量对象

    不支持,QObject * = 0;

    支持,通过propert方法获取属性

    支持,属于值传递

    js中实参param类型param:c++注册进来的类型定义的对象或者QML元素

    支持,可以将QObject*强转成指定类型的指针使用

    支持,通过propert方法获取属性

    支持,属于值传递




     







    展开全文
  • Alpha混合

    2012-12-27 11:50:09
    Alpha混合中的绝大多数特效都与某些类型的(色彩)混合有关。混色的定义为,将某个象素的颜色和已绘制在屏幕上与其对应的象素颜色相互结合。至于如何结合这两个颜色则依赖于颜色的alpha通道的分量值,以及/或者所...
            Alpha混合中的绝大多数特效都与某些类型的(色彩)混合有关。混色的定义为,将某个象素的颜色和已绘制在屏幕上与其对应的象素颜色相互结合。至于如何结合这两个颜色则依赖于颜色的alpha通道的分量值,以及/或者所使用的混色函数。Alpha通常是位于颜色值末尾的第4个颜色组成分量。前面这些课我们都是用 GL_RGB来指定颜色的三个分量。相应的GL_RGBA可以指定alpha分量的值。更进一步,我们可以使用glColor4f()来代替 glColor3f()。
    
       绝大多数人都认为Alpha分量代表材料的透明度。这就是说,alpha值为0.0时所代表的材料是完全透明的。alpha值为1.0时所代表的材料则是完全不透明的。

    8.1、混色的公式
       若您对数学不感兴趣,而只想看看如何实现透明,请跳过这一节。若您想深入理解(色彩)混合的工作原理,这一节应该适合您吧。( 译者:其实混合的基本原理是就将要分色的图像各象素的颜色以及背景颜色均按照RGB规则各自分离之后,根据 — 图像的RGB颜色分量*alpha值+背景的RGB颜色分量*(1-alpha值) — 这样一个简单公式来混合之后,最后将混合得到的RGB分量重新合并。)公式如下:

    (Rs Sr + Rd Dr, Gs Sg + Gd Dg, Bs Sb + Bd Db, As Sa + Ad Da)
    OpenGL按照上面的公式计算这两个象素的混色结果。小写的s和r分别代表源象素和目标象素。大写的S和D则是相应的混色因子。这些决定了您如何对这些象素混色。绝大多数情况下,各颜色通道的alpha混色值大小相同,这样对源象素就有(As, As, As, As),目标象素则有(1, 1, 1, 1) - (As, As, As, As)。上面的公式就成了下面的模样:

    (Rs As + Rd (1 - As), Gs As + Gd (1 - As), Bs As + Bs (1 - As), As As + Ad (1 - As))
      这个公式会生成透明/半透明的效果。

    8.2、OpenGL中的混色
      在OpenGL中实现混色的步骤类似于我们以前提到的OpenGL过程。接着设置公式,并在绘制透明对象时关闭写深度缓存。因为我们想在半透明的图形背后绘制 对象。这不是正确的混色方法,但绝大多数时候这种做法在简单的项目中都工作的很好。
       Rui Martins的补充:正确的混色过程应该是先绘制全部的场景之后再绘制透明的图形。并且要按照与深度缓存相反的次序来绘制(先画最远的物体)。考虑对两个多边形(1和2)进行 alpha混合,不同的绘制次序会得到不同的结果。(这里假定多边形1离观察者最近,那么正确的过程应该先画多边形2,再画多边形1。正如您再现实中所见到的那样,从这两个“透明的”多边形背后照射来的光线总是先穿过多边形2,再穿过多边形1,最后才到达观察者的眼睛)。在深度缓存启用时,您应该将透明图形按照深度进行排序,并在全部场景绘制完毕之后再绘制这些透明物体。否则您将得到不正确的结果。我知道某些时候这样做是很令人痛苦的,但这是正确的方法。
      我们将使用第七课的代码。一开始先在代码开始处增加两个新的变量。出于清晰起见,我重写了整段代码。

      #include <windows.h>                    // Windows的头文件
      #include <stdio.h>                     // 标准输入/输出库的头文件
      #include <gl\gl.h>                     // OpenGL32库的头文件
      #include <gl\glu.h>                    // GLu32库的头文件
      #include <gl\glaux.h>                   // GLaux库的头文件

      HGLRC hRC=NULL;                      // 永久着色描述表
      HDC hDC=NULL;                       // 私有GDI设备描述表
      HWND hWnd=NULL;                      // 保存我们的窗口句柄
      HINSTANCE hInstance;                    // 保存程序的实例

      bool keys[256];                      // 用于键盘例程的数组
      bool active=TRUE;                     // 窗口的活动标志,缺省为TRUE
      bool fullscreen=TRUE;                   // 全屏标志缺省设定成全屏模式

      BOOL light;                        // 光源的开/关
      bool blend;                        // Blending 开/关 ( 新增 )
      BOOL lp;                          // L键按下了么?
      BOOL fp;                          // F键按下了么?

      GLfloat xrot;                       // X 旋转
      GLfloat yrot;                       // Y 旋转
      GLfloat xspeed;                      // X 旋转速度
      GLfloat yspeed;                      // Y 旋转速度

      GLfloat z=-5.0f;                      // 深入屏幕的距离

      GLfloat LightAmbient[]= { 0.5f };             // 环境光参数
      GLfloat LightDiffuse[]= { 1.0f };              // 漫射光参数
      GLfloat LightPosition[]= { 0.0f };             // 光源位置

      GLuint filter;                       // 滤波类型
      GLuint texture[3];                     // 3种纹理的储存空间
      LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);   // WndProc定义

      然后往下移动到LoadGLTextures()这里。找到“if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))”这一行。我们现在使用有色玻璃纹理来代替上一课中的木箱纹理。

          if (TextureImage[0]=LoadBMP("Data/glass.bmp"))   // 载入玻璃位图 (已修改)

      在InitGL()代码段加入以下两行。第一行以全亮度绘制此物体,并对其进行50%的alpha混合(半透明)。当混合选项打开时,此物体将会产生50%的透明效果。第二行设置所采用的混合类型。 Rui Martins的补充:alpha通道的值为0.0意味着物体材质是完全透明的。1.0则意味着完全不透明。

          glColor4f(1.0f,1.0f,1.0f,0.5f);          // 全亮度, 50% Alpha 混合(新增)
          glBlendFunc(GL_SRC_ALPHA,GL_ONE); // 基于源象素alpha通道值的半透明混合函数 (新增)

      在接近第七课结尾处的地方找到下面的代码段。

          if (keys[VK_LEFT])                // Left方向键按下了么?
          {
              yspeed-=0.01f;              // 若是,减少yspeed
          }

      接着上面的代码,我们增加如下的代码。这几行监视B键是否按下。如果是的话,计算机检查混合选项是否已经打开。然后将其置为相反的状态。

          if (keys[VK_LEFT])                // Left方向键按下了么?
          if (keys[’B’] && !bp)               // B 健按下且bp为 FALSE么?
          {
              bp=TRUE;                 // 若是, bp 设为 TRUE
              blend = !blend;              // 切换混合选项的 TRUE / FALSE
              if(blend)                 // 混合打开了么?
              {
                  glEnable(GL_BLEND);        // 打开混合
                  glDisable(GL_DEPTH_TEST);    // 关闭深度测试
              }
              else                   // 否则
              {
                  glDisable(GL_BLEND);       // 关闭混合
                  glEnable(GL_DEPTH_TEST);     // 打开深度测试
              }
          }
          if (!keys[’B’])                 // B 键松开了么?
          {
              bp=FALSE;                // 若是, bp设为 FALSE
          }

    但是怎样才能在使用纹理贴图的时候指定混合时的颜色呢?很简单,在调整贴图模式时,文理贴图的每个象素点的颜色都是由alpha通道参数与当前地象素颜色相乘所得到的。比如,绘制的颜色是(0.5, 0.6, 0.4),我们会把颜色相乘得到(0.5, 0.6, 0.4, 0.2)(alpha参数在没有指定时,缺省为1.0)。
      就是如此。OpenGL实现Alpha混合的确很简单。

       原文注(11/13/1999)
    我(NeHe)混色代码进行了修改,以使显示的物体看起来更逼真。同时对源象素和目的象素使用alpha参数来混合,会导致物体的人造痕迹看起来很明显。会使得物体的背面沿着侧面的地方显得更暗。基本上物体会看起来很怪异。我所用的混色方法也许不是最好的,但的确能够工作。启用光源之后,物体看起来很逼真。感谢Tom提供的原始代码,他采用的混色方法是正确的,但物体看起来并不象所期望的那样吸引人。
      代码所作的再次修改是因为在某些显卡上glDepthMask()函数存在寻址问题。这条命令在某些卡上启用或关闭深度缓冲测试时似乎不是很有效,所以我已经将启用或关闭深度缓冲测试的代码转成老式的glEnable和glDisable。

    8.3、纹理贴图的Alpha混合
      用于纹理贴图的alpha参数可以象颜色一样从问题贴图中读取。方法如下,您需要在载入所需的材质同时取得其的alpha参数。然后在调用glTexImage2D()时使用GL_RGBA的颜色格式。
    展开全文
  • 最通用的函数还是 PyErr_SetObject() ,包含两个参数,分别为异常对象和异常描述。你不需要使用 Py_INCREF() 来增加传递到其他函数的参数对象的引用计数。 你可以通过 PyErr_Occurred() 获知当前异常,返回当前...
      
    

         登陆论坛  | 论坛注册| 加入收藏 | 设为首页| RSS
     
      
    首页Linux频道软件下载开发语言嵌入式频道开源论坛
    | php | JSP | ASP | asp.net | JAVA | c/c++/c# | perl | JavaScript | Basic | Delphi | 
     
     
    您当前的位置:首页 > 开发语言 > c/c++/c#
        
    使用C/C++扩展Python
    时间:2007-12-12 12:33:14  来源:Linux联盟收集整理  作者:
    如果你会用C,实现Python嵌入模块很简单。利用扩展模块可做很多Python不方便做的事情,他们可以直接调用C库和系统调用。

    为了支持扩展,Python API定义了一系列函数、宏和变量,提供了对Python运行时系统的访问支持。Python的C API由C源码组成,并包含 “Python.h” 头文件。

    编写扩展模块与你的系统相关,下面会详解。1   一个简单的例子
    下面的例子创建一个叫做 “spam” 的扩展模块,调用C库函数 system() 。这个函数输入一个NULL结尾的字符串并返回整数,可供Python调用方式如下:

    >>> import spam>>> status=spam.system("ls -l")
    一个C扩展模块的文件名可以直接是 模块名.c 或者是 模块名module.c 。第一行应该导入头文件:

    #include <Python.h>
    这会导入Python API。

    Warning

    因为Python含有一些预处理定义,所以你必须在所有非标准头文件导入之前导入Python.h 。

    Python.h中所有用户可见的符号都有 Py 或 PY 的前缀,除非定义在标准头文件中。为了方便 “Python.h” 也包含了一些常用的标准头文件,包括<stdio.h>,<string.h>,<errno.h>,< stdlib.h>。如果你的系统没有后面的头文件,则会直接定义函数 malloc() 、 free() 和 realloc() 。

    下面添加C代码到扩展模块,当调用 “spam.system(string)” 时会做出响应:

    static PyObject*spam_system(PyObject* self, PyObject* args) {    const char* command;    int sts;    if (!PyArg_ParseTuple(args,"s",&command))        return NULL;    sts=system(command);    return Py_BuildValue("i",sts);}
    调用方的Python只有一个命令参数字符串传递到C函数。C函数总是有两个参数,按照惯例分别叫做 self 和 args 。

    self 参数仅用于用C实现内置方法而不是函数。本例中, self 总是为NULL,因为我们定义的是个函数,不是方法。这一切都是相同的,所以解释器也就不需要刻意区分两种不同的C函数。

    args 参数是一个指向Python的tuple对象的指针,包含参数。每个tuple子项对应一个调用参数。这些参数也全都是Python对象,所以需要先转换成C值。函数 PyArg_ParseTuple() 检查参数类型并转换成C值。它使用模板字符串检测需要的参数类型。

    PyArg_ParseTuple() 正常返回非零,并已经按照提供的地址存入了各个变量值。如果出错(零)则应该让函数返回NULL以通知解释器出错。

    2   关于错误和异常
    一个常见惯例是,函数发生错误时,应该设置一个异常环境并返回错误值(NULL)。异常存储在解释器静态全局变量中,如果为NULL,则没有发生异常。异常的第一个参数也需要保存在静态全局变量中,也就是raise的第二个参数。第三个变量包含栈回溯信息。这三个变量等同于Python变量 sys.exc_type 、 sys.exc_value 、 sys.exc_traceback 。这对找到错误是很必要的。

    Python API中定义了一些函数来设置这些变量。

    最常用的就是 PyErr_SetString() 。参数是异常对象和C字符串。异常对象一般由像 PyExc_ZeroDivisionError 这样的对象来预定义。C字符串指明异常原因,并最终存储在异常的第一个参数里面。

    另一个有用的函数是 PyErr_SetFromErrno() ,仅接受一个异常对象,异常描述包含在全局变量 errno 中。最通用的函数还是 PyErr_SetObject() ,包含两个参数,分别为异常对象和异常描述。你不需要使用 Py_INCREF() 来增加传递到其他函数的参数对象的引用计数。

    你可以通过 PyErr_Occurred() 获知当前异常,返回当前异常对象,如果确实没有则为NULL。一般来说,你在调用函数时不需要调用 PyErr_Occurred() 检查是否发生了异常,你可以直接检查返回值。

    如果调用更下层函数时出错了,那么本函数返回NULL表示错误,并且整个调用栈中只要有一处调用 PyErr_*() 函数设置异常就可以。一般来说,首先发现错误的函数应该设置异常。一旦这个错误到达了Python解释器的主循环,则会中断当前执行代码并追究异常。

    有一种情况下,模块可能依靠其他 PyErr_*() 函数给出更加详细的错误信息,并且是正确的。但是按照一般规则,这并不重要,很多操作都会因为种种原因而挂掉。

    想要忽略这些函数设置的异常,异常情况必须明确的使用 PyErr_Clear() 来清除。只有在C代码想要自己处理异常而不是传给解释器时才这么做。

    每次失败的 malloc() 调用必须抛出一个异常,直接调用 malloc() 或 realloc() 的地方要调用 PyErr_NoMemory() 并返回错误。所有创建对象的函数都已经实现了这个异常的抛出,所以这是每个分配内存都要做的。

    还要注意的是 PyArg_ParseTuple() 系列函数的异常,返回一个整数状态码是有效的,0是成功,-1是失败,有如Unix系统调用。

    最后,小心垃圾情理,也就是 Py_XDECREF() 和 Py_DECREF() 的调用,会返回的异常。

    选择抛出哪个异常完全是你的个人爱好了。有一系列的C对象代表了内置Python异常,例如 PyExc_ZeroDivisionError ,你可以直接使用。当然,你可能选择更合适的异常,不过别使用 PyExc_TypeError 告知文件打开失败(有个更合适的 PyExc_IOError )。如果参数列表有误, PyArg_ParseTuple() 通常会抛出 PyExc_TypeError 。如果参数值域有误, PyExc_ValueError 更合适一些。

    你也可以为你的模块定义一个唯一的新异常。需要在文件前部声明一个静态对象变量,如:

    static PyObject* SpamError;
    然后在模块初始化函数(initspam())里面初始化它,并省却了处理:

    PyMODINIT_FUNCinitspam(void) {    PyObject* m;    m=Py_InitModule("spam",SpamMethods);    if (m==NULL)        return NULL;    SpamError=PyErr_NewException("spam.error",NULL,NULL);    Py_INCREF(SpamError);    PyModule_AddObject(m,"error",SpamError);}
    注意实际的Python异常名字是 spam.error 。 PyErr_NewException() 函数使用Exception为基类创建一个类(除非是使用另外一个类替代NULL)。

    同样注意的是创建类保存了SpamError的一个引用,这是有意的。为了防止被垃圾回收掉,否则SpamError随时会成为野指针。

    一会讨论 PyMODINIT_FUNC 作为函数返回类型的用法。

    3   回到例子
    回到前面的例子,你应该明白下面的代码:

    if (!PyArg_ParseTuple(args,"s",&command))    return NULL;
    就是为了报告解释器一个异常。如果执行正常则变量会拷贝到本地,后面的变量都应该以指针的方式提供,以方便设置变量。本例中的command会被声明为 “const char* command” 。

    下一个语句使用UNIX系统函数system(),传递给他的参数是刚才从 PyArg_ParseTuple() 取出的:

    sts=system(command);
    我们的 spam.system() 函数必须返回一个PY对象,这可以通过 Py_BuildValue() 来完成,其形式与 PyArg_ParseTuple() 很像,获取格式字符串和C值,并返回新的Python对象:

    return Py_BuildValue("i",sts);
    在这种情况下,会返回一个整数对象,这个对象会在Python堆里面管理。

    如果你的C函数没有有用的返回值,则必须返回None。你可以用 Py_RETUN_NONE 宏来完成:

    Py_INCREF(Py_None);return Py_None;
    Py_None 是一个C名字指定Python对象None。这是一个真正的PY对象,而不是NULL指针。

    4   模块方法表和初始化函数
    把函数声明为可以被Python调用,需要先定义一个方法表:

    static PyMethodDef SpamMethods[]= {    ...    {"system",spam_system,METH_VARARGS,    "Execute a shell command."},    ...    {NULL,NULL,0,NULL}    /*必须的结束符*/};
    注意第三个参数 METH_VARARGS ,这个标志指定会使用C的调用惯例。可选值有 METH_VARARGS 、 METH_VARARGS | METH_KEYWORDS 。值0代表使用 PyArg_ParseTuple() 的陈旧变量。

    如果单独使用 METH_VARARGS ,函数会等待Python传来tuple格式的参数,并最终使用 PyArg_ParseTuple() 进行解析。

    METH_KEYWORDS 值表示接受关键字参数。这种情况下C函数需要接受第三个 PyObject* 对象,表示字典参数,使用 PyArg_ParseTupleAndKeywords() 来解析出参数。

    方法表必须传递给模块初始化函数。初始化函数函数名规则为 initname() ,其中 name 为模块名。并且不能定义为文件中的static函数:

    PyMODINIT_FUNCinitspam(void) {    (void) Py_InitModule("spam",SpamMethods);}
    注意 PyMODINIT_FUNC 声明了void为返回类型,还有就是平台相关的一些定义,如C++的就要定义成 extern “C” 。

    Python程序首次导入这个模块时就会调用initspam()函数。他调用 Py_InitModule() 来创建一个模块对象,同时这个模块对象会插入到 sys.modules 字典中的 “spam” 键下面。然后是插入方法表中的内置函数到 “spam” 键下面。 Py_InitModule() 返回一个指针指向刚创建的模块对象。他是有可能发生严重错误的,也有可能在无法正确初始化时返回NULL。

    当嵌入Python时, initspam() 函数不会自动被调用,除非在入口处的 _PyImport_Inittab 表。最简单的初始化方法是在 Py_Initialize() 之后静态调用 initspam() 函数:

    intmain(int argc, char* argv[]) {    Py_SetProgramName(argv[0]);    Py_Initialize();    initspam();    //...}
    在Python发行版的 Demo/embed/demo.c 中有可以参考的源码。

    Note

    从 sys.modules 中移除模块入口,或者在多解释器环境中导入编译模块,会导致一些扩展模块出错。扩展模块作者应该特别注意初始化内部数据结构。同时要注意 reload() 函数可能会被用在扩展模块身上,并调用模块初始化函数,但是对动态状如对象(动态链接库),却不会重新载入。

    更多关于模块的现实的例子包含在Python源码包的Modules/xxmodule.c中。这些文件可以用作你的代码模板,或者学习。脚本 modulator.py 包含在源码发行版或Windows安装中,提供了一个简单的GUI,用来声明需要实现的函数和对象,并且可以生成供填入的模板。脚本在 Tools/modulator/ 目录。查看README以了解用法。

    5   编译和连接
    如果使用动态载入,细节依赖于系统,查看关于构建扩展模块部分,和关于在Windows下构建扩展的细节。

    如果你无法使用动态载入,或者希望模块成为Python的永久组成部分,就必须改变配置并重新构建解释器。幸运的是,这对UNIX来说很简单,只要把你的代码(例如spammodule.c)放在 Modules/ Python源码目录下,然后增加一行到文件 Modules/Setup.local 来描述你的文件即可:

    spam spammodule.o
    然后重新构建解释器,使用make。你也可以在 Modules/ 子目录使用make,但是你接下来首先要重建Makefile文件,使用 make Makefile 命令。这对你改变 Setup 文件来说很重要。

    如果你的模块需要其他扩展模块连接,则需要在配置文件后面加入,如:

    spam spammodule.o -lX11
    6   在C中调用Python函数
    迄今为止,我们一直把注意力集中于让Python调用C函数,其实反过来也很有用,就是用C调用Python函数。这在回调函数中尤其有用。如果一个C接口使用回调,那么就要实现这个回调机制。

    幸运的是,Python解释器是比较方便回调的,并给标准Python函数提供了标准接口。这里就不再详述解析Python代码作为输入的方式,如果有兴趣可以参考 Python/pythonmain.c 中的 -c 命令代码。

    调用Python函数,首先Python程序要传递Python函数对象。当调用这个函数时,用全局变量保存Python函数对象的指针,还要调用 Py_INCREF() 来增加引用计数,当然不用全局变量也没什么关系。例如如下:

    static PyObject* my_callback=NULL;static PyObject*my_set_callback(PyObject* dummy, PyObject* args) {    PyObject* result=NULL;    PyObject* temp;    if (PyArg_ParseTuple(args,"O:set_callback",&temp)) {        if (!PyCallable_Check(temp)) {            PyErr_SetString(PyExc_TypeError,"parameter must be callable");            return NULL;        }        Py_XINCREF(temp);        Py_XINCREF(my_callback);        my_callback=temp;        Py_INCREF(Py_None);        result=Py_None;    }    return result;}
    这个函数必须使用 METH_VARARGS 标志注册到解释器。宏 Py_XINCREF() 和 Py_XDECREF() 增加和减少对象的引用计数。

    然后,就要调用函数了,使用 PyEval_CallObject() 。这个函数有两个参数,都是指向Python对象:Python函数和参数列表。参数列表必须总是tuple对象,如果没有参数则要传递空的tuple。使用 Py_BuildValue() 时,在圆括号中的参数会构造成tuple,无论有没有参数,如:

    int arg;PyObject* arglist;PyObject* result;//...arg=123;//...arglist=Py_BuildValue("(i)",arg);result=PyEval_CallObject(my_callback,arglist);Py_DECREF(arglist);
    PyEval_CallObject() 返回一个Python对象指针表示返回值。 PyEval_CallObject() 是 引用计数无关 的,有如例子中,参数列表对象使用完成后就立即减少引用计数了。`PyEval_CallObject()` 返回一个Python对象指针表示返回值。 PyEval_CallObject() 是 引用计数无关 的,有如例子中,参数列表对象使用完成后就立即减少引用计数了。

    PyEval_CallObject() 的返回值总是新的,新建对象或者是对已有对象增加引用计数。所以你必须获取这个对象指针,在使用后减少其引用计数,即便是对返回值没有兴趣也要这么做。但是在减少这个引用计数之前,你必须先检查返回的指针是否为NULL。如果是NULL,则表示出现了异常并中止了。如果没有处理则会向上传递并最终显示调用栈,当然,你最好还是处理好异常。如果你对异常没有兴趣,可以用 PyErr_Clear() 清除异常,例如:

    if (result==NULL)    return NULL;  /*向上传递异常*///使用resultPy_DECREF(result);
    依赖于具体的回调函数,你还要提供一个参数列表到 PyEval_CallObject() 。在某些情况下参数列表是由Python程序提供的,通过接口再传到回调函数。这样就可以不改变形式直接传递。另外一些时候你要构造一个新的tuple来传递参数。最简单的方法就是 Py_BuildValue() 函数构造tuple。例如,你要传递一个事件对象时可以用:

    PyObject* arglist;//...arglist=Py_BuildValue("(l)",eventcode);result=PyEval_CallObject(my_callback,arglist);Py_DECREF(arglist);if (result==NULL)    return NULL;  /*一个错误*//*使用返回值*/Py_DECREF(result);
    注意 Py_DECREF(arglist) 所在处会立即调用,在错误检查之前。当然还要注意一些常规的错误,比如 Py_BuildValue() 可能会遭遇内存不足等等。

    7   解析传给扩展模块函数的参数
    函数 PyArg_ParseTuple() 声明如下:

    int PyArg_ParseTuple(PyObject* arg, char* format, ...);
    参数 arg 必须是一个tuple对象,包含传递过来的参数, format 参数必须是格式化字符串,语法解释见 “Python C/API” 的5.5节。剩余参数是各个变量的地址,类型要与格式化字符串对应。

    注意 PyArg_ParseTuple() 会检测他需要的Python参数类型,却无法检测传递给他的C变量地址,如果这里出错了,可能会在内存中随机写入东西,小心。

    任何Python对象的引用,在调用者这里都是 借用的引用 ,而不增加引用计数。

    一些例子:

    int ok;int i,j;long k,l;const char* s;int size;ok=PyArg_ParseTuple(args,"");/* python call: f() */ok=PyArg_ParseTuple(args,"s",&s);/* python call: f('whoops!') */ok=PyArg_ParseTuple(args,"lls",&k,&l,&s);/* python call: f(1,2,'three') */ok=PyArg_ParseTuple(args,"(ii)s#",&i,&j,&s,&size);/* python call: f((1,2),'three') */{    const char* file;    const char* mode="r";    int bufsize=0;    ok=PyArg_ParseTuple(args,"s|si",&file,&mode,&bufsize);    /* python call:        f('spam')        f('spam','w')        f('spam','wb',100000)    */}{    int left,top,right,bottom,h,v;    ok=PyArg_ParseTuple(args,"((ii)(ii))(ii)",        &left,&top,&right,&bottom,&h,&v);    /* python call: f(((0,0),(400,300)),(10,10)) */}{    Py_complex c;    ok=PyArg_ParseTuple(args,"D:myfunction",&c);    /* python call: myfunction(1+2j) */}
    8   解析传给扩展模块函数的关键字参数
    函数 PyArg_ParseTupleAndKeywords() 声明如下:

    int PyArg_ParseTupleAndKeywords(PyObject* arg, PyObject* kwdict, char* format, char* kwlist[],...);
    参数arg和format定义同 PyArg_ParseTuple() 。参数 kwdict 是关键字字典,用于接受运行时传来的关键字参数。参数 kwlist 是一个NULL结尾的字符串,定义了可以接受的参数名,并从左到右与format中各个变量对应。如果执行成功 PyArg_ParseTupleAndKeywords() 会返回true,否则返回false并抛出异常。

    Note

    嵌套的tuple在使用关键字参数时无法生效,不在kwlist中的关键字参数会导致 TypeError 异常。

    如下是使用关键字参数的例子模块,作者是 Geoff Philbrick (phibrick@hks.com):

    #include "Python.h"static PyObject*keywdarg_parrot(PyObject* self, PyObject* args, PyObject* keywds) {    int voltage;    char* state="a stiff";    char* action="voom";    char* type="Norwegian Blue";    static char* kwlist[]={"voltage","state","action","type",NULL};    if (!PyArg_ParseTupleAndKeywords(args,keywds,"i|sss",kwlist,            &voltage,&state,&action,&type))        return NULL;    printf("-- This parrot wouldn't %s if you put %i Volts through it.n",action,voltage);    printf("-- Lovely plumage, the %s -- It's %s!n",type,state);    Py_INCREF(Py_None);    return Py_None;}static PyMethodDef keywdary_methods[]= {    /*注意PyCFunction,这对需要关键字参数的函数很必要*/    {"parrot",(PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,"Print a lovely skit to standard output."},    {NULL,NULL,0,NULL}};voidinitkeywdarg(void) {    Py_InitModule("keywdarg",keywdarg_methods);}
    9   构造任意值
    这个函数声明与 PyArg_ParseTuple() 很相似,如下:

    PyObject* Py_BuildValue(char* format, ...);
    接受一个格式字符串,与 PyArg_ParseTuple() 相同,但是参数必须是原变量的地址指针。最终返回一个Python对象适合于返回给Python代码。

    一个与 PyArg_ParseTuple() 的不同是,后面可能需要的要求返回一个tuple,比如用于传递给其他Python函数以参数。 Py_BuildValue() 并不总是生成tuple,在多于1个参数时会生成tuple,而如果没有参数则返回None,一个参数则直接返回该参数的对象。如果要求强制生成一个长度为空的tuple,或包含一个元素的tuple,需要在格式字符串中加上括号。

    例如:

    代码 返回值
    Py_BuildValue(”") None
    Py_BuildValue(”i”,123) 123
    Py_BuildValue(”iii”,123,456,789) (123,456,789)
    Py_BuildValue(”s”,”hello”) ‘hello’
    Py_BuildValue(”ss”,”hello”,”world”) (’hello’, ‘world’)
    Py_BuildValue(”s#”,”hello”,4) ‘hell’
    Py_BuildValue(”()”) ()
    Py_BuildValue(”(i)”,123) (123,)
    Py_BuildValue(”(ii)”,123,456) (123,456)
    Py_BuildValue(”(i,i)”,123,456) (123,456)
    Py_BuildValue(”[i,i]”,123,456) [123,456]
    Py_BuildValue(”{s:i,s:i}”,’a',1,’b',2) {’a':1,’b':2}
    Py_BuildValue(”((ii)(ii))(ii)”,1,2,3,4,5,6) (((1,2),(3,4)),(5,6))

    10   引用计数
    在C/C++语言中,程序员负责动态分配和回收堆(heap)当中的内存。这意味着,我们在C中编程时必须面对这个问题。

    每个由 malloc() 分配的内存块,最终都要由 free() 扔到可用内存池里面去。而调用 free() 的时机非常重要,如果一个内存块忘了 free() 则是内存泄漏,程序结束前将无法重新使用。而如果对同一内存块 free() 了以后,另外一个指针再次访问,则叫做野指针。这同样会导致严重的问题。

    内存泄露往往发生在一些并不常见的程序流程上面,比如一个函数申请了资源以后,却提前返回了,返回之前没有做清理工作。人们经常忘记释放资源,尤其对于后加新加的代码,而且会长时间都无法发现。这些函数往往并不经常调用,而且现在大多数机器都有庞大的虚拟内存,所以内存泄漏往往在长时间运行的进程,或经常被调用的函数中才容易发现。所以最好有个好习惯加上代码约定来尽量避免内存泄露。

    Python往往包含大量的内存分配和释放,同样需要避免内存泄漏和野指针。他选择的方法就是 引用计数 。其原理比较简单:每个对象都包含一个计数器,计数器的增减与引用的增减直接相关,当引用计数为0时,表示对象已经没有存在的意义了,就可以删除了。

    一个叫法是 自动垃圾回收 ,引用计数是一种垃圾回收方法,用户必须要手动调用 free() 函数。优点是可以提高内存使用率,缺点是C语言至今也没有一个可移植的自动垃圾回收器。引用计数却可以很好的移植,有如C当中的 malloc() 和 free() 一样。也许某一天会出现C语言饿自动垃圾回收器,不过在此之前我们还得用引用计数。

    Python使用传统的引用计数实现,不过他包含一个循环引用探测器。这允许应用不需要担心的直接或间接的创建循环引用,而这实际上是引用计数实现的自动垃圾回收的致命缺点。循环引用指对象经过几层引用后回到自己,导致了其引用计数总是不为0。传统的引用计数实现无法解决循环引用的问题,尽管已经没有其他外部引用了。

    循环引用探测器可以检测出垃圾回收中的循环并释放其中的对象。只要Python对象有 __del__() 方法,Python就可以通过 gc module 模块来自动暴露出循环引用。gc模块还提供 collect() 函数来运行循环引用探测器,可以在配置文件或运行时禁用循环应用探测器。

    循环引用探测器作为一个备选选项,默认是打开的,可以在构建时使用 –without-cycle-gc 选项加到 configure 上来配置,或者移除 pyconfig.h 文件中的 WITH_CYCLE_GC 宏定义。在循环引用探测器禁用后,gc模块将不可用。

    10.1   Python中的引用计数
    有两个宏 Py_INCREF(x) 和 Py_DECREF(x) 用于增减引用计数。 Py_DECREF() 同时会在引用计数为0时释放对象资源。为了灵活性,他并不是直接调用 free() 而是调用对象所在类型的析构函数。

    一个大问题是何时调用 Py_INCREF(x) 和 Py_DECREF(x) 。首先介绍一些术语。没有任何人都不会 拥有 一个对象,只能拥有其引用。对一个对象的引用计数定义了引用数量。拥有的引用,在不再需要时负责调用 Py_DECREF() 来减少引用计数。传递引用计数有三种方式:传递、存储和调用 Py_DECREF() 。忘记减少拥有的引用计数会导致内存泄漏。

    同样重要的一个概念是 借用 一个对象,借用的对象不能调用 Py_DECREF() 来减少引用计数。借用者在不需要借用时,不保留其引用就可以了。应该避免拥有者释放对象之后仍然访问对象,也就是野指针。

    借用的优点是你无需管理引用计数,缺点是可能被野指针搞的头晕。借用导致的野指针问题常发生在看起来无比正确,但是事实上已经被释放的对象。

    借用的引用也可以用 Py_INCREF() 来改造成拥有的引用。这对引用的对象本身没什么影响,但是拥有引用的程序有责任在适当的时候释放这个拥有。

    10.2   拥有规则
    一个对象的引用进出一个函数时,其引用计数也应该同时改变。

    大多数函数会返回一个对对象拥有的引用。而且几乎所有的函数其实都会创建一个对象,例如 PyInt_FromLong() 和 Py_BuildValue() ,传递一个拥有的引用给接受者。即便不是刚创建的,你也需要接受一个新的拥有引用。一般来说, PyInt_FromLong() 会维护一个常用值缓存,并且返回缓存项的引用。

    很多函数提取一些对象的子对象并传递拥有引用,例如 PyObject_GetAttrString() 。另外,小心一些函数,包括: PyTuple_GetItem() 、 PyList_GetItem() 、 PyDict_GetItem() 和 PyDict_GetItemString() ,他们返回的都是借用的引用。

    函数 PyImport_AddModule() 也是返回借用的引用,尽管他实际上创建了对象,只不过其拥有的引用实际存储在了 sys.modules 中。

    当你传递一个对象的引用到另外一个函数时,一般来说,函数是借用你的引用,如果他确实需要存储,则会使用 Py_INCREF() 来变为拥有引用。这个规则有两种可能的异常: PyTuple_SetItem() 和 PyList_SetItem() ,这两个函数获取传递给他的拥有引用,即便是他们执行出错了。不过 PyDict_SetItem() 却不是接收拥有的引用。

    当一个C函数被py调用时,使用对参数的借用。调用者拥有参数对象的拥有引用。所以,借用的引用的寿命是函数返回。只有当这类参数必须存储时,才会使用 Py_INCREF() 变为拥有的引用。

    从C函数返回的对象引用必须是拥有的引用,这时的拥有者是调用者。

    10.3   危险的薄冰
    有些使用借用的情况会出现问题。这是对解释器的盲目理解所导致的,因为拥有者往往提前释放了引用。

    首先而最重要的情况是使用 Py_DECREF() 来释放一个本来是借用的对象,比如列表中的元素:

    voidbug(PyObject* list) {    PyObject* item=PyList_GetItem(list,0);    PyList_SetItem(list,1,PyInt_FromLong(0L));    PyObject_Print(item,stdout,0); /* BUG! */}
    这个函数首先借用了 list[0] ,然后把 list[1] 替换为值0,最后打印借用的引用。看起来正确么,不是!

    我们来跟踪一下 PyList_SetItem() 的控制流,列表拥有所有元素的引用,所以当项目1被替换时,他就释放了原始项目1。而原始项目1是一个用户定义类的实例,假设这个类定义包含 __del__() 方法。如果这个类的实例引用计数为1,处理过程会调用 __del__() 方法。

    因为使用python编写,所以 __del__() 中可以用任何python代码来完成释放工作。替换元素的过程会执行 del list[0] ,即减掉了对象的最后一个引用,然后就可以释放内存了。

    知道问题后,解决方案就出来了:临时增加引用计数。正确的版本如下:

    voidno_bug(PyObject* list) {    PyObject* item=PyList_GetItem(list,0);    Py_INCREF(item);    PyList_SetItem(list,1,PyInt_FromLong(0L));    PyObject_Print(item,stdout,0);    Py_DECREF(item);}
    这是一个真实的故事,旧版本的Python中多处包含这个问题,让guido花费大量时间研究 __del__() 为什么失败了。

    第二种情况的问题出现在多线程中的借用引用。一般来说,python中的多线程之间并不能互相影响对方,因为存在一个GIL。不过,这可能使用宏 Py_BEGIN_ALLOW_THREADS 来临时释放锁,最后通过宏 Py_END_ALLOW_THREADS 来再申请锁,这在IO调用时很常见,允许其他线程使用处理器而不是等待IO结束。很明显,下面的代码与前面的问题相同:

    voidbug(PyObject* list) {    PyObject* item=PyList_GetItem(list,0);    Py_BEGIN_ALLOW_THREADS    //一些IO阻塞调用    Py_END_ALLOW_THREADS    PyObject_Print(item,stdout,0); /*BUG*/}
    10.4   NULL指针
    一般来说,函数接受的参数并不希望你传递一个NULL指针进来,这会出错的。函数的返回对象引用返回NULL则代表发生了异常。这是Python的机制,毕竟,一个函数如果执行出错了,那么也没有必要多解释了,浪费时间。(注:彪悍的异常也不需要解释)

    最好的测试NULL的方法就是在代码里面,一个指针如果收到了NULL,例如 malloc() 或其他函数,则表示发生了异常。

    宏 Py_INCREF() 和 Py_DECREF() 并不检查NULL指针,不过还好, Py_XINCREF() 和 Py_XDECREF() 会检查。

    检查特定类型的宏,形如 Pytype_Check() 也不检查NULL指针,因为这个检查是多余的。

    C函数的调用机制确保传递的参数列表(也就是args参数)用不为NULL,事实上,它总是一个tuple。

    而把NULL扔到Python用户那里可就是一个非常严重的错误了。

    11   使用C++编写扩展
    有时候需要用C++编写Python扩展模块。不过有一些严格的限制。如果Python解释器的主函数是使用C编译器编译和连接的,那么全局和静态对象的构造函数将无法使用。而主函数使用C++编译器时则不会有这个问题。被Python调用的函数,特别是模块初始化函数,必须声明为 extern “C” 。没有必要在Python头文件中使用 extern “C” 因为在使用C++编译器时会自动加上 __cplusplus 这个定义,而一般的C++编译器一般都会设置这个符号。

    12   提供给其他模块以C API
    很多模块只是提供给Python使用的函数和新类型,但是偶尔也有可能被其他扩展模块所调用。例如一个模块实现了 “collection” 类型,可以像list一样工作而没有顺序。有如标准Python中的list类型一样,提供的C接口可以让扩展模块创建和管理list,这个新的类型也需要有C函数以供其他扩展模块直接管理。

    初看这个功能可能以为很简单:只要写这些函数就行了(不需要声明为静态),提供适当的头文件,并注释C的API。当然,如果所有的扩展模块都是静态链接到Python解释器的话,这当然可以正常工作。但是当其他扩展模块是动态链接库时,定义在一个模块中的符号,可能对另外一个模块来说并不是可见的。而这个可见性又是依赖操作系统实现的,一些操作系统对Python解释器使用全局命名空间和所有的扩展模块(例如Windows),也有些系统则需要明确的声明模块的导出符号表(AIX就是个例子),或者提供一个不同策略的选择(大多数的Unices)。即便这些符号是全局可见的,拥有函数的模块,也可能尚未载入。

    为了可移植性,不要奢望任何符号会对外可见。这意味着模块中所有的符号都声明为 static ,除了模块的初始化函数以外,这也是为了避免各个扩展模块之间的符号名称冲突。这也意味着必须以其他方式导出扩展模块的符号。

    Python提供了一种特殊的机制,以便在扩展模块间传递C级别的信息(指针): CObject 。一个CObject是一个Python的数据类型,存储了任意类型指针(void*)。CObject可以只通过C API来创建和存取,但是却可以像其他Python对象那样来传递。在特别的情况下,他们可以被赋予一个扩展模块命名空间内的名字。其他扩展模块随后可以导入这个模块,获取这个名字的值,然后得到CObject中保存的指针。

    通过CObject有很多种方式导出扩展模块的C API。每个名字都可以得到他自己的CObject,或者可以把所有的导出C API放在一个CObject指定的数组中来发布。所以可以有很多种方法导出C API。

    如下的示例代码展示了把大部分的重负载任务交给扩展模块,作为一个很普通的扩展模块的例子。他保存了所有的C API的指针到一个数组中,而这个数组的指针存储在CObject中。对应的头文件提供了一个宏以管理导入模块和获取C API的指针,客户端模块只需要在存取C API之前执行这个宏就可以了。

    这个导出模块是修改自1.1节的spam模块。函数 spam.system() 并不是直接调用C库的函数 system() ,而是调用 PySpam_System() ,提供了更加复杂的功能。这个函数 PySpam_System() 同样导出供其他扩展模块使用。

    函数 PySpam_System() 是一个纯C函数,声明为static如下:

    static intPySpam_System(const char* command) {    return system(command);}
    函数 spam_system() 做了细小的修改:

    static PyObject*spam_system(PyObject* self, PyObject* args) {    const char* command;    int sts;    if (!PyArg_ParseTuple(args,"s",&command))        return NULL;    sts=PySpam_System(command);    return Py_BuildValue("i",sts);}
    在模块的头部加上如下行:

    #include "Python.h"
    另外两行需要添加的是:

    #define SPAM_MODULE#include "spammodule.h"
    这个宏定义是告诉头文件需要作为导出模块,而不是客户端模块。最终模块的初始化函数必须管理初始化C API指针数组的初始化:

    PyMODINIT_FUNCinitspam(void){    PyObject *m;    static void *PySpam_API[PySpam_API_pointers];    PyObject *c_api_object;    m = Py_InitModule("spam", SpamMethods);    if (m == NULL)        return;    /* Initialize the C API pointer array */    PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;    /* Create a CObject containing the API pointer array's address */    c_api_object = PyCObject_FromVoidPtr((void *)PySpam_API, NULL);    if (c_api_object != NULL)        PyModule_AddObject(m, "_C_API", c_api_object);}
    注意 PySpam_API 声明为static,否则 initspam() 函数执行之后,指针数组就消失了。

    大部分的工作还是在头文件 spammodule.h 中,如下:

    #ifndef Py_SPAMMODULE_H#define Py_SPAMMODULE_H#ifdef __cplusplusextern "C" {#endif/* Header file for spammodule *//* C API functions */#define PySpam_System_NUM 0#define PySpam_System_RETURN int#define PySpam_System_PROTO (const char *command)/* Total number of C API pointers */#define PySpam_API_pointers 1#ifdef SPAM_MODULE/* This section is used when compiling spammodule.c */static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;#else/* This section is used in modules that use spammodule's API */static void **PySpam_API;#define PySpam_System  (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])/* Return -1 and set exception on error, 0 on success. */static intimport_spam(void){    PyObject *module = PyImport_ImportModule("spam");    if (module != NULL) {        PyObject *c_api_object = PyObject_GetAttrString(module, "_C_API");        if (c_api_object == NULL)            return -1;        if (PyCObject_Check(c_api_object))            PySpam_API = (void **)PyCObject_AsVoidPtr(c_api_object);        Py_DECREF(c_api_object);    }    return 0;}#endif#ifdef __cplusplus}#endif#endif /* !defined(Py_SPAMMODULE_H) */
    想要调用 PySpam_System() 的客户端模块必须在初始化函数中调用 import_spam() 以初始化导出扩展模块:

    PyMODINIT_FUNCinitclient(void) {    PyObject* m;    m=Py_InitModule("client",ClientMethods);    if (m==NULL)        return;    if (import_spam()<0)        return;    /*其他初始化语句*/}
    这样做的缺点是 spammodule.h 有点复杂。不过这种结构却可以方便的用于其他导出函数,所以学着用一次也就好了。

    最后需要提及的是CObject提供的一些附加函数,用于CObject指定的内存块的分配和释放。详细信息可以参考Python的C API参考手册的CObject一节,和CObject的实现,参考文件 Include/cobject.h 和 Objects/cobject.c 。


     
    -
     
     
    来顶一下
     
    返回首页
     
     

    发表评论 共有0条评论
    用户名:  密码: 
    验证码:     匿名发表
         
     
     
     
    相关文章
    PHP中通过Web执行C/C++应用程序
    如何在Ubuntu 7.10上实现C/C++开
    c/c++中结构体(struct)知识点强化
    c/c++中结构体的入门教程
    C/C++中命令行参数的原理
    C/C++中利用空指针(NULL),提高程
    c/c++中的字符指针数组,指向指针
    c/c++中字符串常量的不相等性,以
    C/C++中数组和指针类型的关系的入
    C/C++中枚举类型(enum)的入门教程
     
    栏目更新
    C预编译中关于字节对齐的问题
    getopt(3)手冊頁翻譯及其補充
    linux C程序中获取shell脚本
    编辑数值金额成中文金额
    蚁群算法小程序用C/C++语言实
    假币问题--C++解决方案
    C/C++ 简单的多线程编程
    调用C++复制构造函数和拷贝构
    用好c++的const 关键字
    C标准类型的长度bytes
     
    栏目热门
    C#发送Email邮件方法总结
    c语言static与extern的用法
    VC++(Ctime日期函数)应用
    Windows/Linux下配置Eclipse
    C/C++ 简单的多线程编程
    C/C++对文件操作
    c++二叉树实现源代码
    struct的初始化,拷贝及指针
    typedef struct和struct的区
    详细解析C++虚函数表
     
     
    站内搜索:     Linux频道 下载频道 图库 商品 嵌入式频道   高级搜索
     
    网站首页 | 栏目导航 | 服务条款 | 广告服务 | 联系我们 | 网站大全 | 免责声明 | 返回顶部
    Copyright ? 2007-2008 xxlinux.com, All rights reserved.
    Powered by linux联盟 京ICP备05012402号
     
     

    展开全文
  • 如题,最近一段一直在搞工控类项目的上位机调度软件开发,由于扫描模块是余博用C++写的, 所以涉及到混合编程的问题了。  C#调用C++DLL的方法网上都有,把DLL放进exe的生成目录内,然后引用System.Runtime....

    更新部分直接看文章最后!


            

    如题,最近一段一直在搞工控类项目的上位机调度软件开发,由于扫描模块是余博用C++写的, 所以涉及到混合编程的问题了。

            C#调用C++DLL的方法网上都有,把DLL放进exe的生成目录内,然后引用System.Runtime.InteropServices命名空间,然后在主类内部加上外部引用声明就好了。

            [DllImport("CLMS511Data_MFC.dll", EntryPoint = "CLMS511Data", CallingConvention = CallingConvention.Cdecl)]
            public static extern void clms511data(ref Int32 m,ref Int32 n);

    具体声明如上,EntryPoint是DLL内对应的方法名,下面的方法命是自己重命名的,这个随意,后面调用就用这个名字。修饰符public可以换别的或去掉,其他照写吧。

            重点就是在这里,dll内的方法根据接口和余博的描述看,无返回,但接受两个Int32型数组指针,方法格式为大致为:

    void function(int32* a,int32* b)

            C++DLL的部分功能就是读取两个数组指针,输出至文本文档,修改数组内容(第一个数组每个+2,第二个数组每个+5)。

            而我的C#主函数流程很简单:初始化数组,调用DLL方法将数组作为参数传递,然后输出两个数组各自最后的元素至textBox1。

            以上有两个输出,做个记号。

            现在说说我的程序对接口的要求,不关只是传递数组内容,而且最好是能共享内存区块,这样dll负责写入数据,主程序读就好了,不需要主程序与dll频繁通信,共享内存就好了。学过C/C++的同学都知道,这对于C来说无非就是传个指针,规定数据类型和长度就好了。但就这个指针对于C#来说却不是那么容易的。C#是托管代码,依附于.Net,对机器内存直接操作不是那么方便的。

     

            当然,网上有各种解决方法,但大都是一长串代码,对于C#不是很精通的人来说理解起来还是有困难的,理解的不好,用起来就容易出bug。最有代表性的一种方法就是下面全部代码中被我注释掉的部分,利用intPtr型数据和Marshal类。intPtr这个看官方中文api说明是c#的指针,而Marshal类包含开辟内存的方法,所以声明一个intPtr类并让它指向Marshal.AllocHGlobal()返回的对象

    intPtr px=Marshal.AllocHGlobal(400)//400为100维int32数组所占字节数

    然后将dll方法的声明和调用的参数改为ref intptr型。这个方法不报错,但是输出的结果很有意思,两个数组前面5个数据都是错误的无意义数据,从第6个数据开始输出数组的第一个元素,输出到数组第95个数据为止。迄今没搞明白原因是什么,有知道的大神留个盐,不甚感激。

            我用的方法网上也有端倪,就是有关混合编程数据类型对照的问题。C需要指针的时候你传什么过去呢?对于数组,有人说直接传数组名啊,比如我定义的int32[] x,直接ref x就好了啊,其实这个方法你试过就知道不行,你传过去这个数组,你再访问这个数组就会报索引越界错误。

            解决办法是我拍脑袋想到的:纯粹试一试,因为想到C++读数组或指针其实都是数组第一个元素的地址,所以想到直接传x[0]过去。万万没想到,居然出奇的好用,dll里输出的就是我数组的结果,然后在dll里修改了之后,程序主体输出发现数组被改变了,证实确实是达到了共享内存的目的(至少看起来是,内部实现是不是不好说)。

            此处只是做实验的程序demo,数据类型理论上可以改为double等等,二维数组也简单没测试,但其实两边都把二维数组处理成一维就ok了呀。

            总结起来就一句话:对于C/C++需要的指针类型的形参,dll的方法声明里用ref 类型 x,调用的实参用ref a[0]就好了,a就是你C#里的数组。

            全部C#代码如下,C++功能看上面:

    using System;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    
    namespace WindowsFormsApp01
    {
        public partial class Form1 : Form
        {
    
            [DllImport("CLMS511Data_MFC.dll", EntryPoint = "CLMS511Data", CallingConvention = CallingConvention.Cdecl)]
            public static extern void clms511data(ref Int32 m,ref Int32 n);
    
            public Int32[] x = new Int32[100];
            public Int32[] time = new Int32[100];
            //IntPtr px = Marshal.AllocHGlobal(400);
            //IntPtr ptime = Marshal.AllocHGlobal(400);
            
            public Form1()
            {
                InitializeComponent();
                int i = 0;
                while (i < 100)
                {
                    x[i] = i * 2;
                    time[i] = i * 3;
                    i++;
                }
                //Marshal.Copy(x,0,px,100);
                //Marshal.Copy(time, 0, ptime, 100);
    
            }
    
    
    
            private void Form1_Load(object sender, EventArgs e)
            {
                clms511data(ref x[0], ref time[0]);
                textBox1.Text = x[99].ToString()+"    "+time[99].ToString();
            }
        }
    }

            以上有两个输出,第一个输出由调用的dll完成,文本文档内容无误,最后的元素分别为198和297;另一个由C#程序主体完成,textBox1内容为200和302第二次输出的结果可以看出数组元素被改变了,这里就不贴图了。


    更新部分:经实践,本文中已ref或out引用方式直接传数组首地址的方法在程序刚开始运行时确实可行,可随着程序后续的继续运行,特别是在某些关键操作之后,发出的指针在接收方会发生偏移或改变,原因未知(猜测是由C#的内存分配导致,C#的数组地址是会在某些与内存有关的操作后移动的,这种移动对C#程序不可见,但C++端按原固定地址取读或写数据就会发生错误。比较正确的方法应该是:1.用IntPtr类和Marshal.AllocHGlobal()方法开辟非托管内存给指针,这种非托管内存形成的指针与平台无关,不会移动;2.将C#内的数据与指针所指内存数据利用Marshal.Copy()方法进行数据交互;3.由于是非托管内存,C#垃圾回收不会动它,因此尽量在用完后利用Marshal.FresHGlobal()释放掉内存块。需要注意的是,IntPtr实例本身就是指针,可以直接作为实参传递给方法,无需再加ref或out,我此前用IntPtr就是在此跌了跟头导致走了很多弯路,所以才有本文的传递数组首元素的不完全方法。

    展开全文
  • 谈谈App混合开发

    千次阅读 2017-12-22 09:27:44
    混合开发的App(Hybrid App)就是在一个App中内嵌一个轻量级的浏览器,一部分原生的功能改为Html 5来开发,这部分功能不仅能够在不升级App的情况下动态更新,而且可以在Android或iOS的App上同时运行,让用户的体验更...
  • 一、 名词解释抽象 封装 消息【问题解答】面向对象方法中的 抽象是指 对具体问题(对象)进 行概括,抽出一类对象的公共性质并加以描述的过 程。面向对象方法中的 封装就是 把抽象出来的对象的 属性和行为结合成一个...
  • C++面向对象基础

    万次阅读 多人点赞 2018-05-20 12:40:59
    面向对象基础面向对象三大特性封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。封装可以使得代码模块化。优点:确保用户代码不会无意间破坏封装对象的状态被封装的类的具体实现细节可以随时改变,而无须...
  • 面向对象:对函数进行分类和封装,让开发“更快更好更强…” 面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,即:将之前实现的代码块复制到现需功能处。 ...
  • 文章目录golang 混合写屏障插入写屏障删除写屏障混合写屏障golang 混合写屏障实现源码文件写屏障代码触发点`runtime·gcWriteBarrier``wbBufFlush`置灰暂停 goroutine,栈扫描`scanstack`抢占调度`newstack`总结 ...
  • 本节意在使你理解面向对象的编程范式。 更多精彩请阅读 东陆之滇的csdn博客:http://blog.csdn.net/zixiao217本节学习目标 领会面向对象编程范式和结构式编程的不同之处 了解对象的关键特性 了解源于面向对象程序...
  • 高斯混合模型

    千次阅读 2018-09-17 21:54:31
    理论上,它仅当在近似状态下可以恢复正确的分量数(即如果有大量数据可用,并且假设这些数据实际上是一个混合高斯模型独立同分布 生成的)。注意:使用  变分贝叶斯高斯混合  可以避免高斯混合模型中分量数目的...
  • AWS S3(对象存储)基本操作

    万次阅读 热门讨论 2018-06-14 20:28:43
    1 AWS S3对象存储 1.1 基本概念 1.1.1 存储桶(bucket) 1.1.2 对象 1.1.3 对象标签(tag) 1.2 S3存储类型 2 使用 2.1 安装AWS SDK 2.2 编写基础配置文件 3 基础功能 3.1 桶操作 3.1.1 创建桶 3.1.2 桶的生命周期...
  • OpenGL Alpha混合

    千次阅读 2010-02-26 17:28:00
    原 文:Lesson 8: Blending 译 者:CKER OpenGL中的绝大多数特效都与某些类型的(色彩)混合有关。混色的定义为,将某个象素的颜色和已绘制在屏幕上与其对应的象素颜色相互结合。至于如何结合这两个颜色则依赖于...
  • 面向对象分析设计

    千次阅读 2013-03-27 17:05:59
    OO:建立对象的思维方式,对面向对象思想和理论有进一步的理解 UML:能够熟练地使用UML表达面向对象的设计思想 Model:运用对象技术的一般原则和模式进行应用系统的分析和设计建模 目录: 一、开篇亮剑...
  • 如何进行混合推荐,要考虑两个问题:基础的推荐理论框架和系统的混合设计,也就是组合两个以上算法的方法。 为什么推荐系统需要给出解释:卖家想要宣传特定的产品,而买家则比较关心做出正确的购买决策。解释是一项...
  • 用Boost.Python构建混合系统

    千次阅读 2008-05-29 13:12:00
    Building Hybrid Systems with Boost.Python用Boost.Python构建混合系统 Author: David Abrahams Contact: dave@
  • 功能描述:英文,数字和中文混合的彩色验证码是一种比较安全的验证码,虽然这样的验证码会给用户输入带来不便,但对于保障用户账号的安全还是值得的。本实例介绍实现英文,数字和中文混合验证码的彩色验证码的方法,...
  • 图的分类 : 图分为结构行为图 和动态行为图, 结构行为图包括类图,对象图,用例图,组件图,配置图; 动态行为图 包括状态图,活动图,时序图,协作图; 静态图概念 : 类图, 对象图, 包图是静态图; 静态图显示系统的静态
  • 混合 DLL 加载问题

    千次阅读 2007-07-18 10:38:00
    作者:Scott CurrieMicrosoft Corporation 2003 年 3 月 摘要:透过 Visual C++® .NET 和 Visual C++ .NET 2003 编译程序建立且使用混合 DLL (原生和 Microsoft Intermediate Language (MSIL) DLL 的组合) 的应用...
  • C++面相对象三大特性

    千次阅读 2014-03-30 18:44:35
    封装 【封装复杂,对外留出简单接口】 封装是在设计类的一个基本原理,是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与对数据进行的操作进行有机的结合,...1、 这个类是哪个对象的抽象
  • 目录scala、java双向转化通过...目前开发语言众多,在开发中,可能会遇到Java,Scala混合系统,但因java,scala中的集合对象不一致,会致使在开发中遇到很多问题,得需要两种语言集合的互相转换。Scala从2.8.1开始引...
  • OpenGL教程之Alpha混合

    2010-08-05 22:05:00
    OpenGL中的绝大多数特效都与某些类型的(色彩)混合有关。混色的定义为,将某个象素的颜色和已绘制在屏幕上与其对应的象素颜色相互结合。至于如何结合这两个颜色则依赖于颜色的alpha通道的分量值,以及/或者所...
  • 这里首先要构造一个对象的类,为了简单,我们就用两属性,定义一个UserDO这样一个类,描述如下: public class UserDO { protected String name; protected String email; public UserDO() {} public...
  • SWIG - 同Java的混合编程

    千次阅读 2014-12-02 19:15:50
    同Java的混合编程 - SWIG 最后更新日期:2014-04-20 阅读前提:推荐已经阅读《同C#的混合编程_SWIG入门》, 有Eclipse下编写JavaProject的经验。 作者: Kagula 环境:Windows 8.1 64bit(英文版)、VisualStudio 2013 ...
  • 混合使用Objective-C,C++和Objective-C++

    万次阅读 热门讨论 2012-09-02 23:42:59
    写了一篇关于混编的文章,结果却出乎意料的成为Google搜索中关于Objective-C++的最靠前的结果之一。 后来,Apple将基于LLVM的clang做为主选编译器。其作用之一就是可以保证Objective-C的演化,而GCC的进化却太...
  • Matlab与VC混合编程之一

    千次阅读 2013-07-01 23:59:38
    Matlab与VC混合编程之一 1. 问题的提出,为什么需要用Matlab与VC混合编程 做工程项目时用的是VC,经常会用到科学计算的相关算法,比如矩阵求逆,求行列式,求微分方程组初值问题的数值解等问题。如果自己去编写相关...
  • 人机混合智能的哲学思考

    千次阅读 2017-11-30 00:00:00
    虽然由于教士专门负责让你们记住,推理现象并非像道德现象那样为你们所熟知,但是,如果你们关注推理现象,你们可以很容易看到,一个得出理性结论的人不仅认为它是真的,而且认为每一类似情况下的推理同样正确。...
  • 来源 |奇伢云存储责编 | 晋兆雨图片来源 | CSDN 下载自视觉中国大纲插入写屏障删除写屏障混合写屏障golang 混合写屏障实现源码文件写屏障代码触发点对象置灰暂停 gorout...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 55,554
精华内容 22,221
关键字:

关于混合对象描述正确的是