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這個微指令有所不同。