您的位置:首页 > 其它

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

2011-09-04 23:17 567 查看
新一代脚本语言引擎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没有一般脚本语言、J***A等那样的集中处理内存的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,otherPack

function 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语言引擎的各种特性,而且很期望得到各位读者提出的宝贵意见。

谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: