新一代脚本语言引擎Cx -- 应用之AutoCAD二次开发 (2)

来源:互联网 发布:终极麦凯伦数据 编辑:程序博客网 时间:2024/05/17 09:06

 新一代脚本语言引擎Cx -- 应用之AutoCAD二次开发 (2)

            tongshou@gmail.com

13.    实时可控的动态数据处理方式,高级指针、GC

14.    内建大量数据类型及处理方法

15.    支持超高精度整数和超高精度实数

16.     类定义中的“简化型”多重继承、运算符重载、仿函数

17.     模块化(#Package)编程

18.     参考(Reference)/映射(Refect)型 变量和数据

19.     传址参数、署名参数(缺省参数)的函数定义

20.     参数包(Valist)、闭包(Closure)、子函数

21.     窗口函数的消息处理、事件反应、虚拟调用

22.     方便编写键盘重设置(键盘宏)、成倍提高电脑操作速度

23.     支持小型2D-CAM系统,方便编写CNC加工的应用程序

24.     装载、命令行操作

25.     其他说明

--------------------------------

 

 

13.  实时可控的动态数据处理方式,高级指针、GC

         脚本语言编程,会牵涉大量动态内存的申请和释放、系统资源句柄的取得和关闭,在CAD里还有实体对象的取得和关闭,在多线程同步操作中同步锁的取得和释放,所有这些,都牵涉一个共同东西,就是“成对操作”。Cx采用自动 + 可选人工干预(编程)方式,事实上是一个全自动体系,不管是否有人工编程干预、或者人工干预不完整、有欠缺,或程序运行不完整,都不会引起内存泄漏、句柄没有关闭等问题,Cx有一个良好的自动善后处理机制。因此,Cx对程序员的要求不高、很人性化。看下面这段Cx程序(摘自前面的C:C01程序):

for (en in ss) {    acdbOpenObject(pEnt, en, AcDb::kForWrite);    pEnt->setRadius( pEnt->radius() * sc);}

 其实可以使用与ARX更接近的程序方式:

for (en in ss) {    acdbOpenObject(pEnt, en, AcDb::kForWrite);    pEnt->setRadius( pEnt->radius() * sc);    pEnt->close();}

在Cx里,pEnt->close()是可有可无的,也可以用另外一种方式代替:

pEnt  = NULL;

Cx内部会感知局部变量pEnt是否关闭,当pEnt要被新的数据覆盖、或在函数运行结束时作用域失效时,如果pEnt还没有关闭,Cx会懂得及时调用close()方法。

        为了简化、方便叙述,下面以处理动态内存为例。变量、或相应的数据结点都可以接纳动态数据,下面仅以变量进行说明。变量数值的更新、或变量作用域失效统称为“变量值失效”。如果没有变量接纳,这些动态数据属于游离态,在表达式运行结束时会自动释放。接纳动态数据的变量称为宿主变量,该变量既可以显式接纳动态数据:

 A = new  MyClass;

也可以隐式方式接纳:

 myFunc( 123,  new MyClass);

这里的动态产生的数据,是赋给了函数第二个参数对应的参数变量。

        当变量成为动态数据的宿主,该变量就拥有控制该动态数据的权力,当该变量值失效时,其拥有的动态数据会立即释放。

       与一般脚本语言不同,Cx的赋值语句:

B  =  A;

只是表示变量B是变量A的从属变量,B与A指向同一个动态数据,可以与A一样操作同一动态数据,但是B没有对动态数据的控制权,只是变量A的一个影子,如果B的变量值改变时,不会引起原先指向的动态数据的释放。变量A的变量值改变,比如

A= NULL;

动态数据则会马上释放,其影子变量B的值会自动变为NULL。可以说,B原先指向动态数据的指针不仅象C/C++的指针,而且是具有一定智能的“高级指针”。

        如果要让B同时拥有与A一样的对动态数据的控制权,必须:

B = A.__image();

这样,变量B也成为宿主变量。当动态数据拥有超过一个宿主变量时,其与宿主的关系很像COM对象,任何宿主变量值的失效,都不会引发动态数据的释放,只有最后的宿主变量值失效时,才会引发动态数据的自动、立即释放。因此,只有当A和B的变量值都失效,才会引发动态数据的释放。

         变量A可以放弃控制动态数据权力,让动态数据重新变成游离态,可以重新赋予其他变量:

C =  A.__waive();

这样,变量C成为控制动态数据的新宿主变量。变量A自动变为NULL。如果没有新的变量接收动态数据,那么在表达式运行结束时,动态数据会自行释放。

       赋值语句的操作顺序是从右到左,对于如下表达式

C  =  D = new myClass;


 变量D优于变量C先得到动态数据,D成为宿主变量,C只能成为影子变量。

        很多时候,尤其当动态数据的宿主变量在可控的程序作用域内,对动态数据的多重宿主引用是多余的,这个不仅会造成内存回收系统的负担,更可能会造成恶劣的循环引用,在一般脚本语言里会造成内存永久泄漏。Cx里会侦测到循环引用,并且会给出错误提示,因此不会有这方面的内存泄漏问题。是否需要多重宿主引用,很多时候程序员会比内存回收系统(GC)更有判断力,在这方面,Cx提供多一种选择,让Cx更具活力。

        有一种情况比较特殊,就是后面将会专门谈到的“闭包”。在闭包的产生过程,会出现宿主变量控制权的自动转移现象。

        Cx里对象的直接声明,事实上是隐形方式的new过程,与显式方式new没有区别,都是在堆里取得内存,最终必须调用delete释放内存,只是这个delete的调用,是由Cx核心引擎以不同方式自动完成。

        Cx没有一般脚本语言、JAVA等那样的集中处理内存的GC体系,不需要事先取得一大批内存储备。Cx 的内存new过程事实上与C/C++里的new过程没有什么差别,需要多少new多少。Cx里的GC系统,基本上是以变量结点为管理对象,比传统的计数方式拥有更多信息,让Cx的GC体系更智能。Cx不仅自动管理内存,而且还自动处理与内存有关的各种内存句柄等。

 

14.内建大量数据类型及处理方法

 

       Cx支持大量的数据类型:

基本类型:

    Void, nil(NULL), Bool

   char, short, int, __int64

   unsigned char, unsigned short, unsigned int, unsigned __int64

   BYTE, WORD, DWORD

   float, double(Real)

 

扩展类型:

   Type  (类型型),

   Err   (错误),

   Tab   (用于格式化输出)

   FILE* (文件句柄),

   LPDISPATCH, LPUNKNOWN, (用于ActiveX)

   VT_TRUE, VT_FALSE, VT_DATE, VT_DECIMAL, VT_ERROR (用于ActiveX)

   ARRAY (阵列),

   Complex  (虚数,如:123.4j),

   Currency (货币,如:$1.23),

   Vect2d, Vect3d (矢量),

   Date, DateTime (日期、时间),

   aStr(ANSI字符串), wStr(UNICODE字符串),

   BIG_NUM(超级整数),BIG_REAL(超级实数),

   Ref_dat,Ref_var(反射数据,反射变量)

   pVar, (变量地址)

   Bfunc, Efunc, Tfunc, Ufunc (各种函数地址)

   VARIANT, (主要用于ActiveX的“泛型”)

  

   Memo, MemoPool (内存池),

   Regex,wRegex,CMatch,wCMatch(用于正则表达式)

   Lambda,

   Formula,  (格式化表达式)

   __Callback(回调)

   __Typedef (类型定义)

   __Enum    (枚举)

   CCode     (动态C编译)

   DLL       (DLL调用)

   VaList    (参数包,用作“闭包”)

   ENAME,PICKS (CAD里的实体名、选择集)

   等等。

 

数据链:

   Lint   (for int)

   Li64   (for __int64)

   Lreal  (for real)

   LaStr  (for aStr)

   LwStr  (for wStr)

   Lvect  (for Vect3d)

   Pline  (仿真PolyLine的数据链)

   Lmemo  (for Memo)

   Lmix   (混合数据链)

   List   (CAD里的 resbuf 数据链)

   Obj_Link (用户类的对象链)

   MFC_Link (MFC类的对象链)

 

树链:

   TREE

 

句柄(HANDLE):

   Cx目前支持数十种句柄:

   HACCEL, HANDLE, HBITMAP,HDC,。。。,HWND。

 

结构(struct/union)

   Cx目前支持数百种,主要方便调用Win32 API:

   DCB, MSG, POINT,FILETIME,RECT, SIZE,。。。

 

MFC、ARX:

   Cx目前支持数百个MFC和ARX的类,能够直接调用它们其数以万计的方法:

   CWnd,CFrameWnd, CButton, CBitmap, CDC, 。。。

   AcDbObject, AcDbEntity, AcGePoint3d,。。。

 

       可以看出,Cx内建很多类型的数据。Cx对数据的处理内、外有别,也就是,在内部记录的数据类型,与在表达式中展现的类型可能不一样,比如,内存中记录的 bool、char和short型数据,取出用于表达式计算,会自动升级到int型数据。

       Cx为各种类型的数据提供各种操作方法,其中各种句柄、结构、MFC类、ARX类的方法,与C/C++里的用法基本上时一样的。对于数据结构(struct/union),如果没有内建的,可以编程定义:

 

struct myABC {    int    i;    double r;    char   *name;    DWORD  n1 :  5;    DWORD  n2 : 11;    union {       int     ui;       double  ur;   } ux;};


还可以用宏 #align() 设置位对齐数,在缺省情况下的位对齐数为8。

       Cx提供的树类型TREE,内部实现主要是由AA树结合一个小型内存池,使用内存池是为了让TREE结构存储大量数据结点时不容易产生大量内存粹片。TREE型数据很好操作,首先生成TREE结构,格式为:

new TREE.key.vType([Accur]);


其中,

       Key   为键类型名,具体为:int, __int64, real, vect3d, aStr, wStr,Ename

       vType 为结点值类型,可以为很多类型名:int, array, LIST, CWnd,。。。

      Accur 为键值的精度,象键类型为real,vect3d,设置适合精度是很有用的。

 

下面以一个例子说明:

myTree = new TREE.int.real();


 构造的TREE结构,键类型为整数int,树结点值类型为实数real。

 往TREE中加key-Value对的方式:

myTree[8] = 1.23;

 操作树结点值很简单,可以把 myTree[8]看成一个普通数据结点:

myTree[8]++;myTree[8]+= 888;--myTree[8];


      如果事先没有加入键值,只要表达式中出现 myTree[8], 在操作之前,Cx会自动插入一个键值为8,结点值为缺省值0.0。如果要判断是否存在某个键值,比如2,用方法find():

myTree.find(2);


 如果不存在,返回 nil值,如果存在,返回TREE类型值。

        通过方法Sort(),可以对TREE结构里的键值进行排列:

myTree.Sort();


 这是缺省下的排序:从小到大,如果要从大到小排序,格式为:

myTree.Sort(TRUE);

排序后,可以按键值顺序输出键值和结点值:

for(p to myTree) {    printf(“key = %d, value= %lf\n”, p->Key, p->Value);}

可以不必从头到尾全部输出,从某个键值u开始,输出w个:

for (p to myTree.find(u), w) {    printf(“key = %d, value= %lf\n”, p->Key, p->Value);}


        现在谈谈数据链的一些操作,以人们比较熟悉的resbuf 数据链LIST为例进行说明。LIST可以用两种方法构造,一种是调用buildlist函数,用法与ADS本身提供的函数ads_buildlist几乎一样,只是Cx不需要以0作为结束类型,而且类型RTDXF0还可以用0 代替,例如:

L1 = buildlist(0, “CIRCLE”, 10, [1,2,0], 40, 10.23, 8, “OK”);


        这种方法生成的LIST,可以直接用于ssget之类的ADS函数的参数,作为“过滤器”。

        顺便说一下,ADS的函数中用到的点参数,Cx以矢量类型提供,分为2维和3维矢量:Vect2d,Vect3d,Cx把这类数据当成简单数据,可以在表达式中以传值方式传递,构造也很简单,用[]:

   [1];     // =>2 维:(1.0, 0.0)   [1,2];   // =>2 维: (1.0, 2.0)   [1,2,3]; // =>3 维: (1.0, 2.0, 3.0)


       构造LIST数据链的另外一个方法是使用 new,Cx特意为LIST构造提供操作符<、>分别用于构造LTLB、LTLE结点:

L2 = new LIST(0, 1, 2, <3, <4, 5>>, 6, 7, <8, 9>, 10);


生成如下LST数据链:

(0 1 2 (3 (4 5)) 6 7 (8 9) 10)

        Cx对数据的处理内、外有别,也就是,在内部记录的数据类型,与在表达式中展现的类型可能不一样,比如,内存中记录的 char型数据,取出用于表达式计算,会自动升级到int型数据。对于LIST,内部记录的当然是从头到尾一整条数据链,但是向表达式展现的可能是数据链的一部分,甚至可能没有,为NULL。Cx为LIST变量提供高级指针,可以指向任何主结点,LIST变量向外展现的LIST,就是从该主结点开始到末尾这部分,如果指针越界,不能指向有效结点处,那么向外展现空指针NULL。

L2 += 4;

 向外展现的LIST:

(6 7 (8 9) 10)

在操作:

L2 += 20;

 L2当前指针越界,向外展现的LIST为NULL.

 L2.first(TRUE);

让L2的指针重新指向LIST数据链的第一个主结点。让L2向外展现整条LIST:

(0 1 2 (3 (4 5)) 6 7 (8 9) 10)

下面表达式可以让LIST数据链局部倒序:

(L2 + 4).reverse();

 L2变成:

(0 1 2 (3 (4 5)) 10 (8 9) 7 6)

可以修改LIST中任何结点的数据:

(L2+3)[2].Value *= 100;

 L2变成:

(0 1 2 (3 (4 500)) 10 (8 9) 7 6)

其中 (L2+3)是取得指向第三序号主结点的指针:(3 (4 5)),[]操作会跨越RTLB和RTLE结点,取得第2序号有效数据(亚)结点:5。对该结点数值 *=100. 

       可以对数据结点赋予完全不类型的数据,比如:

L2[1] = “Hello”;

L2变成:

(0 “HELLO” 2 (3 (4 500)) 10 (8 9) 7 6)

       LIST数据链还有很多其他操作方法:append(),delete(),insert(),find(), join(), len(), max(), min(), plus(), scale(), sortBy(), sum(),

__waive()等。

 

15. 支持超高精度整数和超高精度实数

 

        Cx提供超高精度整数和高精度实数,两者的有效十进制字位数高达76位:

         BIG_NUM : -5.7896E+76 到5.7896E+76, 精度到1

         BIG_REAL: -5.7896E+38 到5.7896E+38,精度到小数点后37个0再1

 

         超级精度数的表达方式,是在普通数的后面加w,如:

        0x12FF567AB8900w;

        1234888888888888888888.5678901111119999999999999w;

 

    下面用三角函数sin()asin()验证计算的精度/误差:

      A = 0.5w;

     得:BIG_REAL: 0.5

 

      B = sin(A);

     得: BIG_REAL: 0.47942553860420300027328793521557138809

 

     C = asin(B);  //ArcSin

     得:BIG_REAL: 0.49999999999999999999999999999999999962

 

     A-C;

    得:BIG_REAL: -0.00000000000000000000000000000000000038

 

     A-C就是计算误差,极小吧。

 

 

    再举一例子:

     A = 0.5w;

    得:BIG_REAL: 0.5

 

     B = NthRoot(A, 99);  //99次方

    得:BIG_REAL: 0.99302296663237743952963163682042200843

 

     C = pow(B, 99);  // 99 幂次方

    得:BIG_REAL: 0.49999999999999999999999999999999999999

 

     A-C;

    得:BIG_REAL: 0.00000000000000000000000000000000000001

     这个A-C的误差值,同样极小吧。

 

        超高级精度数用8个普通整数共256 bit记录。在进行乘除运算时,为了进一步提高精度,会自动升级到更高精度空间384bit计算,最后返回256bit空间值。一些数值运算的函数,比如三角函数,只要传入超高精度参数,就会按超高精度方式运算,得到超高精度结果。 

        在表达式中如果与浮点数(float, double) 混合,超高精度数会自动转换到double数。可以让普通浮点数显式转为超高精度数,以保持表达式运算结果的超高精度。

  

16.     类定义中的“简化型”多重继承、运算符重载、仿函数

        C/C++中类定义的多重继承,复杂,不易掌握,这可能是导致包括Java在内的不少程序语言放弃支持的原因。Cx支持 一种”简化型”的多重继承,避免普通多重继承可能产生的复杂继承关系,又可以弥补单一继承可能存在的一些缺憾:

class myClass : ClassX, ClassA, ClassB{    //...(省略)};


       Cx定义class,遵循如下规则:

       1) 所有的继承, 都为公有继承 (因此不需要前置关键字public)。

       2)第一继承处的ClassX 没有什么要求,可为任意定义的class, 包括来自MFC中定义的class,比如 CFrameWnd, CWnd等。

       3)第二继承处及之后的ClassA、 ClassB有特定要求:这些class的定义不能继承于其他class,也不能是来自MFC的class,这里的class有点象接口 (interface),但是其使用会比接口来得简单。最多允许继承7个。

       4)Class里定义的数据成员、和成员函数,可以拥有public、protected或 Private属性,在缺省情况下为public,而不象C/C++中的private。因为很多情况下人们使用public情况最多。

       5)构造函数、析构函数的定义,无需前置关键字 def 和 function。这个与C/C++里类似。

       6)与C/C++一样,Cx的成员函数定义体可以整个定义在class里,也可以在class里仅仅声明成员函数头,然后在class外定义成员函数体。  

       7)定义函数、方法的前置关键字是def 和 function,建议:在class里用def,  而在class 外用 function.

       8)构造函数的定义可以重载,只要各自的参数个数不一样。

       9)构造函数的定义,允许在函数头调用基类(base class) 的构造函数。如:

  Class myCls: base baseCls  {      myCls(x, y, z) : baseCls(x, y) {。。。}      myCls(u,v)     : baseCls(u);     ~myCls() {}       //(省略)  };  function myCls :: myCls(u, v) : baseCls(u) {。。。}


        这里的基类可以包括MFC的类。 

      10)支持成员函数的虚拟化,只要在def之前再加上关键字virtual。

      11)支持成员数据的初始化。也就是,直接在变量声明处的初始化。

 

       Cx类的实例化过程,是先初始化基类和各层级派生类的成员变量,然后才调用各层级的构造函数,因此能够在构造函数中支持成员函数虚拟调用,而且支持成员变量的初始化。这些在VC++中是不支持的。

        Cx支持运算符重载和仿函数,因为Cx的函数名可以为任何字符,这为操作符的重载定义提供方便,不需要使用C/C++中的关键字operator,例如 (下面的obj为 obj = new myClass; )

