Wednesday, September 27, 2006

使 用 BCB開 發 大 型 系 統 - Package 的應用重點

◎蕭沖原創

寫這篇文章的靈感來源是來自李維在多年前的一篇注名文章:
http://www2.borland.com.tw/tw/reference9.html
裡面提到如何使用package切割子系統方便開發與管理,並可增進效能。曾經看網友提到如何把裡面的範例轉換成對應bcb的語法,因為裡面用到許多delphi才特有的一些功能,在c++裡原本沒有那些功能,比如說delphi的unit裡有initialization段、finalization段。再者delphi可以直接使用TComponentClass(VMT(Virtual Method Table)的實作)這個特別的類別來create instance。但bcb裡沒有!

★PACKAGE特點:
1/ Explicit linking時仍可以Create object from PACKAGE class。(只要在PACKAGE裡實作void my_startup(void) { RegisterClass(__classid(TXxxx)); } #pragma startup my_startup,並於呼叫端使用Application->CreateForm(FindClass("TXxxx"),&obj_ref);即可達成。)
2/ package中的global變數在同一個process上自然的就使用共享記憶體。
3/ 建立bcb之ide元件,含design time component。

Sunday, September 17, 2006

複習C++ Class間的Overload、Override、Hide

◎蕭沖原創

複習一下過去的學習。

當Base Class裡的method與Derive Class中的method形成Overload的情形時,Base中的那些同名method都會被Hide起來。若是試著去使用Base中的那些method時,就會產生error。但可以把instance upcast後使用那些Hide的method,然而這時候Derive中的那些method也會變成無法使用。這個規則無論是Overload一個Base的Virtual或是非Virtual的method都適用!

當Derive Class中Redefine(同名同signature) Base Class中的method時,若此base 的method是Virtual的,那就會產生Override(覆載)。但若base method非Virtual的,那就會產生Hide的情形。

Overload : 同名但不同簽名(signature)
Override : 同名同簽名的Virtual method被再定義

Pure Virtual method不可使用inline方式定義method,但可以使用外部定義的方式。這樣每個子類別就可以方便以Interface::method的方式直接取用。但一般來說是不會在interface上定義method的。

Saturday, September 16, 2006

物件初始化 -- C style Vs Cpp style

◎蕭沖原創

傳統C語言裡若要初始物件(結構體struct),需使用{ }陣列初始的方法來初始之。但已經不合用於cpp中的class型別,初始化的方式已有所不同。c中使用bitwise copy,一個byte一個byte的copy值。但class中有constructor,並非是簡單的bit copy就可以的。此外,C的其他型別都是用 = 號來初始值。在cpp中擴展了初始化的方法,即用()來做。如: ClassType obj(data); 為了和C的包伏問題,也可以使用 ClassType obj = data來代表同一件事。當然,當建構子的參數超過一個以上你就只能ClassType obj(data1, data2, data3);

cpp裡還有一個功能,允許operator被overloading。於是 = 號則也可能被overloading。造成了某些觀念上會有誤解。比如說:
String obj = rhs // statement A
String obj; obj = rhs ; // statments B
A 與 B 這二種寫法是一樣的嗎?

「通常」是一樣的結果,但根子裡是不一樣的。
第二種的寫法compile是先用default constructor先創出一個String物件。然後再使用String裡的 = operator 複載(overload)來做物件copy。值得注意的是當用 = copy時,左右二邊的型別決定了是否可copy。

第一種則單純依overloading 的constuction的型別媒合而create一次!

問題通常會發生在一個物件它的建構子的所有overloading型別與它自己 = operator 的overloading的型別不全然一樣!
實例說明: 以以BCB中Variant這個類別來說
它的reference construction overloading裡有 short*, int*.........
它的 = operator 則是 Variant& __fastcall operator =(const Variant& rhs);
也就是說它接等號右邊可以是 Variant&,說明檔裡更指出如下:
If the Variants can be converted to types that make sense for the given operation, the assignment is performed
好了,實驗一下:
Variant vv =cat->get_ActiveConnection(); // wrong! no such constructor match
Variant vv;
vv = cat->get_ActiveConnection(); //using Variant::operator= overload is match,ok!
結論:
1/ ClassType obj = rhs 與 ClassType obj(rhs) 是等價的! 但用cpp style ( ) 在bcb中會在design time 出現有錯誤,但不用理會。
2/ ClassType objA; objA = rhs ; 這二行就不一定等於上面1說講的一樣。
3/ 我建議使用cpp style 來初始建構,這樣比較不會被 = 號給搞亂了。因為 = 時而是用建構子,時而又是表達二個物件copy。
4/ 是否要分二行陳述則要依情形來看。以剛我講的例子就需要二行。若是一般情形則用一行就可以。而我個人又強力的建議使用cpp的( ) 建構型態!
5/ 一行的效能一定比二行好,所以不要全都使用二行的方式!!

Friday, September 15, 2006

BCB project (bpr) file 的手動修正方式筆記

◎蕭沖原創

經常會拿到別人的source code回來compile,於是遇到原來的bpr檔資料和本機的有所出入,就出現一堆的錯誤。所以,稍深入的觀察bpr和project option的關係後,寫下這筆記!

◎修改project的bpr(xml)檔時,最好是在非IDE的情形下。不然經常會被IDE給overwrite。
下次又變成原來的樣子。

◎FILELIST放有整個project會用到的source code,注意是否有要增或刪或改的地方。

◎當「使用」dynamic RTL時,對應的project xml檔裡有下面的變化:

<SYSDEFINES value="NO_STRICT;_RTLDLL"/>

<LINKER>
<ALLOBJ value="c0w32.obj Memmgr.Lib sysinit.obj $(OBJFILES)"/>
........
<ALLLIB value="$(LIBFILES) $(LIBRARIES) import32.lib cp32mti.lib"/>
</LINER>

1/ 多了 _RTLDLL
2/ 多了 Memmgr.Lib
3/ cp32mt.lib 變成 cp32mti.lib (注意i結尾)

◎當「不使用」Build with runtime packages時會有下面的變化:

<LIBRARIES value="dbrtl.lib vcldb.lib vclx.lib bcbsmp.lib rtl.lib vcl.lib TntLibR.lib"/>
<SYSDEFINES value="NO_STRICT;USEPACKAGES"/>
<ALLOBJ value="c0w32.obj $(PACKAGES) sysinit.obj $(OBJFILES)"/>

1/ LIBRARIES中原本無值,但不使用package時則多了一堆lib
2/ 多了 USEPACKAGES
3/ 多了 $(PACKAGES)

◎當回復「使用」Build with runtime packages時:下面的系統lib會清空,「但第三元件不會」
<LIBRARIES value="TntLibR.lib"/>


◎<SPARELIBS/> 與 <LIBRARIES/>的問題: <<!注意,僅在未使用Build with runtime packages時>>
當二者不match時,會以SPARELIBS來重建LIBRARIES。以SPARELIBS為主!

◎多數問題出現在<SPARELIBS/> 與 <LIBRARIES/>。適當的刪除/增加第三元件的lib非常有用。

Monday, September 11, 2006

Socket mode Note

◎蕭沖原創

總的來說,Socket 可分為二種模式 : blocking mode, non-blocking mode。

要使socket成為non-blocking mode只要設定ioctlsocket為non-blocking mode就可以,然而,若配合使用了blocking functions,會使得讀/取不到資料時產生WSAEWOULDBLOCK的error。因此若要成功而完整的使用non-blocking mode,就必需配合適當的socket IO models。
Socket I/O 有六種模型:blocking, select, WSAAsyncSelect, WSAEventSelect, overlapped, and completion port。

針對不同的模型,我以一個虛擬的例子來說明: 假設有128條水管可以出水,但每一條出水的時間不同,且有快有慢,若我們的任務是在每一條水管必需各裝3桶水,那麼我們可以達成的方法有:
1/ blocking : 請128人,每人帶一個水桶,站在每一條水管前等候,直到每個人都裝完3桶水
2/ WSAAsyncSelect,自行定義一個使用者msg,然後結合至windows msg loop,由winproc中的switch case 來處理。這如同-- 請1個人帶一個水桶站在128條水管前,拼命的裝先出水的水管。
3/ WSAEventSelect,請2個人各自帶一個水桶,站在128條水管前,一人管64條(最多),然後拼命的裝先出水的水管。
4/ overlapped with event,使用WSA_FLAG_OVERLAPPED旗標建立overlapped socket,然後使用overlapped socket functions ( APCs)。請2個人各自帶任意個水桶,在128條水管前,一人管64條(最多),將任意數的水桶放於水管前等被裝水,先被裝滿水的水桶則先被通知提領,這二個人再進行提取。
4/ overlapped with completion routine,使用WSA_FLAG_OVERLAPPED旗標建立overlapped socket,然後使用overlapped socket functions ( APCs) with completion routine。請128個人帶任意個水桶,在每一條水管前,將3個水桶放於水管前等被裝水,先被裝滿水的水桶則請1人馬上來提取處理。
5/ completion port(overlapped),建立一個completion port來queue completed notification packets. 當APCs完成IO後,系統會自動的發一個complete paket到指定的completion port上,此時我們之前所建立好的IO worker thread(等候中,in alartable waiting state,監控我們指定的completion port),便會自動的活化並開始取走queue裡的packet,並依之前指定的completion port handle(此worker thread的參數),從GetQueuedCompletionStatus( )得到一些提領訊息。請128個人帶任意個水桶,在每一條水管前,將3個水桶放於水管前等被裝水,先被裝滿水的水桶則被(1至少數人, NumberOfConcurrentThreads,依cpu的數量來決定比較好)自動的放在空地某一排等被提走。同時間,有1至數個工人一直在監控這個空地,若發現有裝滿的水桶等候被提,則開始努力的提取。


When your application calls WSAAsyncSelect / WSAEventSelect Model on a socket, the socket mode is automatically changed from blocking to the non-blocking mode。

All functions that allow overlapped operation (WSASend, WSARecv, WSASendTo, WSARecvFrom, WSAIoctl) 需要socket本身也是建立為overlapped mode才有overlapped的作用:WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
若非overlapped socket使用這些function,則這些function就等同一般的blocking functions。

If both lpOverlapped and lpCompletionRoutine are NULL, the socket in this function will be treated as a nonoverlapped socket.

Tuesday, September 05, 2006

以C語言觀點理解C++的this指標/指針

◎蕭沖原創

我常用低階的語言觀點來看高階語言,我覺得那樣可以更深入的了解使用法方,堪至可以模擬出是如何實做的,可能遇到的問題。能想像Bjarne Stroustrup這個創造人當初可能是怎麼想的嗎?

這篇文章最主要的目的是要解釋為何需要用this指標?

前言:想像一下在沒有class之前,C語言程式設計師是如何達成近class模組化功能? 曾看過一些使用C開發的遊戲程式,裡面總定義了一堆的struct,當然還有一堆和這struct有關的function。在cpp裡struct和class是同樣的,都可以定義資料成員(data member)、成員函式(member function/method),僅差別在struct預設成員是public。然而在C語言裡,struct是不能有成員函式定義的,也因此C程式人員可能獨立的把方法寫在Global區塊上,是否可能模擬出把方法也嵌入struct中呢? 這樣就很像class了…請看以下我寫的演示範本:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef
struct StudentInfo
{
char *mName;
int mAgeInAc;
int mAgeInTw;
void (*mpCalc)(struct StudentInfo *self);
int (*mpGetTwAge)(struct StudentInfo *slef);
void (*mCopy)(struct StudentInfo *slef,
struct StudentInfo *src);

}STI;

/* 私人的方法 */
void ac_to_tw(STI *self)
{
self->mAgeInTw = self->mAgeInAc - 1911;
}

/*公開的方法*/
int get_tw_age(STI *self)
{
self->mpCalc(self);
return self->mAgeInTw;
}

/*公開的方法*/
void copy(STI *self, STI *src)
{
*self = *src;
}

/*建構子*/
STI create_sti_object(void)
{
STI object;
object.mName = 0;
object.mpCalc = ac_to_tw;
object.mpGetTwAge = get_tw_age;
object.mCopy = copy;
return object;
}

/*動態建構子*/
STI *p_create_sti_object(void)
{
STI *p_object = (STI *) calloc(1,sizeof(STI));
p_object->mName = 0;
p_object->mpCalc = ac_to_tw;
p_object->mpGetTwAge = get_tw_age;
p_object->mCopy = copy;
return p_object;
}

/*動態解構子*/
void destroy_sti_object(STI *self)
{
free(self);
}

#pragma argsused
int main(int argc, char* argv[])
{
int my_tw_age;
STI student_info;
STI *p_student_info;
STI my_info;
STI babe_info;
STI copy_info;

/*沒有建構式的建立法*/
student_info.mName = "蕭一世";
student_info.mAgeInAc = 1951;
student_info.mpCalc = ac_to_tw;
student_info.mpCalc(&student_info);
printf("%s 民國%d年生\n",student_info.mName,student_info.mAgeInTw);

/*使用動態建構方式,且直接呼叫私人方法*/
p_student_info = p_create_sti_object();
p_student_info->mName = "蕭二世";
p_student_info->mAgeInAc = 1961;
p_student_info->mpCalc(p_student_info);
printf("%s 民國%d年生\n",p_student_info->mName,p_student_info->mAgeInTw);
destroy_sti_object(p_student_info);

/*使用建構子,且呼叫公開方法取回值,Instance1 */
my_info = create_sti_object();
my_info.mName = "蕭沖";
my_info.mAgeInAc = 1971;
my_tw_age = my_info.mpGetTwAge(&my_info);
printf("%s 民國%d年生\n",my_info.mName,my_tw_age);

/*使用建構子,且呼叫公開方法取回值,Instance2A */
babe_info = create_sti_object();
babe_info.mName = "寶貝";
babe_info.mAgeInAc = 1976;
my_tw_age = babe_info.mpGetTwAge(&babe_info);
printf("%s 民國%d年生\n",babe_info.mName,my_tw_age);

/*使用拷備函式,Instance2B */
copy_info = create_sti_object();
copy_info.mCopy(& copy_info, &babe_info);
copy_info.mName = "寶貝拷備";
copy_info.mAgeInAc = copy_info.mAgeInAc + 7;
my_tw_age = copy_info.mpGetTwAge(& copy_info);
printf("%s 民國%d年生\n",copy_info.mName,my_tw_age);

system("pause");

return 0;
}


我們使用了函式指標來實作「方法」的部份。值得注意的部份是:從這裡我們可以看出方法的部份是共享的,僅資料的部份是各自不同(參考最後二個instance)。使用建構式是為了要把方法與Global下的function連結起來,這也是cpp中的一個新觀念-「建構子」。但在這裡還沒有實作出public與private的限制,也就是data hiding的部份。上面的方法中,都有一個參數STI *self這就是為何cpp中的method需要*this的原因了!只是cpp中把這個參數給隱藏起來,在complie時在偷偷的插入這個pointer到最前面的參數,並在method中關於data member和member function的部份加上this->,就如同上面的範例使用了self->。這樣就可以達成「Instance的方法共享,資料私有」。free後method的部份依舊存在。僅data被free了!

範例子寫了一個copy method,演示了cpp中何時你可能會把this這個關鍵字用出來,當實作copy時,你就要自己寫入 *this 這就不是compiler會幫你的了! 還有另一個可能會用到的時候: 傳回物件的reference時return *this,不過這是在reference type 是cpp裡才有的。

Saturday, September 02, 2006

386 addressing mode Note (386 定址模式)

◎蕭沖原創

看了好幾年的Assembly了,總一段時間就會翻一下,尤其是在Debug hi-level language或是做reserve engineer(dont say it crack :->)時會參考。直接用ASM來寫程式是難得有機會了,所以某些學習過的記憶也經常就忘了。對於386 addressing的部份,看過許多不同的書,分類方式總有些些不同,在此想把各種版本的精華歸納出來。

Instruction Format (微指令格式)

Lower mem addr ----------------------->Higher mem addr
prefix|opcode|mod|reg|r/m|displacement|immediate data

Prefix :1 Byte, Optional, chage the default size of opcode size field(W),or
change address size, override selector....
Opcode :1~2 Bytes micro instruction
Mod+r/m :combined to operand (運算元之一).
mod 2Byte indicate that r/m (3Bytes) is mem or reg.
Reg :3 bytes operand (另一個運算元)
Displacement : 1~ 4 Bytes (位移)
Immediate data : 1~4 Bytes (立即資料,為常數)

所謂定址法(addressing mode)就是:「CPU找資料所在位置的方法」
以Instruction Format來看就是指opcode後面的那些值,(mod+r/m, reg, disp, immediate data)。
以mov ax, bx 這個Instruction來說,就是用二個相同的暫存器定址模式(register addressing mode),也就是說資料都是存放在register裡,即ax和bx裡。整個指令的意思就是把bx裡的資料copy到ax裡(注:有人直接翻mov為"搬",這樣感覺指令結束後bx會變成空的,然而不是這樣的)。
以mov ax, [mem] 來說,則用了二種不同的定址法: 其一是上面的reg addressing,另一個則是記憶體直接模式(memory direct mode or called displacement)。意思是去mem這個位址找資料然後把資料copy到ax暫存器裡。

我的分類法:
1/ 依容器來分 2/把memory模式再依取法分 3/在把memory indirect的部份依元素來分


+-REG mode +---General REG mode
| |___Segment(selector) REG mode
+-Immediate mode
|
+-Memory mode +--Direct mode(Displacement mode)
|__Indirect mode+-Memory Indirect mode(REG indirect modeS mix)
|_REG indirect mode+- base reg indirect
|_ base reg + displacement
|_ base reg + index
|_ base reg + index + disp
|_ ESP- mode
|_ ESP+ mode


定址法與高階語言的關聯:

1/ 程式中的全域變數在compile的時候就會經過計算把變數名換成"固定"的相對記憶體位址。比如說變數gVar被轉成0x11A0000的位址,而和gVar相關的statement則會被轉成Direct mode addresing,for example:
gVar += 2; 在runtime時的ASM被轉成

add dword ptr [0x004ce408], 0x02

這指令即用了 Direct mode + Immediate mode。故我稱gVar為直間變數。

2/在函式裡編寫下面的方程:


int i = 7; //C745 F4 07000000 MOV DWORD PTR SS:[EBP-C],7
int *p_var = &i;
//8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C]
//8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
(*p_ar)++;
//8B55 F0 MOV EDX,DWORD PTR SS:[EBP-10]
//FF02 INC DWORD PTR DS:[EDX]


注解的部份是被組譯後的情形。而最後一行INC DWORD PTR DS:[EDX]則使用了Indirect mode。
也是我想強調的部份,指標最終都是使用間接模式。故我稱指標為接間接變數!
順便也解說一下上面幾行機器碼的Instruction Foramt:
C745 F4 07000000 <<-- C7是MOV 的opcode,45是mod+r/m 與reg共1Byte,F4(C的補數)為1byte的有號值,等於-12,此為displacement的部份,最後一部份是立即值07000000共4byte。
同樣是MOV微指令,但當定址方式不同,或是二個operand位置不同等,都會造成MOV這個微指令有所不同。

Thursday, August 31, 2006

BCB 上安裝 indy 9 的所有重要事項與筆記

◎蕭沖原創

重裝bcb6,不選indy安裝,更新至sp4,或者到控制台選變更設定,把Indy功能取消。然後把source 放至 c:\indy\source。

run C:\Indy\Source\Fullc6.bat
會產生 C:\Indy\Source\c6 的目錄

Component->Install package->Add 選 C:\Indy\c6\dclIndy60.bpl

tools->enviroment options->Library 中加入C:\Indy\c6,並移至最上層

project options 裡的include與libary也加入C:\Indy\c6,並移至最上層,選左下角的Default然後ok

(4.1)Indy Help 可以在此下載
http://www.indyproject.org/indy/downloads/Indy-9-0-Help-WinHelp.zip
(4.2)解壓縮以後,把所有檔案複製到以下的目錄,把原來 BCB 的檔案覆蓋
即可
C:\Program Files\Borland\CBuilder6\Help

裝好後indy60.bpl會出現一個copy在windows\system32\,(Fullc6.bat所產生的),此乃新版的bpl。若未來重裝舊版的indy時,因為bcb不會自動去replace system32下的indy60.bpl,也因此就會出現 @Idwinsock@initialization$ggrv錯誤。這時候只要去把system32下的indy60.bpl殺了,就ok了。

更新後可以將C:\Program Files\Borland\CBuilder6 下關於indy的dcu, hpp 等檔rar,然後殺了,以防不小心用到舊的

Multithread Pool and Socket Programming Note

◎蕭沖原創

Thread Pool
1/ Create map container of thread data MAY including idle property for later SetEvent, event handle property for Reset event later, thread handle property for close handle later .....by thread id as its key.
2/ Create WorkItem Queue (list for insert priority item or deque for normal queue) data with object,parameter pointer,function pointer.....
3/ Create thread function with thread map container parameter. Impelement function as : get event handler by thread id key from container, WaitForMultipleObjects for handler just get and more utra event handler like shutdown notify. Or using simple semaphore mechanism. Get WorkItem from Queue, and call funtion. Keep looping...
4/ Create threads and insert each thread data into map.
5/ Queue the WorkItem and set event handler for each idle thread in map.

One thread can create its own event object, and be controled after queue work.
Semaphore is a global object, and every thread check it to see if it's count greater than 0,
if yes, then go work, if equal to 0, then keep waiting....

Multithread Socket programming
1/ normal model is after listening imcoming request.... loop for accept call.
2/ accept function is a bloking function, ie, if there's no request, it won't return, for this reason,
process beter create one thread to create server socket and binding and listening....
3/ when accept call returned, it create a new client socket for further communication with client.
4/ create a thread to deal with this new client socket for sending msg or rcv msg....

Traditional model is just like above, it using one thread per connection. and dynamically create thread.
Threads are created and then terminated, cycling....but this may cause taking much overhead.
Using thread pool for deal with client sockets is a good ideal for saving resource.

PS.The other model is using IOCP model for more robust and scalable....

Big5 GB2312 convert 繁簡轉換

原作者: 不詳
加註 : 蕭沖

/*
蕭沖 於20051017注記
從下面的程式可以猜測出

1/ GB字集裡含有繁體字型與簡體字型
2/ GB裡的繁體字型部份轉為Unicode後便與Big5轉為Unicode後的值是一樣的
3/ 使用LCMapString函數可將GB裡的繁與簡字體做內碼映射轉換

由以上的三個重點演出如下的logical

1/ Big5 to GB 方法為:
Big5->Unicode (使用MultiByteToWideChar)
->GB繁字集(GBK)->GB簡字集(GB2312) (使用LCMapString)

2/ GB to Big5 方法為:
GB簡字集(GB2312)->GB繁字集(GBK) (使用LCMapString)
->Unicode->Big5 (使用WideCharToMultiByte)
*/
//1. 輸入Big5字符,返回Gb簡體字符
//---------------------------------------------------------------------------
//函數輸入Big5字符,返回Gb簡體字符
//---------------------------------------------------------------------------
AnsiString __fastcall Big2Gb(AnsiString sBig)
{
char* pszBig5=NULL; //Big5編碼的字符
wchar_t* wszUnicode=NULL; //Unicode編碼的字符
char* pszGbt=NULL; //Gb編碼的繁體字符
char* pszGbs=NULL; //Gb編碼的簡體字符
AnsiString sGb; //返回的字符串
int iLen=0; //需要轉換的字符數

pszBig5=sBig.c_str(); //讀入需要轉換的字符參數

//計算轉換的字符數
iLen=MultiByteToWideChar (950, 0, pszBig5, -1, NULL,0) ;
//給wszUnicode分配內存
wszUnicode=new wchar_t[iLen+1];
//轉換Big5碼到Unicode碼,使用了API函數MultiByteToWideChar
MultiByteToWideChar (950, 0, pszBig5, -1, wszUnicode,iLen);

//計算轉換的字符數
iLen=WideCharToMultiByte (936, 0, (PWSTR) wszUnicode, -1, NULL,0, NULL, NULL) ;
//給pszGbt分配內存
pszGbt=new char[iLen+1];
//給pszGbs分配內存
pszGbs=new char[iLen+1];
//轉換Unicode碼到Gb碼繁體,使用API函數WideCharToMultiByte
WideCharToMultiByte (936, 0, (PWSTR) wszUnicode, -1, pszGbt,iLen, NULL, NULL) ;

//轉換Gb碼繁體到Gb碼簡體,使用API函數LCMapString
LCMapString(0x0804,LCMAP_SIMPLIFIED_CHINESE, pszGbt, -1, pszGbs, iLen);

//返回Gb碼簡體字符
sGb=pszGbs;

//釋放內存
delete [] wszUnicode;
delete [] pszGbt;
delete [] pszGbs;

return sGb;
}

//2. 輸入Gb字符,返回Big5字符
//---------------------------------------------------------------------------
//函數輸入Gb字符,返回Big5字符
//---------------------------------------------------------------------------
AnsiString __fastcall Gb2Big(AnsiString sGb)
{
char* pszGbt=NULL; //Gb編碼的繁體字符
char* pszGbs=NULL; //Gb編碼的簡體字符
wchar_t* wszUnicode=NULL; //Unicode編碼的字符
char* pszBig5=NULL; //Big5編碼的字符
AnsiString sBig5; //返回的字符串
int iLen=0; //需要轉換的字符數


pszGbs=sGb.c_str(); //讀入需要轉換的字符參數

//計算轉換的字符數
iLen=MultiByteToWideChar (936, 0, pszGbs, -1, NULL,0) ;

//給pszGbt分配內存
pszGbt=new char[iLen*2+1];
//轉換Gb碼簡體到Gb碼繁體,使用API函數LCMapString
LCMapString(0x0804,LCMAP_TRADITIONAL_CHINESE, pszGbs, -1, pszGbt, iLen*2);

//給wszUnicode分配內存
wszUnicode=new wchar_t[iLen+1];
//轉換Gb碼到Unicode碼,使用了API函數MultiByteToWideChar
MultiByteToWideChar (936, 0, pszGbt, -1, wszUnicode,iLen);

//begin 蕭沖加改某些符號無法互轉,先手動取代!
const wchar_t wt = 0x2506;

for(int i=0;i <= iLen;i++)
{
if(wszUnicode[i]==wt)
{
wszUnicode[i]=0x2502;
}
}

//end 蕭沖修正

//計算轉換的字符數
iLen=WideCharToMultiByte (950, 0, (PWSTR) wszUnicode, -1, NULL,0, NULL, NULL) ;
//給pszBig5分配內存
pszBig5=new char[iLen+1];
//轉換Unicode碼到Big5碼,使用API函數WideCharToMultiByte
WideCharToMultiByte (950, 0, (PWSTR) wszUnicode, -1, pszBig5,iLen, NULL, NULL) ;

//返回Big5碼字符
sBig5=pszBig5;

//釋放內存
delete [] wszUnicode;
delete [] pszGbt;
delete [] pszBig5;

return sBig5;
}


void __fastcall TForm1::Button2Click(TObject *Sender)
{
this->Edit3->Text = Gb2Big(this->Edit1->Text);
}
//---------------------------------------------------------------------------

BCB Hook VCL Message (VCL消息的攔截)

◎蕭沖原創

VCL component msg handling:
Event -> ProcessMessage (TApplication)
-> 訊息被傳到指定的VCL,如Form、Button、Label............
-> 該物件的MainWndProc (最初定義在 TWinControl,不可被override)
-> 該物件的WndProc (最初virtual定義在 TControl)
-> 該物件的Dispatch (最初virtual定義在 TObject)
-> 該物件的TWinControl.WMCommand
-> 子物件的Handler (但方法是定義在父類上的)
-> 該物件或子物件的DefaultHandler


A. Hook MSG at WinMain:
1/用Application的OnMessage處理POST的訊息,但對於某些由系統直接send的msg或是user用SendMessage方法傳的msg是無法被trigger的。
1.1/ Application->Run裡的Loop中包含ProcessMessage這個method,而OnMessage則含在它的判別中。
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False; //稍後可能經由OnMessage而改變,進而影響DispatchMessage是否被叫
if Assigned(FOnMessage) then FOnMessage(Msg, Handled); //是否有指定OnMessage
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;

2/用HookMainWindow/UnhookMainWindow 改寫TApplication.WndProc中的訊息處理部份。


B. SubClass WndProc:
1/用SubClassing的技術SetWindowLongPtr(),指向另一個新的WndProc,這是最全面性的更改視窗類的處理函式。然後再用CallWindowProc 呼叫原WndProc,最後app結束前再用SetWindowLongPtr原來的WndProc的註冊。 (事實上vcl自己就有用,它先指向InitWindow,然後再轉向MainWndProc)



C. Override default WndProc:
1/ override WndProc 這是全面性的。在TControl裡有個property叫WindowProc,就是指向原始的WndProc,可簡單的透過這個屬性指向自己的method。

D. Override TObject.Dispatch method :

BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_XXXX, TWMXXXX, WMXXXX)
END_MESSAGE_MAP(TForm)

