2017年4月4日 星期二

Windows消息傳遞

http://blog.xuite.net/grimmslaw/78/53174061-SendMessage()+%E8%88%87+PostMessage()

在C++就是使用PostMessage和SendMessage
而在C#其實有另一種委派(delegate),這方面我就不熟了

所以我這邊只講PostMessage和SendMessage

基本上現代的程式都是多執行緒程式,一般在做處理時,要將運算比較多的部分移到背景執行緒去做,最後再通知主執行緒處理畫面UI的動作,這時候就會用到PostMessage和SendMessage,

當然您也可以用Timer監控一些flag做一些UI更新,但是您想想,假設我30ms觸發Timer檢查一次UI要不要更新,那1秒鐘要檢查30次,要是您要檢查很多總類的UI更新,那不就要判斷很多flag例如下面這樣

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
If(flag1)
{
// 更新某部分UI
}
If(flag2)
{
// 更新某部分UI
}
If(flag3)
{
// 更新某部分UI
}
}


這樣有壞處,就是您Timer要一直跑,再來就是您處理這麼多flag更新畫面不累嗎
因此後來就有所謂的PostMessage和SendMessage,通知主Form的WndProc處理畫面

如連結講得的
PostMessage通知完主Form處理會馬上return,當主畫面有空時會去處理接收到的消息(PostMessage一次,會處理一次)
SendMessage則是會Block住,等到送出去的消息處理完,才會執行下一行


基本上意思就是thread做工作,要處理UI時再通知主Form處理,只需要一次的動作,也比較直觀,並且不需要寫一大堆flag做同步動作

當然了thread的處理會有同步的問題要解決,基本上要先知道Critical Section或mutex來處理同步問題,礙於篇幅,這裡先不講,往後會再補充詳細

總之意思大概就是假設您將影像存在一個陣列裡,要是這時您通知主Form處理將影像陣列畫到畫面上,結果您的執行緒在while loop裡又重新將影像陣列重新new,這時您可能剛好再畫影像,結果影像陣列剛好重新new時位置換地方了,導致出錯

這種細節很討厭的,要做很多同步處理,只要沒處理好就會當機,程式根本就不能用
好啦,總之會有同步問題往後有機會會講清楚
這裡您可以參考我的範例裡面註解的地方做建置,您大概就知道用法了,

總之我會有幾個Button,您點點看,照我說的地方下中斷點,追Code看看,您就知道了
這裡就不打那麼多了,請看我的範例就好

總之Wndproc是Windows程式用來接收訊息的地方,每個元件都有一個Wndproc,但是基於方便,我們統一送給主Form的Wndproc,而不是到處送給底下的vcl元件,避免太亂

用法舉PostMessage的例子(SendMessage一樣,只是效果不同)
PostMessage(Handle, WM_EVENT, event, (DWORD)data);
其中Handle是您要處理的主Form
WM_EVENT是自訂義的東西,用來識別您要接收的事件

#define WM_EVENT WM_USER + 10000

Event就是您的事件放在lparam,data,放您要送的變數或指標放在rparam,用以傳遞訊息
總之送過去,您只要照我這樣寫,對應的處理函式會被觸發,您就可以在那邊處理UI的東西了

我這只提供MESSAGE_MAP法,其實您可以virtual繼承Wndproc來觸發您要執行的事件,意思是相同的

這裡要提到的是,照理講,我裡面Button7在執行緒執行UI操作,應該會當掉的,不知道為什麼在XE10沒當…

其實在執行緒執行UI不是每次都會當的,他是有機會當掉,例如兩個執行緒同時存取相同VCL元件,

我範例裡面有6個按鈕5~10,您如果隨便點不點7,10,影像顯示正常,

要是您隨便點的話,例如7,9快速點(先把我連續post_event註解打開,讓他比較容易打架,不然您改成兩個執行緒,用while loop一直畫),過一陣子TImage顯示會異常,

也就是兩個執行緒不能同步更新TImage的,還是乖乖通知主Form更新吧

範例連結
https://drive.google.com/open?id=0B6u5oifJv3EKV184Smh3SFdYelU






template實例化和typedef用法

http://stackoverflow.com/questions/115703/storing-c-template-function-definitions-in-a-cpp-file
http://www.codeproject.com/Articles/48575/How-to-define-a-template-class-in-a-h-file-and-imp
主要是參考上面兩篇,其中第二篇的方法3才是我要用的方法

先說想法,我們知道template可以省掉非常多的寫Code量,舉例來說,我們生成一個不同類型的Point,也就是XY值,假設您要生成有char類型的,short int類型的,int類型的或是double類型的結構,