def myClass:: '++i' () {…}; // ++obj;  时 调用此函数def myClass:: 'i++' () {…}; // obj++;  时 调用此函数def myClass:: '()' (x) {…}; // obj(2); 时 调用此函数 ,为仿函数调用

 

17.     模块化(#Package)编程

        Cx通过宏 #package进行名字空间定义,把程序模块化,这样可以显著降低程序命名的冲突,这对于脚本语言程序来说尤其重要,因为脚本程序的装载可以很随机,所装载的不同程序很可能来自不同程序设计者,如果没有名字空间定义,很容易造成程序命名的冲突,导致程序无法正常装载和运行。Cx还为名字空间提供附属名字,进一步防止名字空间本身名字的冲突。以方式 #package _end_结束模块定义。例如:

     #package myPack  [xxx : yyy]     public class myClass {          //…     };     public function  myFunc(x, y)     {            //…     }     public var x=888;     #package  _end_


 其中,myPack为模块名,[…]中的 xxx 为模块的附属名字,: 后的  yyy 为密码值。

        [xxx : yyy]为可选项,可有可无。如果在装载其他处的程序时出现同样的模块名,程序会比较附属名字,如果两者不同,程序会报措而停止装载,如果有密码值部分,同一台电脑第一次装载该模块时会出现要求输入密码的对话框,如果密码不同,程序无法装载。虽然Cx是脚本语言,不过它提供加密伪编译功能,因此对模块设计密码保护是有意义的。

        一个程序文件中可以定义多个模块,一个模块也可以定义在多个程序文件中,分多次或仅仅一部分装载。附属名字可以任意长,一般可以用设计者名字或所在公司名字,能有效防止命名冲突。附属名字对模块操作没有任何影响,因为程序对模块的操作,仅仅使用模块名。例如:

q = new myPack@myClass;myPack@myFunc(1, 2);

        通过宏#import可以在模块中插入其他模块,这样,对插入的模块中定义的调用,就不需要这些模块名字做前置了。例如:

#package PackB#import  myPack,otherPackfunction funcB(){    x = new myClass;  // 不需要前置 new myPack @}#package _end_

 


18.     参考(Reference)/映射(Refect)型 变量和数据

       Cx提供一种参考/映射变量(类似C/C++中声明的变量 int &x = y;),用于变量之间的数据同步、共享很有用,格式有两种,一种是用一个变量去映射另外一个变量:

B = ~ &^A;

       这里的操作符~&^实际上是两种结合:~ 与 &^,其中&^用于取得变量的地址。这样,对B的操作完全等效对A的操作,对A的操作,也完全等效对B的操作。

      另外一种是用变量去映射某个比较简单的数据,比如A = new INT[11],如果

B = ~ &A[8]; 

那么变量B就与A[8]元素完全相互映射。而且变量B只能接受整数数值。

 

19.     传址参数、署名参数(缺省参数)的函数定义

       Cx支持定义传址参数的函数: 

function myFunc(&A, &^B) {    A *= 10;    B = “Hello--” + B;}

上面函数myFunc的定义,参数A为数据传址,参数B为变量传址。A允许简单型数据,B允许任何数据。如果

X = 12; Y = 45;

 那么,

myFunc(X, Y);

 运行的结果是:

X 为 120

Y 为 “Hello--45”

如果,

X = 12.456;  Y = “ABC”;

 那么,

myFunc(X, Y); 

运行的结果是:

X 为 124.56

Y 为 “Hello--ABC”

       如果参数的类型要确定为某类型数据,必须按C/C++格式定义参数:

function myFunc(int x, double y){    //省略}


       对于参数比较多的函数,如果以署名参数(缺省参数)方式定义,调用起来时很方便的:

function myFunc (A:=11, B:=23.45, C:=”Hello”){    princ(“A= ”); princ(A); princ(“; ”);    princ(“B= “); princ(B); princ(“; ”);    princ(“C= “); princ(C); prinn();}

myFunc();

得到打印输出:

A= 11; B= 23.45; C= Hello

 myFunc(C=”XYZ”, A=89);

得到打印输出:

A= 89; B= 23.45; C= XYZ

 

20.     参数包(Valist)、闭包(Closure)、子函数

        正在运行的函数,可以通过内建函数thisParamNo()取得函数实际参数个数,通过内建函数ThisParam()取得各个参数的地址,对于可变参数的函数,这两函数很有用,在前面博文中有用例。Cx还提供内建函数ThisVaList(),用于获得当前运行函数的整个运行环境的参数包,还包括该运行函数的子函数表。Cx支持定义子函数,也称为内部函数,这个参数表可以被用来当“闭包”(Closure)使用,先看个简单的:

function myFuncA(A, B){    ///(省略)    return thisVaList();}Q = myFuncA(11,22);


Q得到的是VaList(”参数包”)对象,其内部包含函数myFunc()运行结束时参数A和参数B的状态值,这些参数的名称被保留下来,可以通过点操作各参数:

Q.A++;X = Q.B;

也可以通过[]操作:

Q[0]++;X=Q[1];


       再看一个比较复杂一些的:

function myMath(f1, f2){    var  result;    def funcA(u)    {        return result = f1(u);    }    def funcB(w)    {        return result = f2(w);    }    return ThisVaList();}myCal = myMath(&.sin(), &.cos());

myCal得到的也是VaList(”参数包”)对象,可以当成闭包使用:

myCal.funcA(1.23); 

返回0.942489,实际上就是sin(1.23)的运行结果。该结果还保存在myCal的变量result里:

myCal.result;

也返回0.942489。

        还可以把myCal.funcA赋予一个变量:

mySin = myCal.funcA; 

然后运行:

mySin(1.23);

是等效的,结果一样。

       调用子函数funcB:

myCal.funcB(1.23);

返回0.334238,是运行cos(1.23)的结果。

        可以看出,上面运行一次myMath()时,该函数的自变量f1,f2,被保留下来,存到myCal里,局部变量result也被保留到myCal里,而且它们的名称也被保留下来,通过点“.”可以直接操作它们。更重要的是,可以通过myMath定义的子函数操作闭包里的变量,这些子函数共享闭包里的所有变量。这个“闭包”的行为,很象普通类定义生成的对象。

        总之,函数的运行环境会复制闭包里,以同名、同类型方式,而且环境变量的宿主权会自动转移给相应的闭包里的变量,而其本身成为一种“反射变量”,如前所说,这种变量的操作行为,与其对应的变量完全一样,唯一不同的是,在函数运行结束时,“反射变量”的消失,不会引起其对应的动态数据的释放,此时的动态数据还保留在闭包里,由其相应的宿主变量控制着。

        很多脚本语言的闭包,表面上看,是以子函数地址方法返回得到,同样是子函数,为什么在不同时候返回的子函数地址(闭包)会不一样?这是有点令人费解的,显得有些神秘。事实上,闭包是当前函数“一次性”运行动态环境数据与子函数地址的组合、得到的综合数据块。在Cx里看到的是神奇、而不是神秘的“闭包”。

 

        Cx支持动态参数重构,格式为:

Va = new VaList(Name1:= val1 [, Name2: = val2] 。。。); 

得到也是“参数包”,与上面用函数ThisVaList()得到的参数包,是同一类的数据,只是缺省内部函数而已。其中,Name1, Name2,…为参数名称,Val1, val2,…为对应参数值。参数名称为可选项,可以不要。如果有名称,得到的参数包,可以用相应名称操作。例如,

myVaList = new VaList(X:=1, Y:=2);

得的参数包,可以用X和Y操作:

myVaList.X++;myVaList.Y = 123;

 

        通过内建函数CallFunction(),以该参数包数据做参数,调用上面定义普通用户定义的函数、内建函数、Lambda,甚至调用VaList闭包。比如有个用户定义的函数:

fnction myFunc(a, b){    //省略} CallFunction(&.myFunc(), myVaList);

 其与直接调用是完全等效的:

myfunc(1, 2);

 

21.     窗口函数的消息处理、事件反应、虚拟调用

 

       在前面的博文实例中已经有程序展示窗口函数的消息处理方式。Cx都窗口消息的处理,实际上是提供控制窗口函数中的虚拟函数WindowProc,对于不同的标准消息,Cx提供多达一百多种的相应名称,虚拟调用用户定义的类函数(方法),如果没有找到相应名称、参数个数的方法,Cx试图索寻、调用通用方法OnOtherMsg(),用户方法运行结束,如果没有返回值或返回FALSE,Cx会让窗口消息继续传到MFC里,让MFC调用相应函数继续运行下去。

        对于事件,Cx提供不同类型事件相应的虚拟用户函数名称:

    event_method

    event_propRequest

    event_propChanged

    event_propDSCNotify

 

       事件参数以DISPPARAMS结构提供,同样可以调用内建函数CallFunction(),接受这些参数,调用用户函数,处理相应事件。

       Cx属于泛型语言,区分定义的虚拟函数,只能用函数名称和参数个数。

       下面的示例,展示窗口函数如何扑捉ActiveX构件产生的事件,通过方法Event_method()得到参数nID, dispId, pa,其中的nID是m_pWnd. CreateControl()时设置的,dispId是Flash动画的ActiveX构件里面设置的,pa则是对应事件的参数阵列,为DISPPARAMS结构,可以为内建函数CallFunction()所使用。

#package myDemo//////////////////////////////////////////////////////////////////#include "windows.ch"//////////////////////////////////////////////////////////////////public function [C:C14](){    msg = new MSG;    myF = new CMyForm;    myF.Create(NULL, "Demo", WS_OVERLAPPEDWINDOW, NULL);    while (GetMessage(msg) && myF.m_process) {        DispatchMessage(msg);    }}//////////////////////////////////////////////////////////////////class CMyForm : CFrameWnd{    def  OnNcDestroy     ();    def  OnCreateClient  (pcs, pcc);    def  event_method    (nID, dispId, pa);    def  event_150       (s1, s2);    CWnd         m_pWnd;    LPDISPATCH   m_disp;    int          m_process = TRUE;    static TREE  m_EventMap= new Tree.int.uFunc();    static aSTR  m_strFileName = findfile("myFlash.swf");    static function InitEventMap()    {        m_EventMap[xInt(88, 150)] = &.event_150();    }};//////////////////////////////////////////////////////////////////CMyForm::InitEventMap();//////////////////////////////////////////////////////////////////function CMyForm::event_method(nID, dispId, pa){    var pT= m_EventMap.find( xInt(nID, dispId));    if (pT) return CallFunction(pT->value, pa);}//////////////////////////////////////////////////////////////////function CMyForm::event_150(s1, s2){    if (s1 == L"bt" && s2 == L"enter") {        MessageBox("Hello", "Flash Demo", 0);    } else if (s1 == L"quit") {        if (MessageBox("Are you sure to quit?", "Quit", 4) == 6) {            m_process = FALSE;        }    }    return TRUE;}//////////////////////////////////////////////////////////////////function CMyForm::OnNcDestroy(){    m_process = FALSE;}//////////////////////////////////////////////////////////////////function CMyForm::OnCreateClient(pcs, pcc){    .RECT  x_rec;    w = 800;    h = 600;    SetWindowPos(0, 80, 60, w, h, 0);    ShowWindow(SW_NORMAL);    UpdateWindow();    x_rec.left   = 20;    x_rec.right  = w-50;    x_rec.top    = 20;    x_rec.bottom = h-50;    sty = WS_CHILD | WS_VISIBLE;    res = m_pWnd.CreateControl("ShockwaveFlash.ShockwaveFlash",          "FlashDemo", sty, x_rec, this, 88);    m_disp = m_pWnd.GetControlUnknown().Dispatch();    m_pWnd.MoveWindow(x_rec, TRUE);    res = m_disp.LoadMovie(0, m_strFileName);    m_disp.Play();}#package _end_//////////////////////////////////////////////////////////////////


  22.     方便编写键盘重设置(键盘宏)、成倍提高电脑操作速度 

       AutoDesk中国论坛上看到版主Hoomoo的一篇帖子,其中谈到:

。。。

下面再说说CAD的操作习惯:
1、左手键盘
2、右手鼠标
        这是多数人在使用CAD时候的标准姿势,但是操作上面也有很大差别,首先要考虑尽量减少右手的工作量,相对增加左手的工作量。
所以说左手微操作是提高CAD使用速度的先决条件。满屏幕找按钮的工作方法,不是CAD专业人员的工作方法。
。。。

        非常赞同这段话,因为以多年操作CAD的经验,我有非常深刻的体会。在Windows时代,为了简化、直观操作,严重依赖鼠标,人们的左手快退化了。很多时候,操作软件的速度明显不如DOS时代,一个最明显的例子就是 Norton Commander。早在DOS时代,AutoCAD R10,我就使用DOS方式重设置键盘,结合LISP语言程序,可以轻松操作CAD,而且效率很高。到了Window时代,设置键盘就没有那么容易了。ZWCAD特意提供一个键盘重设置的窗口,而且支持ALT-组合键,这是个难得的进步。Windows时代,ALT-组合键被标准化、广泛用于菜单操作,而菜单操作不是最有效率的操作方式。

         Cx提供一种编程方式,比窗口设置更快捷、灵活,只要一个命令,就可以完成设置、或转换设置。下面展示键盘重设置的一个Cx程序,其中命令函数C:sKey()为设置键盘,C:dKey()为取消键盘设置。变量alt用于设置ALT-组合键,变量Ctr用于设置CTR-组合键,key则用于设置功能键,当然也可以设置普通键,只是这样做的后果很严重(^_^)。结合一些其他程序,这样的键盘设置,会产生神奇的效果,比如ALT-Q的组合,可以产生四种不同效果。

#package  myDemo#include "windows.ch"//////////////////////////////////////////////////////////////////public function [C:sKey](){    alt = new List(        <'W', "'zoom w "    >,        <'S', "'zoom p "    >,        <'1', "cen,ins "    >,        <'2', "mid "        >,        <'3', "qua "        >,        <'5', "chprop "     >,        <'Q', "end,int,nod ">,        <'A', "nea "        >,        <'E', "per "        >,        <'D', "tan "        >,        <'C', "cross "      >,        <'X', "window "     >    );    ctr = new List(        <VK_F1, "LINE "     >,  // test only        <'A',   "CIRCLE "   >    );    key = new List(        <VK_F4,    "'view r ">, // test only        <VK_F5,    "'pan "   >    );    RegAcadHotKey (alt, ctr, key);}//////////////////////////////////////////////////////////////////public function [C:dKey](){    DelAcadHotKey();}#package  _end_//////////////////////////////////////////////////////////////////

 

  23.   支持小型2D-CAM系统,方便编写CNC加工的应用程序 

        Cx为CAD实现CAM提供一组函数,方便编写线切割、CNC加工等应用程序。这些程序会比较智能化。这里举一个例子,见上图,这是个电子线路板(PCB)冲模底部漏料板局部图,那些白圆圈是废料,必须斜面加工,让这些废料漏入那些紫色空洞里。要求用CNC斜面加工。 

        这些斜面实际上是比较随机的凹凸曲面,用UG、PRO_E等三维软件根本使不上劲,效率很低,等您花数个小时造型出来,还没有实施CNC加工,用手工机加工一、两小时早就加工完成了。

        用Cx进行3D几何虚拟。从2D的DWG图里取得相关数据,再从这些数据计算出3D-CNC加工路径。下面列出演示程序,上图中的黄色箭头是Cx程序根据2D DWG图计算后得到的相关数据的几何展示,并且把箭头起点和终点数据按顺序记录到一个文件里,根据这些数据,再推算出3D-CNC加工程序非常容易了。这里有两点要求:1)紫色空洞里的圆废料显然不需要加工,2)斜面加工应该很有秩序,不应该乱跳。

        运行该Cx程序仅仅需要几秒钟,结合一些手工修正,十几二十分钟内可以出CNC加工程序。

        CAM功能中,一个重要函数PLineFrom()以CAD实体名做参数,得到用POLYLINE方式描述实体几何参数的PLINE型数据链,然后基于该数据链进行各种几何计算。比如,如果CAD的实体是一个闭合的曲线POLYLINE,以某点A作为基点,绕该曲线的PLINE 数据链求扫描角,如果扫描角度是2PI,那么可以肯定A点在闭合的POLYLINE里,如果是0,那么可以肯定A点在闭合的POLYLINE外。

      Cx为PLINE型数据链提供数十种方法,其中Offset(B)为求偏置B值后的几何描述PLINE,如何获得的PLINE的结点数,与原有的PLINE不同,那么可以肯定,以原有的PLINE作为CNC、或线切割编程轨迹线,进行偏置B值加工操作(G41/G42?),机器加工过程会报警,无法完整运行结束。

       下面程序中的SaveAcadSysvarSym()和 SaveSysvar()分别用于存储CAD系统变量和Cx的系统变量,一旦相应的数据变量失效,系统变量会恢复到数据变量记录时的状态。

       下面程序中有用到“闭包”(见子函数SortList()),让闭包用于LIST数据链排序。先让闭包记录需要在哪个亚结点(subPos)进行排序。

 

/////////////////////////////////////////////////////////////////////////////#define  SM_OFFSET     2.0#define  SM_FILENAME  "sm.cc"#define  SM_ACCUR      0.0001#define  MB_ICONHAND   0x00000010L#package myDemo/////////////////////////////////////////////////////////////////////////////public function [C:15](){    var  Vx;    var  PtNear = new VXPTNEAR;    def Invalid_pl(Vx) {        entmake(buildlist( 0, "LINE",10, [], 11, Vx.First()->Node.pt,                62, 6)         );        redraw();        MsgBox (NULL, "Existing Invalid (LW)POLYLINE.", "GM", MB_ICONHAND);    }    def SortList(subPos) {        def _Do(x, y) {            return x[subPos].Value - y[subPos].Value;        }        return ThisVaList();    }    SortList_0 = SortList(0);    SortList_2 = SortList(2);    acadSys = SaveAcadSysvarSym(CMDECHO, DIMASZ, OSMODE);    casSys  = SaveSysvar();    filter = buildlist(0, "CIRCLE", 8, "HOLES,H,HS");    ss_cir = ssget(filter);    if (!ss_cir) return;    setvar("CMDECHO", 0);    setvar("DIMASZ",  1.2);    Setvar("OSMODE",  0);    $luprec = 3;     Tx     = new TREE.ENAME.LIST;    filter = buildlist(0, "LWPOLYLINE,POLYLINE,CIRCLE", 62, 6);    len    = ss_cir.len;    num    = 0;    for (en in ss_cir) {        if ($i % 10 ==0) {            printf("\r***** %6.2lf%% *****", ($i+1.0) /len *100);        }        acDbOpenObject(pEnt, en, AcDb::kforRead);        ptc = pEnt.Center().data;        pEnt = NULL;         Lpts = ptPolygon(ptc, 15);        command("zoom", "w", ptc +[-30, -30], ptc +[30, 30]);        ss_px = ssget("CP", Lpts, filter);        if (!ss_px) continue;        toNext = TRUE;        List = new List;        for (enj in ss_px) {            Vx  = PLineFrom(enj);            Vl  = Vx.Last();            Vx.Scale([1,1, 0]);            if (!equal(Vx->Node.pt, Vl->Node.pt, SM_ACCUR)) {                Vx++;                if (!equal(Vx->Node.pt, Vl->Node.pt, SM_ACCUR)) {                    Invalid_pl(Vx);                    return;                }                dir = (Vx.CornerAngle("R") < (PI - 0.1)) ? "R" : "L";            } else {                Ax = Vx.tangent();                pt = polar(Vx->Node.pt, Ax+ PI*0.5, 0.1);                Ax = Vx.PtSweepAngle(pt);                if (Ax == NULL) {                    Invalid_pl(Vx);                    return;                }                dir = (abs(Ax) > 0.1) ? "L" : "R";            }             Vx = Vx.Offset(SM_OFFSET, dir, INT_MAX, SM_ACCUR);            if (!Vx) {                toNext = FALSE;                break;            }            Ax = Vx.PtSweepAngle(ptc);            if (Ax && abs(Ax) > 0.1) {                toNext = FALSE;                break;            }            VxNear = Vx.PtNear(PtNear, ptc, INT_MAX, SM_ACCUR);            prev_len = PtNear.prev_len + VxNear.SegLen(INT_MAX, TRUE);            List.append(<PtNear.near_dist, enj, ptc, PtNear.near_pt, prev_len>);        }        if (!toNext || !List) continue;        num++;        List.to_sortBy(sortList_0);        Lsub = List.First().SubList;        Tx[Lsub[1].Value].append(<Lsub +2>);    }    prinn("\r***** 100.00% *****");    fw = fopen(SM_FILENAME, "w");    for(lnk to Tx.Sort()) {        for (p to lnk.Value.to_sortBy( sortList_2)) {            ptc     = p[0].Value;            pt_near = p[1].Value;            command("dim1", "leader", pt_near, ptc, ^C^C);            entmake( buildlist( 0, "TEXT", 1, itoa($i),               10, ptc, 40, 1.2, 41, 0.8, 62, 4)            );            redraw();            xout(fw, ptc, tab(20), ">>  ", pt_near, "\n");        }    }    prinn ("\nNum of Circles Associated : " +num);}/////////////////////////////////////////////////////////////////////////////#package  _end_/////////////////////////////////////////////////////////////////////////////


 

24.     装载、命令行操作 

       Cx程序的装载过程非常类似AutoLISP程序。有自动转载,有命令行操作。其中autoSys.cas、autoUsr.cas为自动装载文档。原则上,autoUsr.cas为用户/程序设计者使用,autoSys.cas为Cx引擎本身专用。

       内建函数SetThisSearchEnv()为新设置搜寻路径,AddThisSearchEnv()为追加搜寻路径。

       命令行中输入CAS启动Cx命令行操作,出现:

Command:

C>

    在这里输入表达式进行运算。其中最常用的应该就是:

C>  load(“myFile”);

进行装载Cx程序。表达式应该以分号‘;’结束。

 

25.     其他说明

        1)细心的读者可能有留意到,文中提供的图片展示的AutoCAD都是2002版本,十年前的版本了。需要特别说明的是,Cx同样适合各种新的CAD版本,其中用在ZWCAD的最新版本是2011。为什么钟爱AutoCAD2002?只有一个原因,VC6.0的编译速度比其他编译器快好多倍。MSVS2005/MSVS2008都用过,但是慢得无法接受,Cx数十万行代码,用VC6编译大概6分钟,而用高版本编译器却要超过30分钟。为了测试效果,需要频繁重编译。

         2)试用问题。无论那种软件,要实用,就必须先试用,目前有在做这方面的事,还没有完成。抱歉!

        3)迄今已经给各位读者展示Cx语言各方面的特性,写得不好,但是我已经很尽力了(^_^),老实说,我很怕写东西。就我个人认识水平上看,觉得Cx挺好的。但是与你们的认识、要求、期望等可能会有较大的落差,你们对编程语言可能会有更好、更多的要求。我本人对Cx的认识也在不断变化,几年前的认识与几年后完全不同,几年前认为不可能完成的功能,几年后可能在几天、甚至几个小时内设计成功。Cx引擎也因此进行过彻底重写数次。

        我很相信一句话:“当事者迷、旁观者清”。因此本博文不仅是要展示Cx语言引擎的各种特性,而且很期望得到各位读者提出的宝贵意见。

        谢谢!

 

原创粉丝点击