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