1. 程式人生 > >新一代指令碼語言引擎Cx -- 應用之AutoCAD二次開發 (1)

新一代指令碼語言引擎Cx -- 應用之AutoCAD二次開發 (1)

 

          新一代指令碼語言引擎Cx -- 應用之AutoCAD二次開發 (1)

“十年磨一劍”,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#和JAVA,但是C/C++中有很多很值得人們珍惜和學習的東西!這些年來,AutoCAD每年升級一次,原有的ARX應用程式,就面臨升級/版本相容問題,這既會嚴重影響人們對CAD升級的慾望,也影響人們對ARX應用程式慾望。

        Cx以ARX方式結合到AutoCAD中。Cx程式的書寫格式、運作方式、資料結構等都非常類似C/C++程式,支援包括指標、回撥函式、位域、MFC、Win32 API等在內的C/c++大部分資料結構和功能,也可以進行記憶體直接處理的非常底層的操作。

        與C/C++不同,Cx的記憶體處理基本上是自動的。與.NET的記憶體託管、JAVA的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型視窗:

//////////////////////////////////////////////////////////////////////
#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。