這個macro經preprocessing後長得像
virtual void __fastcall Dispatch(void *Message)
{
switch (((PMessage)Message)->Msg)
{
case WM_XXXX:
WMXXXX(*((TMessage *)Message));
break;

default:
TForm::Dispatch(Message); //呼叫父類別的Dispatch方法 ,類似delphi的inherited
break;

}
}

E. 指定VCL預設的On事件屬性,如OnButtonClick等至自己的handler
1/在每個元件上的on event上指定自己的handler,即一般的操作方法。


F. override TWinControl.DefaultHandler (或是透過屬性FDefWndProc指向自己的handler)。這裡可以處理user define msg的最後一個地方。

Saturday, August 26, 2006

後記Pointer(指標/指針/指位器)的宣告/定義風格

◎蕭沖原創

資料的變數是為了方便找資料(memory address 的alias)。
型別則是為了資料「取出後」該用什麼方式處理與呈現。

我們都知道在電腦世界裡,所有資料的最後結構都是010101…二進位。型別則讓我們可以分門別類的把010101的資料做不同的詮釋以呈現我們人類所可以理解的資訊。

Pointer是為了存取資料的方式不同而生,它該是屬於定址問題,也就是說它和變數是該是「同一夥」的!

Type Variable 我的說詞是: 用「直接定址法存取」,資料型別是Type,稱直接變數。
Type *Variable 用「間接定址法存取」,資料型別是Type,稱間接變數。

