新一代脚本语言引擎Cx -- 应用之AutoCAD二次开发 (1)
2011-08-15 23:07
477 查看
新一代脚本语言引擎Cx -- 应用之AutoCAD二次开发 (1)
tongshou@gmail.com“十年磨一剑”,Cx经历十年的研究、沉淀、设计。。。
01. 拥有ARX的强大功能,具备LISP的灵活、简便、动态等特性
02. 非常方便与其他程序的沟通和调用DLL功能
03. 支持反应器
04. 高度动态
05. 尽力静态
06. 简洁的数据操作
07. 支持DCL, MFC
08. 支持ForEach, map
09. 良好容错、除错能力
10. 支持(自)覆盖 / 热替换
11. 支持正则表达式
12. 支持多线程编程、并行计算
(2):
13. 实时可控的动态数据处理方式,高级指针、GC
14. 内建大量数据类型及处理方法
15. 支持超高精度整数和超高精度实数
16. 类定义中的“简化型”多重继承、运算符重载、仿函数
17. 模块化(#Package)编程
18. 参考(Reference)/映射(Refect)型 变量和数据
19. 传址参数、署名参数(缺省参数)的函数定义
20. 参数包(Valist)、闭包(Closure)、子函数
21. 窗口函数的消息处理、事件反应、虚拟调用
22. 方便编写键盘重设置(键盘宏)、成倍提高电脑操作速度
23. 支持小型2D-CAM系统,方便编写CNC加工的应用程序
24. 装载、命令行操作
25. 其他说明
(3): (待续)
一种新的强大的脚本语言引擎,Cx语言, 拥有C/C++语言的强大功能,又具备普通脚本语言的灵活、便利、简洁、动态、非编译等特性,已成功用在AutoCAD、ZWCAD和办公室软件WPS等,成为其强大、灵活的二次开发程序语言。本文介绍用在AutoCAD里的Cx。
01. 拥有ARX的强大功能,具备LISP的灵活、简便、动态等特性
对于AutoCAD,其ObjectARX(以下简称ARX)的编译型C/C++程序,功能可以非常强大,但是,作为编译型的C/+C++程序语言,门槛高、编程繁琐、编译扰人、共享不易、版本敏感等,让不少人转到C#和J***A,但是C/C++中有很多很值得人们珍惜和学习的东西!这些年来,AutoCAD每年升级一次,原有的ARX应用程序,就面临升级/版本兼容问题,这既会严重影响人们对CAD升级的欲望,也影响人们对ARX应用程序欲望。
Cx以ARX方式结合到AutoCAD中。Cx程序的书写格式、运作方式、数据结构等都非常类似C/C++程序,支持包括指针、回调函数、位域、MFC、Win32 API等在内的C/c++大部分数据结构和功能,也可以进行内存直接处理的非常底层的操作。
与C/C++不同,Cx的内存处理基本上是自动的。与.NET的内存托管、J***A的GC不同,Cx的内存处理是实时可控的。Cx里可以存在多个变量/结点同时拥有同一动态数据,却不会引发死循环而造成内存永久不能释放的问题。
文件句柄、资源句柄、CAD实体对象句柄等,如果Cx程序没有显示Close或释放,Cx会在拥有句柄的变量/结点的作用域失效时、或数值要被覆盖时,自动关闭或释放这些句柄。象句柄的打开与关闭、多线程中数据同步锁的取得与释放等需要成对的操作,Cx会严格控制,一旦程序异常中断,Cx会自动进行善后处理,没有及时关闭或释放的,会将其关闭或释放,这对于脚本语言来说非常重要。如果CAD实体没有及时close(),对CAD来说基本上是致命的崩溃、甚至烟消云散。
作为脚本语言,Cx同时又具备普通脚本语言的各种高级特性,与AutoLISP一样,很少受CAD平台的版本影响,编程、装载、调用、功能共享等,都非常方便。在功能、表达能力、运行速度等方面,CX显著超越AutoLISP。
在数据类型方面,Cx既支持C/C++方式的强制类型数据,也支持普通脚本语言的泛型数据。
先看看下面一个例子,这个例子取自网络牛人“雨中飞燕” 提供的求1000阶乘算法的C代码,通过改写函数头,long前加一个点,就成为Cx语言程序了:
#define Nx 1000 public function [C:C00]() { .long s[Nx]={1,1}, n=Nx, t=2, a=1, b=0; for (; a <= *s || (++t <= 1000 ? (b=0, a=1) : 0); (*s == a++ && b) ? (*s)++ : 0) s[a] = (b += s[a] * t) % 10000, b /= 10000; for (printf("%d", s[*s]); --*s > 0; ) printf("%04d", s[*s]); }
相信绝大多数的脚本语言无法书写表达如此精练的程序(不是可读性^_^)。这个例子足以显示Cx语言良好的表达能力、与C的亲近、相似程度。
Cx定义函数,以关键字 def 或 function 标识,函数名称可以用任何字符,当然包括使用中文,当使用"不规范字符"的名称时,用[], ‘’,或""界定,上面定义的这个函数,用[ ]界定,名称是 C:C00,如果函数定义用 关键字public前置修饰,那么,定义的这个函数,会在CAD的命令队列中注册一个命令C00,这样,操作者在CAD命令行中输入 C00时,就会启动相应的函数。这点,非常类似AutoLISP函数的定义。
ARX中的大部分类定义都可以映射到Cx中,在Cx中使用这些类,宛如在C/C++中使用,因此,熟悉C/C++,尤其熟悉ARX程序的人,对Cx程序应该不会陌生。
Cx操作CAD可以使用多种方式编程: ARX方式, ADS / LISP方式, ActiveX / VBA方式,C动态编译方式等,其中以ARX方式编程运行效率最高,如果不是操作CAD,而是纯粹的数值计算,C的动态编译方式的运行效率最高,它基本上等效C的静态编译程序。
下面以同时缩放5万个圆实体半径为测试实例,展示CX不同的编程方式。电脑配置:XP, CPU: Q6600 @2.4G HZ(4 核),RAM: 2G。这也是本文所有测试使用的电脑配置。预先画好的5万个CIRCLE实体,启动相应命令时,选取该5万CIRCLE,再输入缩放值,比如 2.0。运行结果如下表:
---------------------------------------------------------------
程序语言 耗时(毫秒) 比较
---------------------------------------------------------------
Cx (Arx 普通方式) 297 1
Cx (Arx 优化方式) 219 0.74
Cx (Ads 方式 ) 3250 10.94
Cx (ActiveX 方式) 828 2.79
Cx (C的动态编译方式) 2766 9.31
AutoLISP 4109 13.84
ARX (C/C++ 编译型) 94 0.32
ADS (C 编译型) 2687 9.05
---------------------------------------------------------------
无论是ADS的静态编译型的C程序,或动态编译型的C程序,还是AutoLISP程序,对CAD实体的操作,都是通过动态生成resbuf数据链间接操作,因此相当耗时,C程序的高效性在这里无法表现出来, 如果只是数值运算,编译型程序会展现很好的运行效率,象上面第一个程序C:C00的求1000阶乘中的循环部分,经过测试,得到如下结果:
-------------------------------------------------------------------------------------
程序语言 耗时(毫秒)
--------------------------------------------------------------------
Cx (普通方式) 375
Cx (TrySpeed优化方式) 172
Cx (动态编译方式) <10
C (静态编译) <10
---------------------------------------------------------
可以看出,这里的动态编译的程序,其运行速度与传统静态编译型的C程序没有什么差别,显著超越非编译型的脚本语言。
ActiveX方式操作CAD,没有牵涉数据链,因此运行效率较高,但是ActiveX接口本身存在一定的效率损耗。
Cx程序展示出高度的灵活性和适应性,能够以各种方式混合编程,既可以根据实际运行效率的需要,也可以根据编程者自身的喜好、掌握的技能、水平状况编写相应的程序。无论ARX编程者,还是VBA编程者,或AutoLISP编程者,在Cx里都可以找到自己熟悉的编程方式。
优化后的Cx程序,其操作CAD的运行速度达到ARX C/C++编译型程序的一半,而其程序码的编写比C/C++程序要来得简洁。AutoLISP程序是所有程序中 运行最慢的。
各种程序码如下:
1)ARX 方式, 耗时 297 ms (毫秒) 或 219 毫秒:
public function [C:C01]() { ss = ssget( buildlist(0, "CIRCLE")); if (!ss) return; sc = getreal("\nScale<2.0> ="); if (!sc) sc = 2.0; t1 = GetCurrentTime(); for (en in ss) { acdbOpenObject(pEnt, en, AcDb::kForWrite); pEnt->setRadius( pEnt->radius() * sc); } printf("Time = %d\n", GetCurrentTime() - t1); }
该程序处理5万个CIRCLE运行耗时297毫秒。
用Cx提供的AcDbObjectOpenArray类,把选择集中的所有实体一次性open(),再逐一操作实体,最后一次性close()所有实体。这样可以进一步提高Cx程序的运行效率,仅仅耗时219毫秒。
public function [C:C02]() { ss = ssget( buildlist(0, "CIRCLE")); if (!ss) return; sc = getreal("\nScale<2.0> ="); if (!sc) sc = 2.0; t1 = GetCurrentTime(); A = new AcDbObjectOpenArray(ss.len); A.appendSS(ss, AcDb::kForWrite); for(pEnt in A) { pEnt->setRadius( pEnt->radius() * sc); } printf("Time = %d (ms)\n", GetCurrentTime() - t1); }
2)ADS 方式, 耗时 3250 ms (毫秒):
public function [C:C03]() { ss = ssget( buildlist(0, "CIRCLE")); if (!ss) return; sc = getreal("\nScale<2.0> ="); if (!sc) sc = 2.0; t1 = GetCurrentTime(); for(en in ss) { eg = entget(en); eg.SeekType(40)->Value *= sc; entmod(eg); } printf("Time = %d\n", GetCurrentTime() - t1); }
3)ActiveX 方式, 耗时 828 ms (毫秒):
public function [C:C04]() { ss = ssget( buildlist(0, "CIRCLE")); if (!ss) return; sc = getreal("\nScale<2.0> ="); if (!sc) sc = 2.0; t1 = GetCurrentTime(); for(en in ss) { pDisp = en.Dispatch(); pDisp.Radius = pDisp.Radius * sc; } printf("Time = %d\n", GetCurrentTime() - t1); }
4)C的动态编译方式, 耗时 2766 ms (毫秒):
public function [C:C05]() { cc = new CCode(); cc.__add_include_path( CCodeMainPath() + "ADS\\"); cc.__add_ADS(); cc.__compile( {%% #include <windows.h> #include "ads.h" #include "adscodes.h" void ScaleCircles() { struct resbuf *filter=NULL, *eg, *p; ads_name ss, en; double sc; long slen, i; int t1, res; filter = ads_buildlist(RTDXF0, "CIRCLE", 0); res = ads_ssget(NULL, NULL, NULL, filter, ss); ads_relrb(filter); if (RTNORM != res) return; res = ads_getreal("\nScale<2.0> = ", &sc); if (RTNORM != res) sc = 2.0; t1 = GetCurrentTime(); ads_sslength(ss, &slen); for (i=0; i<slen; i++) { ads_ssname(ss, i, en); eg = ads_entget(en); p = eg; while(p) { if (p->restype == 40) { p->resval.rreal *= sc; break; } p = p->rbnext; } ads_entmod(eg); ads_relrb(eg); } ads_ssfree(ss); ads_printf("T1=%d\n", GetCurrentTime() - t1); } %%} ); cc.__declare { void ScaleCircles(); }; cc.ScaleCircles(); }
5)AutoLISP程序,耗时4109 ms (毫秒):
(defun C:L01 (/ sc i en eg a1) (setq filter (list (cons 0 "CIRCLE"))) (setq ss (ssget filter)) (if ss (progn (setq sc (getreal "Scale <2.0> = ")) (if (not sc) (setq sc 2.0)) (cascript "t1 = GetCurrentTime(); ") (setq sslen (sslength ss) i 0) (while (< i sslen) (setq en (ssname ss i) i (1+ i) eg (entget en) a1 (assoc 40 eg) eg (subst (cons 40 (* (cdr a1) sc)) a1 eg) ) (entmod eg) ) (cascript " printf(_'Time = %d (ms)\n', GetCurrentTime() - t1);") )) (princ) )
6)C/C++ (ARX)编译程序,耗时 94 ms (毫秒):
void Arx_ScaleCircles() { Acad::ErrorStatus es; struct resbuf *filter=NULL; ads_name ss, en; AcDbObjectId objId; AcDbCircle *pEnt; cat_real sc; int res, t1; long slen, k; filter = ads_buildlist(RTDXF0, "CIRCLE", 0); res = ads_ssget(NULL, NULL, NULL, filter, ss); ads_relrb(filter); if (RTNORM != res) return; res = ads_getreal("\nScale <2.0> = ", &sc); if (RTNORM != res) sc = 2.0; t1 = GetCurrentTime(); ads_sslength(ss, &slen); for (k=0; k<slen; k++) { res = ads_ssname(ss, k, en); if (res == RTNORM) { es = acdbGetObjectId(objId, en); if (es == Acad::eOk) { es = acdbOpenAcDbEntity((AcDbEntity* &)pEnt, objId, AcDb::kForWrite); if (es == Acad::eOk) { pEnt->setRadius( sc * pEnt->radius()); pEnt->close(); } } } } ads_printf("Time = %d\n", GetCurrentTime() - t1); }
7)C (ADS)编译程序,耗时 2687 ms (毫秒):
该程序与“4)C的动态编译方式” {%% %%}中的程序完全一样。
再测试,以生成5万条直线为例子,得到的结果与上面的情况基本一样:
------------------------------------------------------
程序语言 耗时(毫秒) 比较
------------------------------------------------------
Cx (ARX方式) 297 1
AutoLISP 1360 4.58
ARX C/C++ 编译型程序 109 0.37
ADS 编译型程序 1032 3.47
------------------------------------------------------
限于篇幅,这里仅仅给出Cx 的ARX方式的程序:
public function [C:C06]() //定义一个CAD命令 C06 { .AcGePoint3d p1(0,0,0), p2(0, 100, 0); acdbCurDwg().getBlockTable(pTab, AcDb::kForRead); pRec = NULL; pTab.getAt("*Model_Space", pRec, AcDb::kForWrite); pTab = NULL; t1 = GetCurrentTime(); repeat( x in 50000) { p2.x = x; pEnt = new AcDbLine(p1, p2); pRec.appendAcDbEntity(pEnt); } printf("Time = %d (ms)\n", GetCurrentTime() - t1); }
02. 非常方便与其他程序的沟通和调用DLL功能
Cx支持包括指针、回调函数在内的绝大部分C/C++数据类型,因此Cx调用普通DLL中的函数、数据非常简便,例如:
myDll = new DLL(“myDll.dll”); myDll.__declare{ double func1( double a, double b); void func2( int x); int varNum; }; R = myDll.func1(1.0, 2.0); myDll.func2(123); ++myDll.varNum;
被LISP函数(vl_acad_defun) 注册过的LISP函数,可以被Cx函数调用,比如有LISP函数:
(defun lispFunc(x y) (+ (* 10 x) y) ) (vl_acad_defun ‘lispFunc)
这样就可以在Cx程序里直接调用(以操作符 “!”标识调用的是LISP函数):
Val = lispFunc!(2, 3);
以public 标识的Cx函数,会在LISP空间注册相应的函数,让LISP程序直接调用。因为Cx程序往往是定义在各自的命名空间,因此,LISP里注册Cx函数时,会在函数名前加空间名的前缀,比如:
//Cx 程序:
#package pak_1 public function cxFunc(x, y) { return 10 *x + y; } #package _end_
;LISP 程序:
(defun test() (setq val (Pak_1^cxFunc 2 3)) )
但是,如果函数名是前缀C:,则不做追加前缀处理,而是与LISP语言一样,直接注册成CAD命令型函数。
Cx支持操作ActiveX接口,既可以通过该接口操作CAD,比如对上面对CIRCLE实体进行半径缩放的例子(C:04),也可以通过该接口,启动、连接诸如Excel.Application,或电脑系统中大量的存在的ActiveX构件,比如:
public function [C:C07]() { for (x in GetObject( "WinMgmts:").InstancesOf( "Win32_DiskDrive")){ MsgBox(x.Model, L"Information of Win32_DiskDrive"); } }
显然,通过ActiveX接口,让CAD与Excel等软件交换数据是很方便的。
下面给一个例子,展示如何用Cx的回调函数为参数调用 Win32 API 的函数,具体是枚举AutoCAD 里 acadbtn.dll中所有图标BITMAP/ICON资源的名字,并且屏幕显示前十个名字。在声明Win32 API函数时,回调函数的参数以 void * 类型声明。 Cx支持类的成员函数构建回调函数,这样的回调函数类似C#中的delegate,能够调用对象里封装的数据成员。与C/C++不同,Cx里取得函数myFunc的地址是通过&.myFunc()格式。
////////////////////////////////////////////////////////////////////// #define RT_BITMAP 2 #define RT_ICON 3 ////////////////////////////////////////////////////////////////////// #package myDemo ////////////////////////////////////////////////////////////////////// public function [C:C08] () { Enum = new CEnumResource("acadbtn.dll"); if (!Enum.Start()) return; prinn("\nBitmap Names List:\n"); for (x in Enum.m_arrBitmap, 10) prinn(x); prinn("\nIcon Names List:\n"); for (x in Enum.m_arrIcon, 10) prinn(x); } ////////////////////////////////////////////////////////////////////// public class CEnumResource { def CEnumResource( sourceFile); def Start(); def EnumResTypeProc (HMODULE hInst, LPSTR pszType, LPARAM lParam); def EnumResNameProc (HMODULE hInst, LPSTR lpszType, LPSTR lpszName, LPARAM lParam); DLL m_source; DLL m_kernel32; CStringArray m_arrBitmap; CStringArray m_arrIcon; __Callback m_TypeProc; __Callback m_NameProc; }; ////////////////////////////////////////////////////////////////////// function CEnumResource::CEnumResource ( sourceFile) { m_source = new DLL(sourceFile); if (!m_source) return; m_kernel32 = new DLL("kernel32.dll"); m_kernel32.__declare { BOOL EnumResourceTypesA (HMODULE hInst, void *TypeProc, LONG lParam); BOOL EnumResourceNamesA (HMODULE hInst, LPCTSTR pszType, void *NameProc, LONG lParam); }; // 构建回调函数 m_TypeProc = new __Callback( &.EnumResTypeProc()); m_NameProc = new __Callback( &.EnumResNameProc()); } ////////////////////////////////////////////////////////////////////// function CEnumResource::Start() { if (!m_kernel32) return FALSE; m_kernel32.EnumResourceTypesA( m_source.__GetInstance(), m_TypeProc, 0); return TRUE; } ////////////////////////////////////////////////////////////////////// function CEnumResource::EnumResTypeProc (HMODULE hInst, LPSTR pszType, LPARAM lParam) { m_kernel32.EnumResourceNamesA( hInst, pszType, m_NameProc, lParam); return TRUE; } ////////////////////////////////////////////////////////////////////// function CEnumResource::EnumResNameProc (HMODULE hInst, LPSTR lpszType, LPSTR lpszName, LPARAM lParam) { switch ((int) lpszType) { case RT_BITMAP: m_arrBitmap.add(lpszName); break; case RT_ICON: m_arrIcon.add(lpszName); break; } return TRUE; } ////////////////////////////////////////////////////////////////////// #package _end_ //////////////////////////////////////////////////////////////////////
输入命令C08启动上面定义的函数 C:C08,得到如下输出结果:
Bitmap Names List:
ICON_16_2DOPTIM
ICON_16_3DCLIP
ICON_16_3DCLIPBK
ICON_16_3DCLIPFR
ICON_16_3DCORBIT
ICON_16_3DDISTANCE
ICON_16_3DFACE
ICON_16_3DMESH
ICON_16_3DORBIT
ICON_16_3DPAN
Icon Names List:
上面这段程序中有几个函数、方法定义中,对参数的类型做了强制限制,这是回调函数的需求。
03. 支持反应器
下面展示的例子是建立AcEditorRector型反应器,当该反应器打开(ON)时,一旦完成 OFFSET,COPY,MIRROR,ARRAY或 –ARRAY命令后,该反应器会自动把刚刚生成的所有实体的层、颜色和线型的属性改成DWG当前值:
/////////////////////////////////////////////////////////////////// #package myDemo class MyEdReactor; /////////////////////////////////////////////////////////////////// public function [C:C09]() { static var myEd=NULL; initget("ON OFF"); kw = getkword ("\nEnter an option [ON/OFF] <" + (myEd ? "OFF" : "ON") + ">: "); if (!kw) { kw = myEd ? "OFF" : "ON"; } if (kw == "ON") { if (myEd) return; myED = new MyEdReactor; prinn(" acting myEdReactor..."); } else { myED = NULL; prinn(" myEdReactor delected."); } } /////////////////////////////////////////////////////////////////// class MyEdReactor : AcEditorReactor { var m_en; def commandWillStart (cmd) { m_en = entlast(); } def commandEnded (cmd); }; /////////////////////////////////////////////////////////////////// function MyEdReactor::commandEnded (cmd) { ss = sslast(m_en); if (!ss) return; switch(cmd) { case "OFFSET": case "COPY": case "MIRROR": case "ARRAY": case "-ARRAY": ss.SetLayer( GetVar( "CLAYER")); ss.SetColor( GetVar( "CECOLOR")); ss.SetLtype( GetVar( "CELTYPE")); break; } } /////////////////////////////////////////////////////////////////// #package _end_ ///////////////////////////////////////////////////////////////////
04. 高度动态
Cx程序可以非常动态,象前面说的C程序的动态编译,后面将要说明的程序覆盖/“热替换”,都是很好用的动态特性。此外,函数名、变量名、类型名等标识符(名称)可以为任何字符组成,甚至可以计算。一般以前置符$作为特殊标识开始,比如:
$[姓名] = “张三”; $”年龄” = 23; ++${“AB # C & @” + 123};
在可以明确是标识符的地方,比如函数名定义处,前置符$可以省略。
Cx程序码可以动态构建,方式有多种:Lambda函数、普通函数体、字符串等,都可以动态插入运行。对于函数体、Lambda定义体的插入运行,与单独运行这些函数、Lambda定义体不同,比如定义体中的return语句,仅仅起到结束插入体程序运行的作用,不会对当前正在运行的函数产生影响。类似C语言中运行一个{。。。}局部结构的程序,不同的是Cx有带参数,这一点又象宏带入。一旦插入的程序段运行结束,期间产生的局部变量会马上释放。例如,有字符串:
S = “ x = a + b * c”;
可以动态执行:
S.eval();
等效执行字符串里表达的程序。只是这里的运行解释过程会比较消耗时间,如果对字符串里的表达式要求高效运行,可以先进行格式化处理:
F = Formula(S);
再运行:
F.eval();
或
F();
运行效率一般可以提高一个数量级。
Cx的函数定义,支持可变参数,比如,定义一个任意参数个数的加法函数:
function myAdd(A, …) { no = ThisParamNo(); for(k=1; k<no; k++) { A += *ThisParam(k); } return A; }其中内建函数ThisParamNo()取得当前函数myAdd的参数个数,内建函数ThisParam()则取得当前函数的参数地址/指针,这样,通过表达式:
B = myAdd(1,2,3,4);变量B得到的值就是:10
05. 尽力静态
Cx支持泛型变量,也支持强制类型声明的变量。明确变量类型,有利于Cx程序在运行过程静态处理,比如对高强度循环过程,通过语句:trySpeed {…} 有可能显著提高程序运行速度:
public function [C:C10]() { res = 0i64; t1 = GetCurrentTime(); for (k=0; k<10000000; k++) { res += k; } printf("Time = %d (ms)\n", GetCurrentTime() - t1); }
public function [C:C11]() { .int k; .__int64 res=0; t1 = GetCurrentTime(); trySpeed { for (k=0; k<10000000; k++) { res += k; } }; printf("Time = %d (ms)\n", GetCurrentTime() - t1); }
上面定义两个函数,其中C:C10采用泛型方式,而C:C11采用强制类型声明,并且引入程序结构trySpeed{…},运行结果显示,C:C10耗时2234 毫秒,C:C11耗时仅仅719毫秒。可以算出C:C11的运行速度是C:C10的3倍。
Cx语言会对一些语句进行静态优化,比如switch-case语句,因为进行静态优化处理过,其case 匹配过程非常快速,即使存在数以千计的case 语句,也可以在瞬间匹配到位。如果使用传统的if –else if … 语句方式,当case 比较多时,会明显影响运行效率。
06. 简洁的数据操作
Cx中的大部分数据结构,与C/C++中的数据结构是完全一致的,当与C/C++编译成型的Win32 API的DLL模块进行沟通时,数据不需要进行任何转换,比如,Cx语言生成的字符串数组:
mArray = new TSTR[1024];
在内部数据结构分布上与C中的如下数组是完全一样的:
LPTSTR * cArray = new LPTSTR[1024];
因此,当有C的库函数需要这种类型的参数时,可以把mArray直接赋值参数,Cx语言内部不需要做任何数据转换,如:
myDll = new DLL(“abc.dll”); myDll.__declare { void xFunc(int a, LPTSTR[] b); }; myDll.xFunc(123, mArray);
在Cx语言里对mArray数组元素的赋值,要比C里简洁、自在得多,不需要考虑内存泄漏问题,也不会发生内存泄漏,有关内存的释放,Cx语言会自动完成,程序员的编程仅仅是进行简单的赋值操作:
mArray[8] = “ABC”; mArray[8] = aNewStr;
不要担心mArray[8]处原来是否存在字符串,如果存在,也用不着编程去释放这个字符串,Cx 语言会自动、马上释放。
如果把某数组元素赋予变量v,实际上是让v指向该元素拥有的字符串:
v = mArray[8];
当mArray[8]拥有的字符串更新时,原来的字符串自然失效,变量v的值会自动变为NULL。
调用Win32 API函数时,往往会要求各种struct型参数,有相当部分在Cx内部有预定义,如果没有,可以用Cx程序直接书写,Cx支持位域、位对齐等,效果与C/C++中定义的完全相同。
有C/C++编程经验的人,应该可以从中看出Cx语言程序的简便方式对编程效率、品质的意义。
AutoLISP语言操作CAD,往往通过resbuf 数据链,因为LISP语言的传值特性,对数据链的任何修改,都会引发重构整条数据链,造成明显性能损耗。Cx支持指针,可以修改resbuf 数据链中任意数据结点而不必重构数据链,而且操作非常简单,比如,变量L1中有如下resbuf数据链:
(0 1 2 3 4 5 6)
如果要修改第三个数(从0位算起),比如乘8:
L1[3].value *= 8;
L1中的数据链变为:
(0 1 2 24 4 5 6)
也可以把第三个结点的数据直接赋其他类型数据的值:
L1[3] = “Hello”;
L1中的数据链变为:
(0 1 2 “Hello” 4 5 6)
还可以方便操作resbuf数据结点亚结构中的数据,比如,变量L2中有如下resbuf数据链:
(0 1 2 (11 “X” “Y”) (22 “U” “V”) 3.14)
用如下表达式:
(L2+3)[2].Value += “-ABC”;就可以把L2改成:
(0 1 2 (11 “X” “Y-ABC”) (22 “U” “V”) 3.14)
Cx仅仅是通过数据指针修改相应亚结点的数据,其他结点的数据完全没有改变。操作很方便吧。
大部分脚本语言,都采用传统的所谓的垃圾回收机制调控资源,这个回收机制虽然已经改善很多,但是仍然存在一些问题,一个是垃圾回收过程的不可预测性;其次是编写程序过程必须很小心,否则可能会出现相互引用而造成内存永远无法释放;象数据链、数组之类的复合型数据,相关引用很多,会影响数据处理效率。Cx语言完全没有这些问题。
07. 支持DCL, MFC
Cx除了支持AutoCAD提供的窗口控制语言DCL外,更强力支持Microsoft Visual C/C++的MFC编程。用MFC设计窗口,会比DCL强大、漂亮得多。由于Cx脚本特性,建立独立小单元的测试非常方便,学习、测试MFC特性是一件相当让人振奋的事。
Cx内部连接大量MFC类(class),可以直接使用这些类,无缝地继承这些类,虚拟函数、操作符重载、仿函数、枚举值,等等,都可以继承过来。对于窗口类,Cx处理消息要比C/C++语言简洁得多,不需要使用映射消息的数据结构和相应的宏。对于不同的窗口消息,Cx会自动调用相应名称的方法处理消息。Cx本身的类定义,支持“简化型”的多重继承,一个类里可以同时定义具有不同参数个数的构造函数,析构函数功能是健全的,在对象失效时,会及时启动。下面先给个简单的例子:
Mx = new AcGeMatrix3d(); Mx(1,1) = 123; ++Mx(1,1);
这里的操作,隐式调用了 AcGeMatrix3d仿函数。
Cx中定义的类,如果继承自MFC的CObject之类的类,可以拥有MFC类CRuntimeClass,进行相当底层的操作,比如进行动态产生窗口,象下面的例子,将主窗口分割成3个子窗口,利用CRuntimeClass为每个子窗口动态产生Cview型窗口:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202001/22/92e9ae09a9a0d9fb3577a7dd20fba8b7.gif)
////////////////////////////////////////////////////////////////////// #package myDemo ////////////////////////////////////////////////////////////////////// #include "windows.ch" #define MY_PHONE 88 #define MY_EXIT 99 ////////////////////////////////////////////////////////////////////// public function [C:C12]() { myC = new CCreateContext; MyF = new CmyForm; res = MyF.Create( NULL, "myDemo", WS_OVERLAPPEDWINDOW, CFrameWnd::rectDefault, AfxGetMainWnd(), NULL, 0, myC); if (!res) return; MyF.ShowWindow(SW_NORMAL); MyF.UpdateWindow(); MyF.RunModalLoop(); } ////////////////////////////////////////////////////////////////////// class CmyForm : CFrameWnd { def OnCreateClient (lpcs, pContext); def OnSize (type, cx, cy); def OnClose (); def OnNcDestroy () { EndModalLoop(1); } CSplitterWnd m_sV, m_sH; RECT m_rec; int m_bCreateSplitter=FALSE; }; ////////////////////////////////////////////////////////////////////// function CmyForm::OnCreateClient(pcs, pcc) { pcc.m_pCurrentFrame = this; siz = new SIZE; rView1 = RUNTIME_CLASS(CmyView_1); rView2 = RUNTIME_CLASS(CmyView_2); rView3 = RUNTIME_CLASS(CmyView_3); m_sV.CreateStatic(this, 1, 2); m_sH.CreateStatic(m_sV, 2, 1, WS_CHILD | WS_VISIBLE, m_sV.IdFromRowCol(0, 1)); siz.cx = 700; siz.cy = 600; if (!m_sV.CreateView(0, 0, rView1, siz, pcc)) return 0; siz.cx = 200; siz.cy = 200; if (!m_sH.CreateView(0, 0, rView2, siz, pcc)) return 0; if (!m_sH.CreateView(1, 0, rView3, siz, pcc)) return 0; m_sV.SetRowInfo (0, 100, 10); m_sV.SetActivePane(0, 0, NULL); m_bCreateSplitter = TRUE; SetWindowPos(0, 60, 40, 900, 600, 0); ShowWindow(SW_NORMAL); return 1; } ////////////////////////////////////////////////////////////////////// function CmyForm::OnSize(type, cx, cy) { if (!m_bCreateSplitter) return; GetClientRect(m_rec); Width = m_rec.Right - m_rec.Left; Height = m_rec.Bottom - m_rec.Top; m_sV.SetColumnInfo(0, Width * 6/9, 0); m_sV.SetColumnInfo(1, Width * 2/9, 0); m_sH.SetRowInfo(0, Height /2, 0); m_sH.SetRowInfo(1, Height /2, 0); m_sV.RecalcLayout(); m_sH.RecalcLayout(); } ////////////////////////////////////////////////////////////////////// function CmyForm::OnClose() { if (MessageBox("Are you sure to quit?", "Quit", MB_YESNO) != IDYES) return 1; } ////////////////////////////////////////////////////////////////////// class CmyView_1 : CView { CPicture m_pic; RECT m_rec; def OnCreate(cs) { m_pic.Load("highway.jpg"); } def OnDraw (pDC) { GetClientRect(m_rec); m_pic.Render(pDC, NULL, m_rec); } }; ////////////////////////////////////////////////////////////////////// class CmyView_2 : CView { CPicture m_pic; RECT m_rec; def OnCreate(cs) { m_pic.Load("warShip.jpg"); } def OnDraw (pDC) { GetClientRect(m_rec); m_pic.Render(pDC, m_rec); } }; ////////////////////////////////////////////////////////////////////// class CmyView_3 : CView { def OnCreate (cs); def OnCommand (msg); def OnDraw (pDC); CPicture m_picPhone1; CPicture m_picPhone2; CPicture m_picExtit; CBitmapButton m_btnPhone; CBitmapButton m_btnExit; HWND m_hFrame; }; ////////////////////////////////////////////////////////////////////// function CmyView_3::OnCreate( cs) { rec = new RECT; m_hFrame = ((CCreateContext) cs.lpCreateParams).m_pCurrentFrame->m_hWnd; m_picPhone1.Load("phone_up.jpg"); m_picPhone2.Load("phone_down.jpg"); m_picExit. Load("exit.jpg"); sty = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW; rec.left = 30; rec.right = rec.left+20; rec.top = 50; rec.bottom = rec.top +20; m_btnPhone.AttachBitmaps( (HBITMAP) m_picPhone1.GetHandle(), (HBITMAP) m_picPhone2.GetHandle() ); m_btnPhone.Create("", sty, rec, this, MY_PHONE); m_btnPhone.SizeToContent(); sty = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW; rec.left = 150; rec.right = rec.left+20; rec.top = 50; rec.bottom = rec.top +20; m_btnExit.AttachBitmaps( (HBITMAP) m_picExit.GetHandle() ); m_btnExit.Create("", sty, rec, this, MY_EXIT); m_btnExit.SizeToContent(); } ////////////////////////////////////////////////////////////////////// function CmyView_3::OnCommand(msg) { switch(msg.wParam) { case MY_PHONE: CreateDispatch("SAPI.SpVoice").Speak("Hello"); return 1; case MY_EXIT: ::SendMessage(m_hFrame, WM_CLOSE, 0, 0); return 1; } } ////////////////////////////////////////////////////////////////////// function CmyView_3::OnDraw(pDC) { s1 = "Hello"; s2 = "Exit"; pDC.TextOut(50, 120, s1, s1.len); pDC.TextOut(170, 120, s2, s2.len); } ////////////////////////////////////////////////////////////////////// #package _end_ //////////////////////////////////////////////////////////////////////
08. 支持ForEach, map
作为脚本语言既酷、又富有效率的程序结构:ForEach 和 map,Cx强力支持。对于ForEach结构,Cx的表达方式有:
1) for (x in express [, len]) {…}
2) for (p to express [, len]) {…}
3) repeat(x in num [,Zero [, Step]]) {…}
其中express, 可以是各种形式的数组结构、数据链、字符串、和CAD选择集,等等。这类表达式已经不断出现在上面例子程序中。它们不仅表达简洁,而且可以让Cx程序的运行更富有效率,For(x in experss) 中的 x, 不仅对应各个数据结点的值,很多时候是这些数据的参照(Reference),让这些结点数据直接进行存取,比如,有数组:
A = new int[1000]; for (x in A) x = $i;
这里的 $i为Cx的系统变量,自动记录了该 for 循环的序号。因此,现在A中各元素的值分别是:
0, 1, 2, 3, 4, …, 998,999。
for (p to express) {…}结构,用于操作复杂的数据结点,比如ADS的resbuf数据链,p是指向各个数据结点的高级指针。通过该指针,可以方便操作所指数据结点的各种值。比如,建立如下resbuf的LIST数据链:
L = new LIST(<1,"Sa">, <2, "Sb">, <3, "Sc">);
得到LIST数据链:
((0 “Sa”) (1 “Sb”) (2 “Sc”))
进行 to 的 forEach操作:
for (p to L) { p[1].value += "-x" + $i; //$i为Cx的系统变量,自动记录了 for 循环的序号 }LIST数据链L的内容变为:
((0 "Sa-x0") (1 "Sb-x1") (2 "Sc-x2"))
对于Map的结构,Cx的表达方式有:
1) map_in(L1, L2, …) {
functionPtr;
}
2) map_to(L1, L2, …) {
functionPtr;
}
其中L1, L2, …可以为各种形式的数组结构、数据链、字符串、和CAD选择集等, FunctionPtr为Lambda、函数指针、或闭包(下篇博文有专门论述)。比如有3个数组:
#define MyLEN 1000 A = new double[MyLEN]; B = new double[MyLEN]; C = new double[MyLEN];
。。。初始化A和B各元素的值后,
map_in(A, B, C) { Lambda(x, y, z) { z = x + y; } }
完全等效C/C++中的表达式:
for (int i=0; i < MyLEN; i++) { Z[i] = A[i] + B[i]; }
对于map_to,与 for(p to…) 的情况一样,用于操作复杂的数据结点。
09. 良好容错、除错能力
作为脚本语言,良好的容错性是很重要的,不像编译型程序语言的程序错误可以在编译过程发现和更正,脚本语言的很多错误只能在运行过程发现。因此,脚本语言应该有一定的纠错、“容错”能力。比如在前面讲到的程序在异常情况下中断,必须能够自动对要求成对操作进行善后处理。
Cx中的对象变量或对象结点,一般都具有双重性:指针和参照,因此,Cx对方法的调用,一般即可以用点操作符 “.”,也可以用指针操作 “->”。
Cx的标识符不区分大小写。函数名称、变量名称、类型名称等可以同名,因此不用担心变量名称的随意使用而可能导致同名函数、类型的失效。
采用下面程序结构,可以处理程序中可能的出错:
1)try {};
{}中出错时,跳出{},继续执行下去。
2)try {1} except {2};
{1}中出错时,跳到{2}中运行,如果没有出错,则跳过{2}。无论如何,程序都会在 {2}后继续运行下去。
3)try {1} finally{2};
无论{1}中是否出错,都会进入{2}中运行,然后程序都会在{2}后继续运行下去。
010. 支持(自)覆盖 / 热替换
Cx的函数定义,类定义,结构定义等,都可以进行(自)覆盖/ 热替换,程序运行过程可以随时随地装载新的程序,无论新的程序中的各种定义是否与旧的定义重叠,都没有关系,新、旧定义可以并存,旧的定义体仍然可以正常运作下去,新的定义也可以开始新的操作。即使在多线程复杂情况下也可以进行正常装载!
AutoLISP支持函数自覆盖特性,这是非常有用的特性,利用该特性,数以千计的功能、函数,可以在真正需要的时候才自动、随时随地装载,而不是在宿主程序启动时一次性装载而严重影响启动时间、也造成内存浪费。
用脚本语言进行大型软件的二次开发,说简单很简单,可能仅仅一行代码就可以完成一项任务,它的简洁、它的灵活性,是编译型语言不能匹敌的!但是二次开发、尤其脚本语言进行二次开发,说困难也困难,因为运行环境很恶劣,很难预料、不好控制,操作者的操作可能很随机,甚至是恶意的,如何去适应?二次程序开发者可能不止一个,素不相识的开发者的程序之间如何协调、共处而不冲突?二次开发的程序可能非常多,而可能调用的功能很随机,在不能把所有功能程序装载、甚至绝大部分功能程序都没有装载的情况下,操作者如何随心所欲、让宿主平台自动、简易调用需要的功能?程序重载、覆盖,是否会影响正在运行的程序?
等等,所有这些,让人眼花缭乱。
Cx语言的产生,最初的宗旨就是为了解决上面这些问题。很庆幸、也很意外的是,上面的这些问题,现在在Cx语言里已经不成问题了。
011. 支持正则表达式
例如下面是一个定义有效日期格式的正则表达式(考虑了润年影响,摘自网络文章):
exp = @"^(?:([0-9]{4}-(?:(?:0?[1,3-9]|1[0-2])-(?:29|30)|" @"((?:0?[13578]|1[02])-31)))|([0-9]{4}-(?:0?[1-9]|" @"1[0-2])-(?:0?[1-9]|1\d|2[0-8]))|" @"(((?:(\d\d(?:0[48]|[2468][048]|" @"[13579][26]))|(?:0[48]00|[2468][048]00|" @"[13579][26]00))-0?2-29)))$"; rx = new regex(exp); rx.match("2000-2-29"); // 返回 1 (有效日期) rx.match("2001-2-29"); // 返回 0 (无效日期)
正则表达式是一种很特殊的语言,看起来怪异,不容易书写和上手,但是很有用,可以在网上找到不少实例。
012. 支持多线程编程、并行计算
作为脚本语言,Cx没有象Python那样的“全局锁”(GIL),所有运行于各线程的Cx程序,都是真正的系统级线程程序,程序语句独立、安全。一般脚本语言的内部实现,需要牵涉太多公共数据读和写,在多线程状态下要安全实现数据共享,往往需要付出太多性能代价,Cx非常成功解决了这个问题,代价很小,很安全,甚至在极端情况下,比如10个线程同时在同步锁协调下为某个整数增值操作,同步协调造成的运行速度损耗不会超过3%!如果采用传统同步锁方法进行同步操作,运行速度损耗可能高达85%!
下面给个比较测试的例子。Cx提供一种同步锁:单写多读同步锁wrLock,允许同时很多读操作,而写操作具有排他性,一次只允许一个写操作。比较用的同步锁程序来自 Jeffrey Richter编写的类CSWMEG,也是单写、多读同步锁。为了便于比较,该类已经被Cx关联,可以直接在Cx里调用。这里先给出结果,再列出相应的程序。程序的结构以Cx支持的并行计算方式书写,对数据成员m_res共做 1千万次增1操作。其中类CmyPa_1采用 wrLock同步锁,而类CmyPa_2采用 CSWMRG同步锁。函数C:13和C:14都调用类CmyPa_1,分别使用单线程和10个多线程操作,函数C:15和C:16都调用类CmyPa_2,方别使用单线程和10个多线程操作,结果如下(消耗时间:ms
毫秒):
同步锁 单线程 多线程 比较(单线程/多线程)
-----------------------------------------------------------------------
wrLock (Cx) 5,344 5,484 97.45%
CSWMRG (By Jeffrey Richter) 5,750 39,015 14.74%
-----------------------------------------------------------------------
从中可以看出极大的差异!显示Cx语言提供的同步锁无比优越的性能!需要指出的是,两种同步锁下的程序运行过程,CPU占用率都不太高,在20~30%之间徘徊。
////////////////////////////////////////////////////////////////////// #package myDemo ////////////////////////////////////////////////////////////////////// public function [C:C13]() // 用wrLock, 单线程 { t1 = GetCurrentTime(); P1 = new CmyPa_1(1); P1.start(); printf("Time = %d (ms)\n", GetCurrentTime() - t1); prinn("result = " + P1.m_res); } ////////////////////////////////////////////////////////////////////// public function [C:C14]() //用WrLock, 10个线程 { t1 = GetCurrentTime(); P1 = new CmyPa_1(10); P1.start(); printf("Time = %d (ms)\n", GetCurrentTime() - t1); prinn("result = " + P1.m_res); } ////////////////////////////////////////////////////////////////////// public function [C:C15]() //用CSWMRG, 单线程 { t1 = GetCurrentTime(); P2 = new CmyPa_2(1); P2.start(); printf("Time = %d (ms)\n", GetCurrentTime() - t1); prinn("result = " + P2.m_res); } ////////////////////////////////////////////////////////////////////// public function [C:C16]() //用CSWMRG, 多线程 { t1 = GetCurrentTime(); P2 = new CmyPa_2(10); P2.start(); printf("Time = %d (ms)\n", GetCurrentTime() - t1); prinn("result = " + P2.m_res); } ////////////////////////////////////////////////////////////////////// class CmyPa_1 : CParallel //用WrLock { CmyPa_1( nThread) { CParallel::initial( nThread); m_nThread = nThread; m_lock = new wrLock(); m_res = 0; } ~CmyPa_1(){} def InvokeProcess() { my_lock = m_lock.__image(); repeat(10000000 / m_nThread) { my_lock.EnterWrite(); m_res++; my_lock.LeaveWrite(); } } wrLock m_lock; __int64 m_res; int m_nThread; }; ////////////////////////////////////////////////////////////////////// class CmyPa_2 : CParallel //用CSWMRG { CmyPa_2( nThread) { CParallel::initial( nThread); m_nThread = nThread; m_lock = new CSWMRG(); m_res = 0; } ~CmyPa_2() {} def InvokeProcess() { my_lock = m_lock.__image(); repeat(10000000 / m_nThread) { my_lock.WaitToWrite(); m_res++; my_lock.Done(); } } CSWMRG m_lock; __int64 m_res; int m_nThread; }; ////////////////////////////////////////////////////////////////////// #package _end_ //////////////////////////////////////////////////////////////////////
多核PC已经普及几年了,能够利用多核优势进行编程的程序语言不多,脚本语言就更少了。为了有效使用多核,要求程序能够支持真正的多线程(Multihread)!
有一些脚本语言声称支持多线程编程,但是它们即使能够运行于多核,却达不到提高程序运算效率的目的,为什么?象现在很流行的脚本程序语言Python和Ruby,其多线程的运作是通过全局锁(GIL)调控,在GIL控制下,多线程的运行是畸形的,在同个时间段,只允许一个线程取得GIL,处于运行状态,其他线程是处于休眠状态!可想而知,在GIL控制下,这些多线程合计的运算效率,与一个普通单线程情况下的运算效率不会有多大差别!因此,人们一直期望能够移除这个GIL。Python到版本3.1、Ruby到版本1.9都没有能够移除掉GIL,倒是基于微软。NET平台的
IronPython和IronRuby没有采用GIL,这是一大进步。
脚本语言的实现,一般内部存在大量需要共享的数据,虽然在多线程环境里数据共享方法很早就有,也健全,但是就是存在明显的效率问题,线程一多,问题更大,可能会象死机一样,这是个令人非常头痛的问题。有人抱怨CPU提供的“同步”机制太弱,Intel等硬件厂商把无法在硬件上再提升速度的问题,转嫁给软件业。不过,这也反映出,软件的发展跟不上硬件的发展。
Cx程序语言的成功,在一定程度上是受益于多线程同步技术的探索取得突破性进展!为了适应多线程需要,Cx语言的架构就是基于多线程。它支持系统级多线程,能同时运行于CPU多核,不仅支持MFC的工作者(Worker)线程,而且支持用户界面(UI)线程。
上面的例子是围绕同步锁激烈竞争情况,下面给个没有同步竞争的多线程平行计算性能比较测试结果,与Python、IronPython 对比,测试过程是求从 1到5千万的和。分别测试在单线程和有10个线程并行计算的结果如下(消耗时间单位: ms 毫秒):
单线程 10个线程 (单线程/10线程)的比值
------------------------------------------------------------------
Cx 1875 547 3.43
Python v3.1 4747 4470 1.06
IronPython v2.0 6735 4030 1.67
------------------------------------------------------------------
限于篇幅,省略源程序。可以看出,Python 在多线程下并行计算得到的效率提升很少,几乎可以忽略不计。在多线程下IronPython的并行运算效率有些提升,而效率提升很多的是Cx语言!Cx语言的运算效率与核数量几乎成线性关系,核利用率高达85.75% (3.43/4)。从IronPython的结果看,感觉其内部运行机制中还是存在类似GIL的东西,只是效果比真正存在GIL时改进一些。
AutoCAD的操作不支持多线程,但是在AutoCAD里仍然可以正常运行多线程,只要这些多线程不牵涉操作CAD。
相关文章推荐
- 新一代脚本语言引擎Cx -- 应用之AutoCAD二次开发 (2)
- AutoCAD二次开发中VB或VBA的应用问题
- 【干货】iData二次开发——以脚本语言Python为例
- Java版AVG游戏开发入门示例[3]——脚本引擎的制作及应用
- Java版AVG游戏开发入门示例[3]——脚本引擎的制作及应用
- 使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍
- Java版AVG游戏开发入门示例[3]——脚本引擎的制作及应用
- 使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍 推荐
- 使用C#作为Cocos2dx引擎脚本语言进行游戏开发——C#脚本简介(一)
- 浅谈EPS2008脚本二次开发的应用
- AutoCAD二次开发技术在工程图纸绘制中的应用
- 使用C#作为Cocos2dx引擎脚本语言进行游戏开发——脚本运行时基础(二)
- Java版AVG游戏开发入门示例[3]——脚本引擎的制作及应用
- AutoCAD的二次开发可以用什么开发语言?
- Java版AVG游戏开发入门示例[3]——脚本引擎的制作及应用 推荐
- .Net语言 APP开发平台——Smobiler学习日志:在应用中添加WeiXin组件
- AutoCAD二次开发之创建菜单
- 有关Lua脚本语言应用
- (c#)AutoCAD二次开发,运行时,出现“对象的当前状态使该操作无效”
- 规则引擎-BRMS在企业开发中的应用