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