關於定址法,請參照組合語言(Assembly Language)。

最後被存取的資料才是我們重視的,所以最後被存取的資料型別才是我們關心的,而Pointer雖然也是資料的一部份,但放的都是位址資料,這些位址資料只是為了靈活的操作我們在意的最終資料。

我們在意的資料型別是什麼? 就是 int, char, float, double, stuct, class.....等對人有價的資料。我想,單純的pointer type對我們來說只是存取資料的過程用,並非我們最後想看的吧!

關於Pointer(指標/指針/指位器)的宣告/定義風格

作者:蕭沖
Email: qs.xiao@gmail.com
時間:20060826


許多人都在爭議c/c++的pointer的宣告/定義風格:
type *var1; // code standard 1
type* var2; // code standard 2
似乎第二種方式比較容易讓人了解,但是事實上那樣的風格並不"正確"。
使用第二種風格有一種限制: 不能連續宣告
type* var3, var4 // 只有var3是pointer,var4是一般的變數
就compiler與從assembly的觀點來看,第二種方式才是對的。

每個變數名都代表著memory的位址,變數名只是方便讓我們不用去記憶每個放資料的位址。ASM裡有幾種定址方(addressing):
1/port addressing
2/register addressing //使用register來放資料
3/immediate addressing //直接給常數值
4/memory direct addressing //使用memory的位址放資料
5/memory indirect addressing //使用registers內容所記的位址放資料