您看這樣就要寫四份相似的class,替每個結構弄一個class,新增一個function要改每份一次,修改一個function每份也要調一次,真的會累死

註:至於點為什麼要那麼多類型,因為您會發現vcl元件mouse move時的XY是int,但是您會發現您在做影像處理運算時,點的類型要用double,因為您要直接用點做運算

這樣其實Coding量會很大,因此使用Template可以減少Coding量,上面的問題您只要寫一個template class就可以完成了,看這個之前,我當作您已經知道template再幹嘛了,因為我這裡不是要講template怎麼用,而是要講其他問題

如此使用template可以減少工作量,這不是很好嗎,但是衍伸問題

Template實作只能寫在.h中,這會造成我們要是要修改template的東西時,會一直修改.h檔,導致編譯時有include到這個檔案的會跟著重新編譯,導致編譯時間變長,要是您這個東西很常用,您會發現您常常要要同時編譯很多.cpp檔,程式才可以Link

這不是一個好做法,因此我們就會想有沒有辦法能將實作的地方改到.cpp檔呢

其實就是template實例化(template instantiation)

假設我們做出template,這是一般寫法
point.h
template <typename T>
struct hahaha_point
{
public:
T x_;
T y_;
public:
hahaha_point(){}
~hahaha_point(){}
Void function(){};
};


您會發現上面的函式都必須在.h修改,不然compiler完會Link不到東西,例如下面這樣
point.h
template <typename T>
struct hahaha_point
{
public:
T x_;
T y_;
public:
hahaha_point();
~hahaha_point();
Void function();
};

Point.cpp
template <typename T>
hahaha_point<T>::hahaha_point()
{
x_ = 0;
y_ = 0;
}

template <typename T>
hahaha_point<T>::~hahaha_point()
{
}

template <typename T>
void hahaha_point<T>::function()
{
}


當您有使用到上面的函數時,例如function,會找不到function(unresolved external)
因此我們在.cpp最後面加入下面(記得一定要寫在同一個.cpp,不然一樣unresolved external)
template class hahaha_point<int>;
template class hahaha_point<float>;
template class hahaha_point<double>;


這樣就完成了,往後您在使用時,如下

Hahaha_point<int> a;
a.function();
就不會發生unresolved external的問題了

而且最重要的是,您要修改實作內容,例如改function的內容,只要在.cpp更改,會比較方便,不會案子做大了一直編譯一直改,很累的:D

附帶一提,為了方便可以將template的類型在.h定義
typedef hahaha_point<int> __POINT_32S;
typedef hahaha_point<float> __POINT_32F;
typedef hahaha_point<double> __POINT_64F;

typedef __POINT_32S POINT_I;
typedef __POINT_32F POINT_F;
typedef __POINT_64F POINT_D;


這樣往後您就可以用比較短的字做宣告,例如
POINT_I pt;
Pt.function();

方便吧,

雖然您使用一種新類型要template實例化一種,但是只需做一次,往後改時還是比較方便的

2016年11月24日 星期四

開發的函式物件導向化2(CreateThread)

如之前講的,字太多很累,而且這裡是草稿,所以我就直接上傳Word和範例了

當然是我自己做的,所以版權所有,請勿亂傳
主要是講如何製作Thread,並將它物件導向化

連結如下
內容:
https://drive.google.com/open?id=0B6u5oifJv3EKdGZiY0YyZlQwQkk
範例:
https://drive.google.com/open?id=0B6u5oifJv3EKejhScHBmM1d4bE0

寫程式的好習慣

總之就是多加註解了,其實上班以後,如果您有做案子的話,通常都一個案子好幾萬行,想想各個地方都是不同的想法,要是忙起來又接兩三個案子,來回不同的切換,就算您是功力很深厚的,怎麼可能都記的住,

也就是所謂的註解可能記不住,不註解更不可能記住

其實註解也是一門學問的,學問的意思不是說您要註解的很深奧或很文言,他其實傾向直接明白,您當時想什麼,有什麼特別構想或者是為什麼要那樣設計,就直白的寫上去,通常在註解時不要太多專業名詞,除非您是給做很久的人看的,他對專業名詞有基本認識,
想想如果您是初學者,我設計一個模組是用來搭架構的,例如底層建置好,您只要繼承我這個class的某個virtual function,他就會自動的在執行緒執行,

想想我這段話,要是看的人不知道執行緒是什麼,那是不是就卡住了?

就算您知道執行緒是什麼,我用一個專業名詞virtual function,沒學過AOOP的是不是又卡住了?
跟您說他會自動執行,我沒給範例,您怎麼知道真的他會在執行緒自動執行?
要是我再把這個class包成lib並把.cpp藏起來,您真的知道怎麼用嗎?
想想每碰到一個地方都是一個關卡,要是我註釋再改成英文,英文不好的不就又死了?