4 的方式就像我們宣告一般變數名處理資料一樣
5則像是pointer 或是 array 間接的取資料
在ASM裡,使用[ ]符號來把register包起來,表示資料是放在這個register內資料所記載的位址,而非register的內容。[register]就像是把一般的變數加上一個[ ]的符號來表示它是用間接的方式來取值!就如同C/C++中用*來表達間接取值。
3的方式亦可見於const變數。當變數定義成const時,以後的存取方式都是以直接給值的方式來組譯。我們知道const int 與 int const 是一樣的宣告,如同pointer一樣,個人覺得用int const的方式才是"對"的。而且可以容易了解 int *const ptr的意義就是唯讀指標/針。把const variable翻成立即定址法!

再用個現實例子來說明一下變數與指標:
比如說你有數個倉庫,它們的門牌號碼是100號,103號,108號。100號裡放的是褲子,103號放的是上衣,108號放的是帽子。當我要請工人去拿上衣的時候,我就會說:請去103號拿庫存。經過一陣子,我們的倉庫變更多了,有好多的門牌號,110,111,120,131…我們自己都快記不住哪個號碼是放什麼。於是我們就想了個方法,即在門前貼上貼紙: 把100號貼上一個褲子的圖,把103號貼衣服的圖,把108號貼帽子的圖…當我們想要拿褲子的時候,我們不再告訴工人去100號拿,而是告訴工人去「貼有褲子的倉庫」拿貨。
上面的例子裡,門牌就相當於memory的位址,而貼紙就像是變數名。
那什麼情形是pointer呢? 比如說我們有一批臨時的食品乾貨,當貨來到倉庫時必需因應當時的倉庫使用情形而暫放在某個門牌號,等工人把貨確定放好位子後再回報放在哪個門牌上。為了這種臨時性的需求,我們開了一個門牌號,比如說140號,並也貼了一張辨識用的「食品記錄本圖」,然而裡面不放一般的貨,只放一個記錄本,上面會有工人回報門牌的記錄。於是當我們要工人去拿這臨時貨時,我們會說:去140號查記錄本,然後再去所記錄的門號拿貨來!
這個記錄圖就是代表pointer變數。