又或者是其實在公司上班,假設這是一個個人的東西,結果我懶或者是為了保密,根本沒加註釋,那是不是每個人都要來問我才會使用呢?
所以您看完這段會了解到,其實如果您想要快速的成長或者是有一群相同向前的同伴(要確保大家都很努力拉,當然也是要賺到錢),並且以最快速度上手並完成案子,

其實到最後很多東西是解密的,就是lib附.cpp,並提供看得懂的註釋,有時間要提供該模組的範例程式,並且有實際應用的案子可以給人參考,甚至,人家來問時,通常有時候我們會做一些拖拖拉拉的回應,為了拖人家時間或者是讓人家知難而退,故意不教人家,這也是不行的,要求效率要以最直白的方式,讓人家上手

這些就是所謂的好習慣

寫Code是一件很累的是,一個經典的function您要靠自己想出來,可能要一兩年一直修改,還是都有可能失敗做不完整,

想想一個大型案子(除非您做小的案子,但是往往小的案子不賺錢),通常要數10個經典算法或者是好的設計才做得出來,

您卡別人人家再卡您,案子是做不好的,但其實如果您是該環境的技術上游0.0有時候還是要保護自己,避免東西被人家挖走
總之,我們還是維持人性本善,通常東西只要用出來,您最好不要有再藏起來的想法,因為會發生我前面描述的那些,導致大家效率不好,團隊不賺錢,您怎麼拿到比較多的錢呢?

這是基本想法,當然一些技術領先還是要藏起來啦@@,不然就您的案子有用,其他案子都不要開發相關題目,這樣也是可以,但是往往不同案子會有相同的需求,其實藏不住的,

而且其實一般趕案子,一些模組不可能完整或者是沒有Bug,保密到最後只會讓大家陷入負向循環(除非您已經不想向前衝了,不然負向循環是一件很可怕的事)

例如您藏程式碼,人家用到來問您,結果您花額外時間回答很多人相同問題,結果您也有案子壓力,一急做不好,做不好東西有問題,人家又找您協助,你會發現一直窮忙,很糟糕的

所以第一件事,仔細評估要拿多少東西出來,可以讓環境有正向成長,通常講這樣就是全部拿出來拉,不想拿不如就不要使用,不要找其他人跟您一起偕同做事
這算是一個好習慣

再者我們寫東西時要把可以reuse的東西模組化,讓他可以重複使用,也就是您花一年的努力的成果,下一個人使用可能花5分鐘就可以達到相同效果,這樣團隊獲利才會比較快速

另外我們設計東西時,要規劃後面可能要做的事情…幹,有蟑螂,租到蟑螂屋…哭哭

總之就是設計的任何東西要有預先保留擴展的可能,例如您拉一個參數窗口,總不能拉的小小的,並且您的頁面剛剛好擺滿,要使用的參數,通常會建置一些分頁(例如PageControl),將參數分群在不同頁面,讓往後您需要新增參數時,可以有地方放,這也是要注意的

不只這個,例如您新增光源模組,您要考慮未來是否會有多組光源要控制,如果會有的話,記得模組化您的光源程式控制片段,並且使用一些可以調整組數的結構,例如使用vector,這樣往後要多幾組有可花比較小的力氣做新增,

您沒做上面這些準備當然沒關係,但是往往您下次要修改時,您可能要花很多倍的時間,寫這個功能,其實多做一點設計和準備是好事,儘管您目前交件時沒用到

註解,模組化,不要太摳,預留備載以利往後修改,總之最重要的就是,做事直接明白一點,不夠直白,喜歡拐彎抹角,往往您寫出來的東西,也會拐彎抹角,Code人家不好接手,您藏一個提示,對其他接手維護的人會增加困擾,少一個重要註解,人家要多花一段時間看您在寫什麼,想想要是您同伴也這麼做,陷入負向循環是遲早的事…不要不信邪喔
基本上多想兩分鐘,做一些比較完善的構思,再開始執行,會比您塗塗改改要容易多了,而且其實剛開始會看起來做事速度比較慢,您東西做大後您會發現人家會操死,而您有事前規劃會比較好做事(除非您只是要接小案子維生爾以,東西改完下個案子又是打掉重練)

四種型態轉換

http://windsplife.blogspot.tw/2010/09/cstaticcast-dynamiccast-reinterpretcast.html
http://ot-note.logdown.com/posts/173174/note-cpp-named-type-convertion