一般的圖和「記錄本圖」有什麼不同? 就是拿貨的方式不同。看到一般圖就進倉庫直接拿貨,看到記錄本圖就只道要進去查記錄本,再到確定的地方取貨。從這裡可以了解,貨品本身是哪一類的並不重要,重要的是圖哪一種圖!不同的圖代表不同的取貨方式。因此,你可以想像為何要用
type *var 的宣告。因為var和*var是不同的,var是去var的地位直接拿,*var是到var的地方去查本取貨,僅管這二個的貨品都是同一類的。
這麼說來,你能了解為何我說 type* var是看似合理,但並非根子裡是那樣的。也因此了解為何c/c++中
type* var1, var2 只有var1被compile成pointer。
*號應該看做是變數名的Qualifier(修飾詞),而非type與type結合。

Friday, August 25, 2006

BCB6 and BCB2006 components installation guide

BCB6與BCB2006控件的安裝
--------------------------------------------------------------------------------
來源:http://www.ccrun.com 作者:不詳
一、安裝步驟:(這是廢話)
1、拿到源碼,要全部源碼,不要那種只有部分源碼的包,。
2、找到BPK文件,如果只有DPK文件,那就用DPK2BPK程序(網上下載)生成一個BPK文件,如果生成失敗,那就自己新建一個包(BPK或bdsproj),把DPK文件中包含的pas文件包含進去。
3、編譯與安裝。
二、注意事項:
1、Bpl包的重名問題,主要是不能重名,在Borland 與 system32下查找要安裝的包的名字,如果找到全刪除掉。
2、文件的重名問題,.pas.h.hpp.dcu.obj.lib.bpl.bpi.dfm.res 等文件均不應有重名現象,如果有應刪除舊版本文件。這裡介紹一個軟件完成這項工作Beyond Compare,其主頁是http://www.scootersoftware.com/
3、文件路徑的設定,編譯查找路徑,鏈接時查找庫文件的路徑,BCB2006的路徑設定會多一點,把Bcc32 Dcc32 ilink32 tasm32 等分開設定,這些均要一一設定正確,並注意先後次序對編譯器的影響。這3點注意事項要時刻想起,出了問題要先查證無誤。不要因為這些問題而讓自己陷入泥潭,那不值得。
三、編譯鏈接中的常見錯誤及排錯方法
1、找不到obj或dcu文件。看看你的硬盤是不是有這麼個文件,如果有那就是第二步所說的路徑問題。
2、如果文件沒加入項目,那是不會生成鏈接所需要的obj及lib文件的。這時要注意編譯多個包時的先後次序。
3、編譯開關控制文件,控件包代碼中的編譯開關一般放在一個文件(.inc)中,如果為pas後綴請改名。類似於下面代碼,如果在BCB2006下安裝,就要自己修改這個文件。
{$IFDEF VER180} //如果是用BDS2006編譯
{$IFDEF BCB} //BCB2006
{$DEFINE VER140} //那麼採用B6一樣的編譯開關
{$ELSE} //Delphi2006
{$DEFINE VER170} //那麼採用D9一樣的編譯開關
{$ENDIF}
{$ENDIF}
要用正確的編譯開關才能編譯正確。至於如何改,那要看具體的文件才行,這個一般也不難改。
4、關於DesignEditors這個單元的問題 , DCP文件的使用。
在BCB2006中,PAS代碼如果包含了這個DesignEditors單元,會引起編譯失敗,原因是BCB2006並沒有提供DesignEditors所要的全部源碼,這裡會提示說找不到Proxie這個文件。可是還是有方法安裝的。解決方式是通過.dcp(Delphi Compiled Packages)文件:
在 Project Options -- Pascal Compiler(Dcc32) -- Other Options 頁 ,有個use package .dcps when compling , 把其選上,同時填上所要的.dcp文件的名字,DesignEditors所需要的.dcp名字是designide(只填名稱就可以了,不要加後綴名哦),填上所要的dcp文件後,DesignEditors的問題得以解決。
在BCB6中,則是將這個dcp文件加入項目,或指定連接對應的lib文件。
5、代碼中的編譯錯誤
這裡指安裝在B2006的控件才會碰上,因為控件沒有for 2006 的,所以才有這問題,過一段時間控件升級,就沒這種問題了。
這只能是見招拆招了,沒有其他辦法。如rxlib中的變量與B2006的TControl新增屬性重名, ehlib中的因2006中TFieldType類型的增加要增加相對應的匹配項。
6、通過編譯後鏈接時出現的錯誤。 這就是眾人討厭的link av 錯誤
出現這類型錯誤時,首先要記得檢查上面說的注意事項,然後才進行後續的排錯工作。切記。這時要進行的也就是修改項目文件了。
這個在BCB6或BCB2006測試版中支持都不好,正式版還未知如何。是一個大毛病,令人心煩。如果有多個包要修改,建議使用工具UEdit32來輔助這項工作。在外部修改了包文件後要在BCB中關閉該項目重新打開後才生效,不要被BCB那個提示迷惑了。
要修改包文件,先介紹一下包文件的內容 :(Borland真應該自己做出一個工具修改自己的包文件)
bpl項目文件用XML格式,大部分一看就明白,說一下與linker有關的部分。
<LINKER>
<ALLOBJ value="c0pkg32.obj $(PACKAGES) Memmgr.Lib sysinit.obj $(OBJFILES)"/>
<ALLRES value="$(RESFILES)"/>
<ALLLIB value="$(LIBFILES) $(LIBRARIES) import32.lib cp32mti.lib"/>
<OTHERFILES value=""/>
</LINKER>
這裡: $(PACKAGES) 是指上面定義的 PACKAGES value 的全部。$(OBJFILES).ALLOBJ ALLRES 這兩項一般不出現什麼問題。
ALLLIB 這項問題多,是LINK AV錯誤的發源地之一。裡面有兩大類:$(LIBFILES) $(LIBRARIES value)
$(LIBFILES) //BCB2006是小寫,B6是大寫。libraries
$(PACKAGES) //packages
$(SPARELIBS) //sparelibs
看看裡面就知道Bolrand做了些什麼,有時會有一大堆沒用到的包與庫都放在那,這裡Borland做得真差勁。
如果你安裝了很多包,那麼存盤的時候所有的包與庫都將寫到這裡面,別人如果沒有這個包就會出錯。這問題很弱智,但Borland就是這麼做。
把你項目中不用到的包與庫刪除掉。鏈接時也就不會報找不到什麼什麼庫了。
可是還是出現link AV ,這時可能出在 libraries 項,檢查libraries項,把重名的刪除(還會出現重名的borland真是),重排一下次序。
sparelibs項如果混亂就設成與libraries一樣。如果你的項目要鏈接vclsmp.lib,手工在這加上,不少lib在IDE中加不上的都在這手工加上。
設定這裡的庫列表需要對這些lib庫的熟悉,如果不清楚,可copy一另一個項目的libraries項過來試試看。
FILELIST 項,BCB可能會有一些沒用的文件也在其中,這裡會造成找不到這文件的錯誤。檢查一下,刪除訪該項即可。如果路徑不對,也改對它吧。
我就是通過修改libraries packages sparelibs FILELIST 解決了不少link av錯誤。其他選項在project->Optons都能設定,但這幾項只有手工改文件。
注意一定要先對上面提到的注意事項仔細做一遍。
關於Uedit
用UEdit可以查找函數出現在哪個lib或bpi文件中,從而知道該鏈接哪個lib。使用哪個bpi。當做內容替換時Uedit也很體貼。正確地連接lib bpi文件及使用dcp文件,可解決使用Borland沒給出源碼的那部分單元鏈接失敗的問題(全編譯時失敗)
關於B2006下安裝可參考: http://community.csdn.net/Expert/topic/4440/4440033.xml?temp=.9818537
我把自己在處理包的一些方法寫下來,供大家參考,希望能得到指正。這方面找不到Borand的Help,就做一下拋磚引玉的動作了。這些錯誤老是令人心煩不已。每次裝控件總忙個不停很煩,這本來就不是程序設計本身的事情呀。
PS. 蕭沖補dpk2bpk的使用: 它針對BCB6來處理,若是其它版本則可用同一作者的dpk2cpp產生package用的cpp檔,然後再由BCB的IDE來自行產生一個PACKAGE,含入此cpp。