有四種static_cast,dynamic_cast,reinterpret_cast,const_cast

其實網頁上寫的應該都是很詳細了
所以這裡我只是就我認知寫一下,

Static_cast<T>exp是靜態轉換
它等同於(T)exp

直接舉例好了
int a = 5;
double b = (int)a; or
double b = static_cast<int>(a);


總之主要就是算術轉型

一般用這個大部分都是用來做算術轉型

雖然他也可以用來轉型指標,但是不保證安全,尤其在多重繼承的情況下,幾乎是很容易出錯,也就是失敗

而主要指標一般有需要用來轉型,一般是在一般的變數結構裡

例如一個一維陣列
char* rrr = new char[100 * 3];
就是一個一維陣列存RGB存100筆
一般我們可以宣告一個結構去接他

Struct aaa
{
       Char r;
       Char g;
       Char b;
};

Aaa* temp = (aaa*)rrr;


這樣只要temp[xx].r,就可以很明瞭的作存取值的動作

一般做繼承的class的指標轉換會用dynamic_cast去轉,但是網頁上有說dynamic_cast轉class,class內必須有virtual function才會成功

總之要是有多層結構,記得用dynamic_cast去轉換,轉失敗他會回傳NULL,

雖然說dynamic_cast需要做一些檢查,但是其實在繼承的架構底下,指標轉來轉去,沒用這個基本上會莫名其妙的錯誤,所以有時候還是要乖乖地做轉換


剩餘兩個其實比較不常用
reinterpret_cast就是強制轉換,反正轉換完他就用新的類型的bit結構去處理

例如:

int* i;
char* str = "test";
i = reinterpret_cast<int*>(str);

您會發現*i讀出會是134514704


總之就是強制bit轉換,轉換後直接把原來的bit樣子用轉換後的類型去解讀

至於const_cast其實我也不常用,總之網頁上是將const類型轉換為一般類型,通常會用const都是為了做保護,把他轉換為可讀寫的,一般容易亂,建議少用

virtual和override和final

http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=547

如網頁上寫的,其實很清楚了

我就講一下virtual的注意地方,其實就是雖然virtual可以寫成虛函式,但是其實有時候型態轉換錯誤,或是不小心存取到虛函式時,還是會出錯,所以我為了避免跳出錯誤訊息,其實就算是用來被繼承而已的虛函式,其實我也會把他實例化(在cpp),也就是寫個他的實體函式,這樣就算錯誤時他也只會執行到空函式,提供給您們參考

總之virtual很好用的,
class CA
{
public:
       virtual void func1(int a)
       {
               ////
               // 其他程式片段
               ////
       }
       virtual void func2(int a)
       {
               ////
               // 其他程式片段
               ////
        }
};

class CB : public CA
{
public:
       void func1(int a) {
               CA::func1(a);
               CA::func2(a);
               ////
               // 其他程式片段
               ////
       }
};

這裡這樣寫的是,如果您繼承後,執行CB的Func1,其實CA的Func1還是存在的如您有寫處理寫在CA的Func1,您可以使用CA::func1(a);順便執行父親class的函式,這是一個小技巧

其實在寫很多繼承架構時,會用到這個,當然其他父class函式也是可以存取的,總之就加CA::其他父class函式,只要您滿足繼承關係(就public,private,protected),要怎樣使用就怎樣使用

例外就簡單講override和final吧,其實連結裡面已經有寫很清楚了,

Override加在virtual函式後面,他會檢查是否有蓋到繼承的class的函式,沒蓋到編譯會出錯,

Fianl則是該函式或是class是最後端點,不可以做繼承用,如果您繼承了,他編譯會出錯

基本上在classic Borland compiler是沒有這個的,要用新的clang c++11 compiler可以編譯過,由於後來我在處理DLL溝通時,在classic Borland compiler(32bit)和clang c++11 compiler(64bit)切換實在麻煩,就是語法上有些差異,要額外處理,因此我都改成clang c++11 compiler(32bit/64bit)了

至於CodeGuard基本上已經棄用了,我在等C++ Builder支援AddressSanitizer再來做偵錯(不確定是否會支援),基本上只要少直接用new指標傳來傳去,基本上不太會會出現沒有delete到的狀況,而且說實在的,其實如果動態產生指標傳來傳去,其實CodeGuard好像也抓不到,所以基本上CodeGuard對我個人用處不是很大(重點CodeGuard好像沒維護了,其實案子一大,有時候跑一跑會程式會出錯,總之感覺用處不大)

而最近其實有測試了Intel Inspector,他在XE10.1 berlin的64bit環境是可以抓記憶體錯誤的(32bit不行),有空可以試試看