How to make dpk to install into BCB by DCC32

New switches for the Pascal DCC32.EXE compiler:
[lb] -jp switch: creates Borland C++ compatible .OBJ files.
[lb] -jph switch: creates C++Builder compatible header (.HPP) files from Object Pascal unit files (.DCL).
[lb] -jphn switch: uses the Object Pascal unit name as the enclosing C++ namespace for both .OBJs and .HPPs that are generated.
[lb] -n switch: specify .DCU output directory
[lb] -nh switch: specify .HPP output directory
[lb] -no switch: specify .OBJ output directory

Example from Indy batch file: Fullc6.bat

@echo off
if (%1)==() goto test_command
if (%1)==(start) goto start
goto endok
:test_command
if (%COMSPEC%)==() goto no_command
%COMSPEC% /E:9216 /C %0 start %1 %2 %3
goto endok
:no_command
echo No Command Interpreter found
goto endok
:start
call clean.bat
computil SetupC6
if exist setenv.bat call setenv.bat
if not exist ..\C6\*.* md ..\C6 >nul
if exist ..\C6\*.* call clean.bat ..\C6if (%NDC6%)==() goto enderror
if (%NDWINSYS%)==() goto enderror
copy *.pas ..\C6
copy *.dpk ..\C6
copy *.obj ..\C6
copy *.inc ..\C6
copy *.res ..\C6
copy *.dcr ..\C6
copy *.rsp ..\C6
if (%NDC6%)==() goto enderror
if (%NDWINSYS%)==() goto enderror
cd ..\C6
REM ***************************************************
REM Compile Runtime Package Indy60
REM ***************************************************
REM IdCompressionIntercept can never be built as part of a package. It has to be compileed separately
REM due to a DCC32 bug.
%NDC6%\bin\dcc32.exe IdCompressionIntercept.pas /O..\Source\objs /DBCB /M /H /W /JPHN -$d-l-n+p+r-s-t-w-y- %2 %3 %4
%NDC6%\bin\dcc32.exe Indy60.dpk /O..\Source\objs /DBCB /M /H /W /JPHN -$d-l-n+p+r-s-t-w-y- %2 %3 %4
if errorlevel 1 goto enderror
%NDC6%\bin\dcc32.exe IdDummyUnit.pas /LIndy60.dcp /DBCB /O..\Source\objs /M /H /W /JPHN -$d-l-n+p+r-s-t-w-y- %2 %3 %4
if errorlevel 1 goto enderror
del IdDummyUnit.dcu >nul
del IdDummyUnit.hpp >nul
del IdDummyUnit.obj >nul
%NDC6%\bin\dcc32.exe Indy60.dpk /M /DBCB /O..\Source\objs /H /W -$d-l-n+p+r-s-t-w-y- %2 %3 %4
if errorlevel 1 goto enderror
copy Indy60.bpl %NDWINSYS% >nul
del Indy60.bpl > nul
REM ***************************************************
REM Create .LIB file
REM ***************************************************
echo Creating Indy60.LIB file, please wait...
%NDC6%\bin\tlib.exe Indy60.lib /P32 @IndyWin32.rsp >nul
if exist ..\C6\Indy60.bak del ..\C6\Indy60.bak >nul
REM ***************************************************
REM Compile Design-time Package RPDT30
REM ***************************************************
%NDC6%\bin\dcc32.exe dclIndy60.dpk /DBCB /O..\Source\objs /H /W /N..\C6 /LIndy60.dcp -$d-l-n+p+r-s-t-w-y- %2 %3 %4
if errorlevel 1 goto enderror
REM ***************************************************
REM Clean-up
REM ***************************************************
del dclIndy60.dcu >nul
del dclIndy60.dcp >nul
del Indy60.dcu >nul
del *.pas > nul
del *.dpk > nul
del *.inc > nul
del *.dcr > nul
del *.rsp > nul
REM ***************************************************
REM Design-time only unit .DCU's are not needed.
REM ***************************************************
if exist IdAbout.dcu del IdAbout.dcu >nul
if exist IdDsnBaseCmpEdt.dcu del IdDsnBaseCmpEdt.dcu >nul
if exist IdDsnPropEdBinding.dcu del IdDsnPropEdBinding.dcu >nul
if exist IdDsnRegister.dcu del IdDsnRegister.dcu >nul
if exist IdRegister.dcu del IdRegister.dcu >nul
goto endok
:enderror
call clean
echo Error!
:endok
cd ..\Source


ref: http://homepages.borland.com/ccalvert/Books/BcbUnleashed/Part14.htm

Using DLL between BCB and VC++

◎蕭沖原創

僅有cdecl這種convention配合C的函式編碼(使用extern"C")才可以互用。cpp的overloading function,class等都是無法互通的!

BCB對於cdecl convention的function是會在function前面加上 _ 底線。
BCB對於stdcall 的部份則是沒加 _ 。
VC的部份則是相反。

__________________________________________________________________
呼 叫 慣 例 原 始 函 式 Borland C++Builder Microsoft Visual C++
__cdecl MyFunc_cdcel _MyFunc_cdcel MyFunc_cdcel
__stdcall MyFunc_std MyFunc_std _MyFunc_std@8
__fastcall MyFunc_fast @MyFunc_fast @MyFunc_fast@8
-----------------------------------------------------------------------


所以在製作for BCB lib from VC DLL時需要用下面的方法(針對cdcel的部份):
implib -a -c -f xxx.lib xxx.dll ::a switch force add _ alias to MS cdecl

或者可由impdef程式產生的DEF檔觀察,並進一步修正DEF檔,然後用個自的lib工具來製作lib檔。
若從VC.DLL來產生def檔,則會看到都沒有底線。此時為了要給BCB使用,則需要將dll中屬於cdecl的部份自己加上 _ 底線的alias(=號左邊),再用implib產生LIB檔,以"騙"BCB正常呼叫,但實則對應到沒有底線的DLL檔。相反的,若從BCB.DLL來產生def則會發現cdecl的部份都有 _ 底線,為了要給VC用,則要自行去底線,再由VC自帶的lib /DEF:xxx.def來產生。

PS 為了仰制name mangling,一定要使用extern "C",這樣才可以交互使用。

注意! BCB的DEF檔的alias與VC的DEF檔的alias方向似乎不同:
BCB : link phrase matching = name of the function that is exported
VC : name of the function that is exported = link phrase matching

Tuesday, August 22, 2006

windows的 stack、heap記憶體(內存)管理重點筆記

◎蕭沖原創

這些天因為看到不少網友提到stack overflow的問題,於是想寫一篇簡單但確重要點的雜記。希望大家多多指教,也期待能對一些朋友有所幫助!
1/ win32 Application的flat memory模式: 因為386機器的定址能力是32bit,故一個task/app我用僅用一個segment就足夠,即4GiB的地址力。加上使用386paging的硬體功能,每個task的segment裡都含有自己的page tables,使得每個task都可以有自己虛擬的4GiB的空間。但每個segment的base address都設定為0,即任意selector所指向的descriptor(位於GDT中)裡的base值都是0。因為windows當初要相容MIPS R4000 architecture,所以高位址的2G部份放的是kernel code,而我們的ap只能用低部份的2G,所以正確的來講,我們的程式能夠使用2G的定址能力(註:透過某些特別修改,可達3G)。
2/虛擬記憶體: 在80386處理器後,cup 支持保護模式,paging 模式。我們都知道windows會使用硬碟來充當ram使用,當ram不足的時候。這就是用paging的cpu功能。有了這個功能,os就可以讓每個process都可以使用2G的data,即使ram只有幾MB。在標題1我們有提到「定址」的這個詞,這只是說cpu的register(暫存器)有能力讓我們定出那麼大的空間。但是可以定出那麼大麼大的空間並不表示你一定先要有那麼大的ram在電腦上。定址是一回事,配置(allocate)又是另一回事,但二者都很重要。比如在dos 80286前的時代,有所謂的640k的限制(那是指定址能力先天不足),所以你就算是買了4MB的ram插在電腦上,還是浪費。回到主題,因為386以後有paging的能力,即使你的ram沒有4G,windows會把你的虛擬記憶體資料放到硬碟上。為何說是「虛擬」?就是指你不一定真的有那麼多的真實的ram。比如說你真實的ram有128MB,而你的程式超大,用到512MB,那麼當程式在跑的時候,只會把正需要的code往128MB的ram上放,而把尚不用的放(paging)在硬碟上。
3/ stack: 何謂stack? 是用來放function上auto級的變數(這樣的說法較專業),所謂auto級的就是變數是宣告在function內,它的生命(life time/ extent)在function結束後就無效了! stack的大小是由 linker來決定,以bcb為例,最大可以到0x1000000,即約16MB,你可以在project option上改。由多程式人員喜歡把object放在stack上,即用下面的宣告方式ClassT object; 這樣整個object的資料都會配在stack上,若class小還好,大則容易overflow。故一般建議用new的方式來create objcet,只留下4byte的指標在stack上。又如這些天許多玩matalab的人問到2維array overflow的問題,也是因為把array宣告在stack上所造成。要解決的方式還是一樣,把它用new的方式create到heap上(稍後會講)。但二維的動態產生array需要一些技巧,可查一下我的文章。還有,使用recursive function的人也要特別注意overfolw的問題。必要的話可以改寫演算法不要用遞回。stack的使用很方便,因為無需去自行清理,唯不要overflow就很棒!
4/ heap: 是用來動態使用記憶體的方式,使用的自由度最高,但需要自行善後清理。通常是用malloc/free或是new/delete來處理。heap在windows下可以分為二種,1為default heap,2為dynamic heap。default heap 可以是windows dll 等api使用,也可以供其他app使用,一個process僅一個default heap。如何使用這heap呢? 可透過下面的三個api來使用GlobalAlloc 或 LocalAlloc 或GetProcessHeap來使用。事實上這個heap還再細分為fixed和movable二種。一般我們都是使用fixed。而vc++的malloc等c run time就是用這個default heap。這個default heap的大小限制為何? 這是一個很重要的題目,我們下個主題講。另一種heap稱為 dynamic heap,一個process可以有好多個dynamic heaps,這個heap就全然是我們的ap自由使用的地方。它和default heap有什麼不同? dynamic heap 全都是自己程式用,沒有別的api使用。另外還有一個重要的地方是這個heap可以控制多緒(multithread)同步共享heap的管理。可由HeapCreate等相關api還有VirtualXxx api來建立。bcb本身的malloc等c run time 聽說是使用這種heap,與vc++有所不同。
5/heap size(一): 這是一個很重要的題目。到底 heap size的限制為何? 答案是… 無論是dynamic 或是 defualt heap都是限制在定址能力上,即以windwos來說,就是2G。但是奇怪了,project option的linker選項中有提到一個heap size的地方,就像stack也在那設定一樣。而這個選項裡的最大值一樣只能到0x1000000,即16MB左右。而實際上許多人都曾在程式中要求超過百mb以上的heap,怎都沒事?? 而這個設定值有啥用? 答案是 : 所謂的min size的值是指commit的值,max size是reserve的值。commit / reserve這二個詞在windows api 與vc++中常用。我大概講一下…所謂的reserve是指程式可以「要求註冊」保留虛擬位址的大小。請注意是「虛擬位址」,比個喻,就像是先向政府預定門牌號1-100號都將是我家所用,但還沒建好房子。而commit size則是每次我建房子都是以幾個門牌為單位建立,比如說,commit=4,即表每次都以4間房子來建,直到100個門牌都用完。所有windows的記憶管理都是用reserve/commit的方式在處理。都是先要求將有多少位址會用到,然後等實值用到時再依commit的大小來一單位一單位的分配實值記憶體給你用。
6/ heap size (二) : 之前有提到像vc++ 的malloc都是用heap。最早我說它的限制是2G,但剛在上面又說linker設定上只能最大是16mb,若真的是16mb,那malloc早就應該overflow n次了,很茅盾! 事實上… heap的大小是真的限制在2G,即使用都不去改option中的值,它還是2G。哇~~那那設定是假的? 不…那設定正確講是為了效能。怎說呢? 雖然最大可使用是2G,但windows並非一開始就拿2G等著你用。它是在需要的時候先行要求位址(門牌),在一一的配給你。在option上設定就好比你打電話去餐聽先定位,先定個15個位子(reserve size),並且向服務生說我們會分批進去,而每批都是3個人為單位(commit size)。服務人員想講完電話後想…我們這個時間的生意這麼好,若事先就空著15個位子,那就少賺了…於是先空3個位子,反正每一批都是3人來。於是第一批到後,餐廳再空3人位子出來等,直到15人都到了! 這種預先定位的好處就是你進去餐廳後不必多廢話,進去就入座! 很有效能!
7/ heap size(三): 把剛的比喻故事延申下去…萬一我臨時又有6個朋友也要一起去吃飯,而我剛只定了15位,餐廳能否再開6位給我呢? 當然沒問題! 只是餐聽的人員說,你要稍等一下,等我再補登記新人數後再配位子入座! 所以事實上defult heap 非靜態的只到你reserve的大小就停了,它可以再自動的變大,只是需要一點時間和資源。記住! 是可以到2G沒問題的! 而從這比喻中你應該可以了解,若是開始的時候我們就正確的預定好人數,就不會多花一點時間補登記。反過來說,即使亂預定也沒關係,都還是有位子可以座,只是要多補n次的登記時間!
8/ 何時該使用default heap,何時用dynamic heap? 這問題其實不是很重要,為了portable,用malloc/ new來處理就好,compiler會選用其中一種來處理。除了在開發multithead的程式時,當你不希望主thread中的heap被二個以上的thead同時存取,你可以用dynamic heap來建立,即用HeapCreate( )的預設建立方法。當然你也可以用這個function建立出一般的fixed的heap,只要將參數1改為HEAP_NO_SERIALIZE,就可以!
9/ 補一下commit值的重要性,若每次你new的東西是很大的,而你把commit值又設很小,那麼每new一次就要補手續n次。比如說每次要new 40k 的資料,但你commit只設4k,那…new一次就配置手續10次。當然,若你說把commit設成100k,那麼一次就搞定,但…也有個小缺點 : 剩下的配給你的真實的60k浪費在那裡了。給別的process用那60k不好嗎? 所以…設成40-50k是不是比較折衷的好設定呢?!

作者: 蕭沖 qs.xiao@gmail.com
ref : http://msdn2.microsoft.com/en-us/library/ms810603.aspx