Feedback
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 18:12 by
@OwnWaterloo
回避的是虛函數。舉個例子
class Base<Derived>
{
public void NeedToInvokeOnDraw()
{
((Derived*)this)->OnDraw();
}
public void OnDraw(){}//空實現
}
class Derived1 : public Base<Derived1>
{
public void OnDraw(){}//我有新OnDraw
}
class Derived2 : public Base<Derived2>
{
//我用舊OnDraw
}
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 18:13 by
@OwnWaterloo
如何在屬性實現狀態變化控制的技術嘛,沒有說過命名……看來我們是互相誤解了啊……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 18:14 by
@cexer
如果你的庫不用dll+lib+h提供的話,請盡情使用模板……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 18:16 by
@陳梓瀚(vczh)
@cexer
我上面也說了, 這和普通的繼承實現的功能是完全一樣的。
依然沒有虛函數開銷,依然有一點點"多態"。
用vczh的例子來說:
class Base
{
void OnDraw() { 空 }
};
class Derived1 : public Base
{
void OnDraw() { 我有新OnDraw }
};
class Derived2 : public Base
{
// 我用默認Draw
};
不也一樣嗎? 除了Base現在是一個類,而不是類模板。
所以,我現在唯一想出的ATL-style繼承的優勢就是:header-only。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 18:20 by
@陳梓瀚(vczh)
【如果你的庫不用dll+lib+h提供的話,請盡情使用模板】……
如果ATL-style繼承的好處就是為了header-only。
class Base
{
void OnDraw() { /* inline */ }
};
或者:
base.h
class Base
{
void OnDraw();
};
base.inl
#include "base.h"
inline void Base::OnDraw() {}
也可以是header-only ……
那么,ATL-style繼承,還有什么好處么……
一開始,我也覺得很神奇…… 后來越想越不是那么回事…… 然后一直沒想通……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 18:39 by
@cexer
管飯的人呢……
這問題反復困擾我很久了…… 一直沒得到解答…… 于是塵封了……
用ATL/WTL的人很少,問都找不到人問……
今天終于找到管飯的了, 一定得追問到底~_~
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 18:51 by
@陳梓瀚(vczh)
【
如何在屬性實現狀態變化控制的技術嘛,沒有說過命名……看來我們是互相誤解了啊……
】
那你也說說你的想法嘛……
比如那個“效率問題”應該怎么理解?
有人說可以拿C/C++程序員作巴浦洛夫實驗,看提到"效率"2字時他們是否會流口水……
我沒有流口水…… 但是我很好奇……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 19:48 by
@OwnWaterloo
撇開論點不講,其實我對語法最感興趣……因為我做編譯器……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 19:49 by
@OwnWaterloo
header-only已經不錯了,我現在最煩一個類要將界面重復在兩個文件里面。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 19:51 by
@OwnWaterloo
話說我想嘗試自己實現BOOST_ITERATE(只用宏復制代碼)的方法,最后研究了好久,發現用C的宏是不對的,應該自己制造代碼生成器……C的宏缺乏語法上的可維護性
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 19:55 by
我是來看神仙打架的
cexer別刪 哈哈
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 20:09 by
@陳梓瀚(vczh)
【其實我對語法最感興趣……因為我做編譯器……】
能否做一個,呃,怎么說呢……
visual assistant你肯定用的吧?類似的還有:
msvc,gcc :
synedit :
http://synedit.sourceforge.net/scintilla :
http://www.scintilla.org/還有一些C/C++源代碼轉換到html、pdf等格式的工具。
它們肯定都能分析C/C++源代碼。
但是源代碼分析后的結果,它們自產自銷了,沒有暴露出來。
你能否做做這方面的工作呢? 一個C/C++(或者其他語言)的源代碼分析器,將分析的結果暴露出來。
方便其他用途使用,比如代碼染色。
這樣,很多編輯器、網頁、甚至是pdf、doc都可以得益于你的工作。
而不是每個工具都自己實現一套(有的功能薄弱得……比如網頁上的染色,基本就只能認識語言的關鍵字而已),然后自產自銷。
你覺得這個提議怎樣?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 20:13 by
@陳梓瀚(vczh)
【header-only已經不錯了,我現在最煩一個類要將界面重復在兩個文件里面。】
頭文件不行嗎?為什么會這樣?
【
話說我想嘗試自己實現BOOST_ITERATE(只用宏復制代碼)的方法,最后研究了好久,發現用C的宏是不對的,應該自己制造代碼生成器……C的宏缺乏語法上的可維護性
】
我也覺得宏很不可靠……
有時候完全可以利用其他一些很方便的語言,來產生C/C++源代碼。
憑借C/C++自身機制來進行元編程,有時候會很…… 很麻煩……
比如boost.pp,它用來產生模板類型參數列表的這個功能,其實也可以寫一些其他語言的代碼,用來生成C++源代碼……
可讀性應該會好很多……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 20:16 by
@OwnWaterloo
模板有一大優勢就是,不必在意類型是否已經存在,就能夠任意調用它的任意成員。這是用虛函數也達不到的,因為虛函數也至少需要提供接口聲明。
ATL這種方式,可以在不知道子類為何物的情況下調用它重寫或者覆蓋的成員。有時候可以完成用虛函數也無法達到的效果。
你試試不用虛函數,不用ATL的這種編譯時的多態手法,在不知道子類為何物的情況下,在基類當中調用子類的方法試試?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 20:29 by
@cexer
你說的是這種情況么?
non-template
class B
{
virtual void f_do() = 0;
void f()
{
... // 有一些其他工作,不僅僅是轉發
f_do();
...
}
};
B可以自己不實現f_do,并且具有這么一個限制:如果派生類不實現f_do,派生類將繼續無法實例化。
ATL-style繼承,提供"類似"的含義。
template<class D>
class B
{
void f()
{
... // 有一些其他工作,不僅僅是轉發
static_cast<D*>(this)->f_do();
...
}
};
class D : public B<D>
{
void f_do() { }
}
如果不實現f_do,D就會產生編譯錯誤。
類似于如果上面那種D如果不定義f_do就無法實例化。
普通繼承無法提供這種限制語意:
class B
{
void f()
{
... // 有一些其他工作,不僅僅是轉發
f_do();
...
}
// void f_do();
// 如果提供,派生類不不覆蓋也能實例化,也不會產生編譯錯誤。
// 無法達到限制"派生類必須提供"的目的
// 如果不提供,B就會產生鏈接錯誤。
};
是這樣嗎?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 20:37 by
@cexer
【你試試不用虛函數,不用ATL的這種編譯時的多態手法,在不知道子類為何物的情況下,在基類當中調用子類的方法試試? 】
應該是不行的。
只有template這種變態代碼生成器才可以:
template<typename T>
void f(T v)
{
v.f(); // 讓你先這么著了,等實例化的時候再做檢查……
}
如果不是模板,父類是不能調用子類函數的。
那么,ATL-style繼承的作用就是:實現靜態的"template method"?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 21:08 by
@cexer
有點感覺了。
ATL-style繼承,一方面有點類類似concepts或者說ducking type的使用。
template<class D>
class B
{
void f()
{
D* d = static_cast<B*>(this);
d->must_have_this_function(); // 必須有某個成員函數
d->must_have_this_variable; // 必須有某個成員變量
typedef typename D::must_have_this_type type;
// 必須有某個嵌套類型
}
};
B可以對D提出一些要求,D必須滿足這些要求。
對這些要求,B可以不必提供"默認實現"。
這2點就是和普通繼承很不相同的地方了。
同時,ATL并沒有通過"組合"來復用庫代碼,而是通過"共有實現繼承"來復用庫的代碼。
使得編寫的代碼會少一些。 就像那個著名的例子:
class person : public public nose, public mouth {};
person p;
p.eat(); mouth::eat
p.smell(); nose::smell
實際上呢,更"學術氣息"一些的方案是:
class person
{
nose n_;
mouth m_;
eye e_[2]; // left and right
public:
// forwarding functions
void eat() { m_.eat(); }
void smell() { n_.smell(); }
};
但是在界面開發中…… 這些轉發函數會寫死人的……
所以,就產生了ATL-style繼承 —— concepts(ducking type) + 實現繼承的結合。
只是為ATL-style繼承舉例的人的例子沒有選好,無法充分說明它的優點。
我只粗看過《mfc程序員的wtl編程指南》還是什么的。
里面就是一個"沒有體現出base對derived的需求",可以用普通繼承改寫的例子。
這樣理解如何?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 21:13 by
@OwnWaterloo
子類不需要非得實現什么,純粹是模擬非純虛函數。因為父類必須提供所有可以覆蓋的界面的空實現,才可以讓實現控件的人,只寫他們關心的東西。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 21:15 by
@OwnWaterloo
我重寫VL++的目的就是想提供一個用來分析字符串(速度不用爆快,但肯定要好用)的庫。雖然不一定會實現一個C++的語法分析器(實際上我可以告訴你我沒法產生精確的語法樹,如果不做語義分析我肯定不知道a<b,c>d;究竟是什么東西),但上下文無關著色什么的肯定是可以輕松實現的。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 21:27 by
@陳梓瀚(vczh)
【子類不需要非得實現什么,純粹是模擬非純虛函數。因為父類必須提供所有可以覆蓋的界面的空實現,才可以讓實現控件的人,只寫他們關心的東西。】
上面已經說明了:
"父類提供所有可以覆蓋的界面的空實現",或者說默認實現,并讓實現控件的人,只寫他們關心的東西 —— 這不需要ATL-style式的繼承,普通繼承就可以了。
只有在"向子類要求一些莫須有的東西"時,才需要依靠template。
【我重寫VL++的目的就是想提供一個用來分析字符串(速度不用爆快,但肯定要好用)的庫。雖然不一定會實現一個C++的語法分析器(實際上我可以告訴你我沒法產生精確的語法樹,如果不做語義分析我肯定不知道a<b,c>d;究竟是什么東西),但上下文無關著色什么的肯定是可以輕松實現的。】
編譯原理那些東西幾乎忘光……
我覺得為編輯器著色有一個相對于編譯器來說很麻煩的地方……
編譯器可以要求源代碼必須是合乎語法的,否則就報錯。
編輯器的著色分析就不能這么暴力了,它必須能夠接受半完整的源代碼,并且盡可能從中榨取出信息……
所以visual assistant還是很強大的……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 21:44 by
@OwnWaterloo
GCC-XML,你可以搜搜這個工具,可以把C++分析成XML結構的東西。如果你需要在語言粒度上進行調整,可以在此基礎上做。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 21:47 by
@空明流轉
我靠!還有這種工具!
太感謝了~_~
其實我也不做什么的啦……
主要是寫文檔(設計文檔、博客)的時候染染色啦……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-16 22:01 by
@空明流轉
好東西啊,好東西,看得我心花怒放~_~
再次感謝~_~
順便也感謝一下cexer。
果然是【閱盡MFC,WTL,SmartWin++,win32gui,jlib2 ,VCF ;看盡天下 GUI 的感覺。】
果然只看示例和書不寫代碼是不行的。就書上的示例代碼,我還領會不了其精髓……
其實也不是我不想寫…… 我寫過一些……
然后就沒機會寫了……
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-17 17:03 by
強烈猛頂!
寫到這,我作為輪子制造愛好者,在這里向那些喊著"不要重復制造輪子的"批評家們承認錯誤。在有那么多好的輪子的情況下,我不值得浪費地球資源,浪費時間精力來自己手工重復打造。但是不值得歸不值得,在值得和喜歡之間我還是選擇后者。并且人生在世,什么才是值得?我覺得不是拯救人類,為世界和平做貢獻,也不是努力奮斗,為地球人民謀福利,而是簡單地做自己喜歡的事。
看來我和樓主是一樣類型。
我也是從封裝類似ATL框架開發,目前正在模仿jlib2...
還有很長的路要走!
向樓主學習!
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-17 18:04 by
c++源碼分析,可以利用ctags,源代碼可以看一下ctags,CodeBlocks的codecompletion,Qt Creator的cplusplus。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-17 20:36 by
@OwnWaterloo
只要你不是在為一個IDE提供著色分析,那都很好做……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-17 20:54 by
@visualfc
Qt Creator我知道,Qt Creator的cplusplus是指的什么?
Qt Creator的自動完成功能嗎?
我看過你的一些作品,果然對這方面的東西了解很多~_~
@陳梓瀚(vczh)
其實我的需求上面也有說…… 就是將代碼染染色……
不然直接在blog上貼代碼太丑陋了……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-17 21:23 by
CPlusPlus是Qt Creator中用于分析C++源文件的核心庫
代碼位于 Qt Creator源碼的 src/libs/cplusplus/目錄下
其中 C++ 語法分析/錯誤檢查部分使用標準C++庫完成,不使用QT庫。
我也是剛開始看。
對于CodeBlocks的C++源碼分析可以參看下面的wiki。
http://wiki.codeblocks.org/index.php?title=Code_Completion_Design
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-17 21:28 by
@visualfc
謝謝分享~_~
英文…… sigh……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-17 21:42 by
@OwnWaterloo
:-)
QT的cplusplus寫的要比CB的好。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 04:18 by
差不多敲定方案了,貼下代碼吧:
class date
{
class impl
{
time_t time_;
friend class date;
};
int get_(int tm::*field) const { return localtime(&impl_.time_)->*field; }
date& set_(int tm::*field,int v)
{
tm t = *localtime(&impl_.time_);
t.*field = v;
impl_.time_ = mktime(&t);
return *this;
}
public:
date() { time(&impl_.time_); }
int get_hour() const { return get_(&tm::tm_hour); }
date& set_hour(int h) { return set_(&tm::tm_hour,h); }
int get_minute() const { return get_(&tm::tm_min); }
date& set_minute(int m) { return set_(&tm::tm_min,m); }
int get_second() const { return get_(&tm::tm_sec); }
date& set_second(int s) { return set_(&tm::tm_sec,s); }
union
{
impl impl_;
property::proxy
<property::get<date,int,&date::get_hour>
> hour_read;
property::proxy
<property::set<date,date&,int,&date::set_hour>
> hour_write;
property::proxy
<property::get<date,int,&date::get_minute>
,property::set<date,date&,int,&date::set_minute>
> minute; // read write
property::proxy
<property::set<date,date&,int,&date::set_second>
,property::get<date,int,&date::get_second>
> second; // read write
};
};
int main()
{
typedef int ASSERT_EQUAL_SIZE[sizeof(date)==sizeof(time_t)?1:-1];
date d;
volatile int v = 1212;
v = d.hour_read;
d.hour_write = v;
// v = d.hour_write;
// d.hour_read = v;
v = d.minute;
d.minute = v;
v = d.second;
d.second = v;
return 0;
}
這次首先重點show出來:
1. 0代價實現proxy
typedef int ASSERT_EQUAL_SIZE[sizeof(date)==sizeof(time_t)?1:-1];
2. 定義property的語法也還不算太壞吧?
使用一種"named template parameter"技巧,將read、writer、read-write合體。
hour_read, hour_write不用解釋了。
second,minute需要解釋一下:set和get出現在proxy的參數中出現的次序是隨意的。
所謂named parameter是也。
不要拿hour_read用于寫或者拿hour_write用于讀,報出的錯誤可能很難讀。
有空閑再處理報錯好看問題吧……
proxy的其他技巧有請vczh同學介紹。
關于union中不能放非pod的問題……
1. 基于C-API來構造自己的類
那自然沒什么問題。
如果還想進行訪問控制,就像date::impl一樣,包到一個private nested class中,并設置外部class為friend。
依然0代價。
2. 基于現有的非pod來構造自己的類
樓上已經提到了union base和union member兩種方案。
不過property::proxy不支持這種用法,得手工實現……
遇到的問題也是樓上提到的"先雞還是先蛋"……
為了解決雞蛋問題,想了很久都沒想出可以很清晰定義property的方案……
換種思路:使用boost.aligned_storage + placement new + 直接調用析構函數吧……
也算一種解決辦法了…… 而且也是0代價的。
玩具代碼,僅為展示上面的2個重點技術而已。實際應用還需自行編寫。
property我以前也沒用過…… 沒什么經驗,不知道通常會怎么使用。
測試編譯器:msvc8、9、10,gcc3.4.x。
寫命名模板參數就沒打算支持vc6…… 不測了……
gcc4.x懶得切換系統了…… 應該沒什么問題……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 04:43 by
為了避免vczh同學誤會……
我再申明一下……
1. 這是徹底的玩具代碼
所以對日期的合法性不作檢查……
2. 代碼還需在頂部加入兩行:
#include <time.h>
#include "property.hpp"
才算完整
3. "property.hpp"
的搜索路徑需要自己配置,或者就和date.cpp放一起。
4. 需要在<time.h>前加入:
#define _CRT_SECURE_NO_DEPRECATE
才能避免msvc上常見的4996警告。
5. 或者另一種方式,代碼行數較多:
#ifdef _MSC_VER
#pragma warning(disable: 4996)
#endif
6. main的return 0可以省略
因為一定是C++代碼,也不用兼容vc6
能想到的我都說了……
你就繞了我吧……
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 09:46 by
@ OwnWaterloo
@ vczh
(注:排名不分先后,按字母順序)
你們兩個都是超級牛人!!我看你們的爭論時,更多的是自卑感!
因為你們討論的很多東西我看不懂!!
唉!!
我把郁悶發在這里了,有空就過去給我簽個名吧。
http://topic.csdn.net/u/20091117/19/54a04541-094f-4d7c-960e-c0ce34783821.html
兩位不要傷了和氣!技術問題,有爭論總是好的!!
我就在旁邊學習好了...
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 09:51 by
@OwnWaterloo
好長的回帖,年度最強了吧?
看了一下,似乎
union
{
class
{
time_t time_;
};
....
};
就可以了吧?impl感覺多余了。當然我沒有試,在上班,嘿。
好像前面OwnWaterloo提到過隱藏union中成員問題,
像上面那樣寫應該可以吧?
如果非要依賴標準cpp來實現某些東西,例如屬性、委托,動則搞個幾千行,那我就寧愿承認自己無能,用VC的擴展了,哈哈
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 09:54 by
@ OwnWaterloo
能否QQ、gmail、Live聯系?有問題請教。
我的QQ:1090833
我的GMail、Live:loaden AT gmail or live dot com
之前在gmail是向vczh請教時,受益匪淺。
想結識牛人:不會啰嗦,只想在關鍵的疑惑上得到點撥。
@ cexer
非常期待你的GUI設計。
我現在傾向于使用thunk封裝,但由于還碰到很多問題,沒辦法拿出來討論。
非常期待能與你在QQ or GMail or Live上探討GUI框架設計。
我目前實現的消息映射是這個樣子:
[code]class MainFrm : public Frame
{
public:
MainFrm() : Frame(_T("Test Demo!"), _T("MainFrame")), m_testBtn(_T("Test!"))
{
Add(&m_testBtn);
m_testBtn.EvtLButtonDown.Bind(this, &MainFrm::OnLBtnClicked);
this->EvtCreate.Bind(this, &MainFrm::OnCreate);
}
~MainFrm()
{
this->EvtCreate.UnBind(this, &MainFrm::OnCreate);
}
qpEvt OnCreate(evt::Create& evt)
{
evt.handled = true;
qpDbgInt(evt.handled);
}
qpEvt OnLBtnClicked(evt::LButtonDown& evt)
{
qpDbgInt(evt.owner);
}
protected:
Button m_testBtn;
};[/code]
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 09:56 by
另,任何一個類如果要處理消息,需要這樣繼承:
class Test : public EvtReceiver<Test>
{
public:
Test();
~Test();
void UnBind();
private:
qpEvt OnCreate(evt::Create& evt);
};
然后這樣注冊:
Test::Test()
{
FrameEvt *sender = App::Module().GetEvtSender<FrameEvt>(_T("MainFrame"));
if (sender != NULL)
{
sender->EvtCreate.Bind(this, &Test::OnCreate);
}
}
Test::~Test()
{
}
void Test::UnBind()
{
FrameEvt *sender = App::Module().GetEvtSender<FrameEvt>(_T("MainFrame"));
if (sender != NULL)
{
sender->EvtCreate.UnBind(this, &Test::OnCreate);
}
}
qpEvt Test::OnCreate(evt::Create& evt)
{
qpDbgInt(evt.handled);
}
但其中:
FrameEvt *sender = App::Module().GetEvtSender<FrameEvt>(_T("MainFrame"));
是極度齷齪的!可是我找不到更多的辦法了。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 09:57 by
其中qpEvt的定義:void __stdcall
因為使用thunk,所以回調函數必須是__stdcall的調用約定。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 12:19 by
@矩陣操作
union里面好像不能出現private的東西。具體得查標準……
我那個,也是權宜之計……
客戶代碼訪問不了這個類型:date::impl
但可以被推導……
template<typename T>
void set_default(T& v) { v = T(); }
date d;
set_default(d.impl_); // 掛了
不過,private也不是萬能的……
指針算術,宏,都有可能讓它失效。
取名為impl_, 應該能起到警示作用 —— 不要使用這個名字 —— 吧……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 12:38 by
@Loaden
對框架設計,我不熟…… 因為一般來說,我討厭那個……
為什么?
框架和類庫有什么區別?
類庫是幫助你的,框架是限制你的……
作為一個框架的設計者,他必須對這個領域十分了解:哪些過程是常用的,哪些是有害的。
才能準確的做出限制。
我對gui編程經驗不多,所以就不做這方面的活了……
說說win32下gui庫/框架必須解決的一個問題吧: user_data,或者叫context。
因為那個萬惡的WndProc沒有傳給你……
thunk是一種辦法,一種i386(其他平臺我不知道怎么實現)上通用的辦法 —— 只要是因為回調設計不正確,沒有帶context參數,就可以用thunk解決。
實際上…… 我畢業設計就是做的這個……
__stdcall也不是必須的…… __cdecl也可以……
比如qsort, 它就是需要一個 __cdecl的。
這里有一部分代碼:
http://code.google.com/p/callback-currying/主要是做自由函數的thunk,而不是成員函數。
一方面是可以給C程序員用。
第2個原因是因為this和成員函數指針的可移植性很糟糕。
第3個原因是因為可以通過自由函數,轉發到成員函數中,對成員函數的支持不是絕對必要的。
直接thunk成員函數有一個好處。我論文里面有提到。
返回一個大結構體的函數,直接thunk成員函數可以,thunk自由函數不行……
stdcall2stdcall 需要12字節
cdecl2cdecl 需要23字節
thiscall2stdcal(msvc) 需要10字節
其他記不清楚了
設計好thunk結構體,只是關鍵技術之一。
之后還有很多很多的麻煩…… 主要是DEP(數據執行保護)
在win32下可以用VirtualAlloc來分配可執行內存,posix下使用mmap。
但是…… 不可能為了這么小一塊內存,就去分配1整頁吧? win32下還要保留64k……
內存池是必須做的…… 而且現有內存池都無法被復用
所以,那個gcode后來就沒有維護了。
svn管理實驗代碼很麻煩…… 轉git了……
最后的版本還沒有敲定,等過年后我會發一個版本出來……
named template parameter就是為了解決callback currying中模板參數混亂(不僅是多,真的是混亂,論文里面好像有詳細解釋)而設計的。
跑題了…… 上面的中心思想是: thunk是一種通用技術,但麻煩多多。
如果僅僅是為了解決WndProc, 有專有技術。
Set/GetWindowLongPtr
還有前幾天學到的…… Set/GetProp
見這里:
http://www.shnenglu.com/kyelin/archive/2009/11/13/100880.aspx需要注意的是,Set/GetWindowLongPtr網上很多文章都說錯了。
具體的使用方式去查msdn。
cbExtra是一個0 based array —— 我記得msdn是有這么一句話的。
GWL_USERDATA是另外一回事。
用這2種方案之一就可以了(或者win32還提供了其他附加數據的方案?)。
mfc的線程局部映射表…… 哎……
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 13:36 by
@ OwnWaterloo
因為要兼容x64平臺,所以統一使用__stdcall。
原因是x64下只有__stdcall。
DEP不是問題,很容易解決:
void* operator new(size_t size)
{
return ::VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
}
void operator delete(void* p)
{
::VirtualFree(p, 0, MEM_RELEASE);
}
而:
Set/GetWindowLongPtr
還有前幾天學到的…… Set/GetProp
由于使用static函數,效率是一個大問題,而且有潛在的危險性。
謝謝你分享:callback-currying
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 13:42 by
補充一下: 關于DEP ,做thunk可能會用到。
win32可以用win32 堆(HeapAlloc那套),創建時可以指定內存權限。
這幾乎是最省心的做法了。
不過我想做posix移植(論文啊…… thunk已經爛大街,不拿點新東西出來怎么過得去……)
所以就要自己實現 ……
做著做著嘛…… 發現內存池可以再分出幾部分……
比如其中的一部分是intrusive data structure。
小對象內存池其實只是intrusive date structure的使用方式之一。
然后打算重構,然后…… 就畢業了…… 然后就無限延期了……
過年之后再做吧……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 13:44 by
@Loaden
兄弟,你知道這會分配多少嗎?
【
DEP不是問題,很容易解決:
void* operator new(size_t size)
{
return ::VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
}
】
這里用new placement也不太妥當。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 13:54 by
@Loaden
【
Set/GetWindowLongPtr
還有前幾天學到的…… Set/GetProp
由于使用static函數,效率是一個大問題,而且有潛在的危險性。
】
static 函數怎么影響效率了? 多傳遞了一次參數?
有缺陷的回調肯定是C的,參數中肯定都是bitwise-copy。
所以,還好吧……
其實你需要的就是一個context而已。
thunk, Set/Get WindowLongPtr/Prop 都是設置/得到context的手段。
thunk的效率不見得比Set/Get WindowLongPtr高,而且用著很麻煩。
WndProc(HWND hwnd,UINT msg,WPARAM w,LPARAM l)
{
CWindow* w = (CWindow*)GetWindowLongPtr( ... );
// 編譯器知道這個調用處的上下文,可以進行優化
return w->handle(hwnd, msg, w, l );
}
thunk的話,要么每個窗口對應一個窗口類,該窗口類對應一個thunk。
要么若干窗口對應同一個窗口類,每個窗口再
SetWindowLongPtr(hwnd, GWL_PROC , ) 去改窗口處理過程。
調用一個chunk是絕對得不到優化的,因為編譯器根本就不可能知道thunk的目的地在哪。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 14:10 by
@ OwnWaterloo
CWindow* w = (CWindow*)GetWindowLongPtr( ... );
這個調用不僅僅是返回一個值而已。
而且不同的窗口類,要返回不同的值。
所以,這里編譯器如何優化呢?
另外,static函數、全局函數也不利于C++封裝。
而thunk,從其匯編代碼可以看到,代價只是多花1~3條CPU指令而已。
顯然要高效。
DEP的問題,我想過內存池來實現。
不過由于技術不過硬,暫時還沒想過去實現它。
先把GUI框架穩定下來再說。
謝謝你的指點!如果有好的解決thunk的內存池(ATL就是這么干的,只是不開源),請介紹一個給我。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 14:31 by
@Loaden
忘寫了…… 聯系方式,同名gmail郵箱……
【
CWindow* w = (CWindow*)GetWindowLongPtr( ... );
這個調用不僅僅是返回一個值而已。
而且不同的窗口類,要返回不同的值。
】
你的意思應該是,不同窗口“實例”,返回不同的值,是這樣嗎?
那就對了,GetWindowLongPtr( hwnd, ... );本來就是不同窗口實例返回不同的值嘛……
【
所以,這里編譯器如何優化呢?
】
我說的優化是它下面的一個調用。
w->handle( ... ); // 可能會被inline,避免參數傳遞。
因為編譯器知道這里肯定調用的是handle,不會是其他函數。
但thunk,調用的地址是運行時的,所以編譯器無法優化。
【
DEP的問題,我想過內存池來實現。
不過由于技術不過硬,暫時還沒想過去實現它。
先把GUI框架穩定下來再說。
謝謝你的指點!如果有好的解決thunk的內存池(ATL就是這么干的,只是不開源),請介紹一個給我。
】
其實 …… atl的源代碼是能看到的…… 至少thunk部分是這樣。
我上面有一個回帖, 估計你看掉了。
如果只想支持win32, 可以用HeapAlloc那套。
一個版本中的atl就是這么干的,還有個版本是自己實現的池 —— 具體記不太清楚了。
如果想在posix平臺下,得到分配可執行內存的高效的池 ……
等過年吧,哈哈哈
【
而thunk,從其匯編代碼可以看到,代價只是多花1~3條CPU指令而已。
顯然要高效。
】
你看到thunk的分配了嗎? 考慮到thunk的復雜性了嗎?
要全局的看~_~
cbExtra可以被實現得很高效。當然,我只是猜……
【
另外,static函數、全局函數也不利于C++封裝。
】
free-function才是范化形式……
member-function只是free-function的一個特例。
是否封裝,是看你是否僅通過被數據認可的操作去使用數據,而不是自己去撥弄數據。
跟語法無關。 語法只是個便利。
這個話題…… 估計要扯很遠了……
你不接受很正常,我可以理解……
我只是提一下……
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 14:49 by
因為編譯器知道這里肯定調用的是handle,不會是其他函數。
但thunk,調用的地址是運行時的,所以編譯器無法優化。
-----
thunk不用調用,直接處理消息后,調用默認窗口過程。
比如:
LRESULT CALLBACK Frame::WndProc(UINT msg, WPARAM wpa, LPARAM lpa)
{
LRESULT res = 0;
bool ret = ProcessMessage(msg, wpa, lpa, res);
if (ret) return res;
if (msg == WM_SIZE && m_layout != NULL)
{
m_layout->DoLayout(0.0f, 0.0f, static_cast<float>(X_LPARAM(lpa)),
static_cast<float>(Y_LPARAM(lpa)), true);
}
switch (msg)
{
EVTCREATE(m_wnd, wpa, lpa);
}
return ::CallWindowProc(m_defWndProc, m_wnd, msg, wpa, lpa);
}
static函數...
-----
可能是個人喜好吧。
我喜歡盡最大可能的 不 在C++中使用全局函數和靜態函數。
我喜歡將什么東西都放在類里。
哪怕是無法實例化的靜態類。
我對你在前面給的“低代價”例子非常感興趣,下午準備好好學習你的例子。
謝謝!!
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 15:05 by
@Loaden
【
thunk不用調用,直接處理消息后,調用默認窗口過程。
】
肯定要調用的……
不然怎么處理消息?
只是我沒看出你的代碼中哪句是調用……
【
我喜歡將什么東西都放在類里。
】
最大的毛病:這會將優秀的算法埋葬到類中。
強調一下,是"零"代價,不是低代價~_~
union base, union member是低代價的。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 15:37 by
@ OwnWaterloo
肯定要調用的……
不然怎么處理消息?
-----
消息處理在這個宏里。
寫宏的原因是:不同消息處理類,可以通用。
// WM_CREATE
#define EVTCREATE(wnd, wpa, lpa) \
case WM_CREATE: \
{ \
evt::Create evt = {wnd, this, reinterpret_cast<LPCREATESTRUCT>(lpa), false}; \
EvtCreate(evt); \
} \
return 0;
struct Create
{
HWND wnd;
Object* owner;
LPCREATESTRUCT createStruct;
bool handled;
};
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 15:45 by
果然沒有錯誤處理是邪惡的……
經過Loaden的強力測試(其實也就隨手一測),崩了……
先上代碼:
time_t t1 = time(0);
tm t2 = *localtime(&t1);
t2.tm_hour = 348294832;
t1 = mktime(&t2);
printf("%d\n",(int)t1);
tm* pt3 = localtime(&t1);
printf("%p\n",(void*)pt3);
t2.tm_hour = 348294832; // 這里對于32位的int來說,沒有溢出。
但hour的"權值"比較重,最后轉化為time_t的表達可能會溢出。
t1 = mktime(&t2);
printf("%d\n",(int)t1);
C89要求:mktime如果不能將tm轉換到正確的time_t格式,就返回(time_t)-1。
而vc8的運行庫不是這么處理的,它會直接崩掉,而不是返回-1。
觸發的斷言是:
loctime64.c (78)
(*ptime <= _MAX_TIME64_T)
所以,在vc8下,mktime那行代碼就會崩。
在vc9、mingw3.4.x下,返回-1。
tm* pt3 = localtime(&t1);
printf("%p\n",(void*)pt3);
如果time_t不是正確格式,C89好像沒說應該怎么做……
vc8、vc9、mingw3.4.x一致返回空指針。
再看date的代碼:
int get_(int tm::*field) const
{
return localtime(&impl_.time_)->*field;
//當time_t不正確時,返回空指針,然后被解引用,崩。
}
date& set_(int tm::*field,int v)
{
tm t = *localtime(&impl_.time_);
t.*field = v;
impl_.time_ = mktime(&t);
// 當t格式不正確時,vc8不會返回-1,而是直接崩。
return *this;
}
錯誤處理還是需要的……
不過這僅僅是示例代碼,還是不加了吧……
以免影響清晰…… 突出重點……
感謝Loaden的測試~_~
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-18 15:56 by
@ OwnWaterloo
糾正一下,是隨手 三 測 ^)^
第三次改輸出代碼時崩潰的。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 15:59 by
@Loaden
【
#define EVTCREATE(wnd, wpa, lpa) \
case WM_CREATE: \
{ \
evt::Create evt = {wnd, this, reinterpret_cast<LPCREATESTRUCT>(lpa), false}; \
...
】
this是怎么得到的?
【
LRESULT CALLBACK Frame::WndProc(UINT msg, WPARAM wpa, LPARAM lpa)
{
...
】
這是靜態還是非靜態的? 函數定義看不出來,只有聲明才行。
估計也是非靜態的吧? 因為有m_wnd這些東西。
那么,this是怎么得到的??
追根究底,要從【注冊窗口類】時給它的【WndProc】查起。
如果你傳遞給它的不是一個真正的函數,而是一個thunk,這個thunk就會被調用。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 16:00 by
@Loaden
【
糾正一下,是隨手 三 測 ^)^
第三次改輸出代碼時崩潰的。
】
隨手1測就錯了,非常不靠譜……
隨手3測就錯了,稍微靠譜一點點……
反正代碼很不靠譜的,免責聲明我也寫了的……
出了任何問題我不負責哦~_~
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 16:06 by
不容易啊,變成水爐跟羅登在聊了……咱才走開了一個上午……工作啊……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 16:07 by
@OwnWaterloo
我就繼續做我的combinator去了,這是一個專門對付上下文無關語法分析的庫,可以在C++里面寫文法,綁定functor和error-recover來做錯誤恢復或者輸出更加貼心的錯誤信息等等。完了會搞一個小demo,也是有用的,造一套比宏順眼的代碼生成器,就用combinator做……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-18 16:08 by
@陳梓瀚(vczh)
我就一閑人……
我和羅登在gtalk上聊得更歡……
要不要來插一腳?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 00:51 by
@陳梓瀚(vczh)
唉,你們這版聊的。。。我現在還是用Python做Code Generator,這樣省心。。。不過就是測試起來很麻煩。。。。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-19 09:07 by
@ vczh
我的網名叫老鄧(非羅登^_^),曾經在Live上向你請教過GUI框架設計。
@ OwnWaterloo
【
evt::Create evt = {wnd, this, reinterpret_cast<LPCREATESTRUCT>(lpa), false}; \
...
this是怎么得到的?
】
這是一個宏替換。宏所在的類的this就是這里的this。
【
LRESULT CALLBACK Frame::WndProc(UINT msg, WPARAM wpa, LPARAM lpa)
{
...
估計也是非靜態的吧? 因為有m_wnd這些東西。
那么,this是怎么得到的??
】
非靜態。但只有三個參數,第一個參數通過匯編,寫到了m_wnd成員變量上了。沒辦法:兼容x64平臺,只能這樣thunk。
另,昨天你教了我一下午,晚上我受到了啟發,想到了一個利用bitset的方法來降低成本。
由于是動態注冊,所以運行成本增加了。
f.Get<EvtCreate>(Frame::EvtCreate)->Bind(this, &A::test);
而且,看起來不舒服。
所以,今天準備結合你的union設計,把Get模板函數搞成屬性來訪問。
如果Demo通過的話,我發gmail給你。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 17:48 by
@Loaden
thunk 完成的功能只是消息從系統到窗口類的很小一步,也是最需要完成的第一步。在這一步上我嘗試過很多方法,包括這種 thunk 技術,甚至還用過TLS做映射。我目前用的方法很簡單就是 SetProp,GetProp 兩個函數,有點重劍換木劍的感覺。效率上肯定沒有 thunk 的直接調用高,但是心里更踏實一點,在內存當中寫二進制代碼總有在犯罪的感覺。
“過早的優化是一切罪惡的根源”。基于這一步只是整個GUI框架當中是很微小的一步,幾乎不影響框架設計,可以先把框架搭好,再來從安全,效率,可移值性各個方面進行考慮。反正不要選擇 GetWindowLong ,GWLP_USERDATA 那一套,如果發布出去后客戶也使用這個東西就一切全完了,“過時的悲觀毫無益處”。
你的消息封裝看起來很舒服,肯定在 GUI 框架上也是下過很多功夫,喜歡重復制造車輪的的同志,我對這個興趣也比較大,希望以后能多與你多交流互相學習,革命路上并肩攜手一同進步!
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-19 17:50 by
我認為WTL是目前最LITE最高效,簡潔易懂,易于使用和維護的基于windows的GUI框架,其它的要么就是技巧用的太多,要么就是不直觀。要么就是效率不好。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 17:56 by
@Loaden
【
非靜態。但只有三個參數,第一個參數通過匯編,寫到了m_wnd成員變量上了。沒辦法:兼容x64平臺,只能這樣thunk。
】
你用的是和atl一樣的方式?
將hwnd"替換"為this? 而非"插入"一個this?
是嗎?
1~3cpu指令只是"調整棧"而已,調整完了,肯定會繼續執行thunk中的call,來到你的函數。
這個call或者jmp是免不到的。
同時,編譯器得不到thunk的目的地的信息,所以不可能進行任何優化。
本來也沒什么優化的余地了,都機器碼了……
x64的我也有點想做……
但對x64上的匯編和調用約定不熟悉,而且也沒有x64的測試平臺……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 18:07 by
@cexer
【
thunk 完成的功能只是消息從系統到窗口類的很小一步,也是最需要完成的第一步。在這一步上我嘗試過很多方法,包括這種 thunk 技術,甚至還用過TLS做映射。我目前用的方法很簡單就是 SetProp,GetProp 兩個函數,有點重劍換木劍的感覺。效率上肯定沒有 thunk 的直接調用高,但是心里更踏實一點,在內存當中寫二進制代碼總有在犯罪的感覺。
“過早的優化是一切罪惡的根源”。基于這一步只是整個GUI框架當中是很微小的一步,幾乎不影響框架設計,可以先把框架搭好,再來從安全,效率,可移值性各個方面進行考慮。反正不要選擇 GetWindowLong ,GWLP_USERDATA 那一套,如果發布出去后客戶也使用這個東西就一切全完了,“過時的悲觀毫無益處”。
】
我就知道……
大多數人對GetWindowLongPtr/GWL_USERDATA的理解是有誤的。
你先仔細看看msdn是怎么介紹cbExtra、GetWindowLongPtr,還有GWL_USERDATA再考慮要不要否定這套方案。
線程局部存儲映射表?
先不說效率,mfc不能在線程之間傳遞CWnd*,就是一個使用上的劣勢。
mfc需要在文檔中記錄: 不能將CWnd*傳遞到另一個線程。
使用SetProp需要在文檔中記錄:不要SetProp("cexer" , ...)
使用GetWindowLongPtr需要在文檔中記錄:不要SetWindowLongPtr( hwnd, 0, ... );
3種方案有區別嗎?文檔中都必須有記錄 —— 什么是庫使用的,用戶不能隨意使用 —— 沒一個逃得掉。
可能只有thunk,不需要在文檔中特別說明什么,比如ATL/WTL。
區別還是有的,效率。
tss-table肯定是最低,編程也最復雜的,完全不值得使用。
SetProp需要查找,效率肯定沒有另外兩種高,而且,它很可能也需要分配內存。
thunk和GetWindowLongPtr旗鼓相當。很難說誰高誰低。
但后者要簡單很多,而且是在所有Windows上都可以使用,不用考慮匯編的問題。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 21:50 by
@OwnWaterloo
就像你把火箭的點火系統裝到拖拉機上,轟轟烈烈地點燃了拖拉機,它還是不能以宇宙第一速度脫離地球獲得自由一樣。Windows 系統維護消息隊列的時間遠遠多于消息到窗口那一彈指一揮間,我們再努力地擠時間出來也只是尋求心理上的安慰。所以在寫 GUI 框架時我盡量避免的是空間消耗而不是時間。
不過最佩服C++程序員們的一大優點就是,就算只有一點效率也是要打破腦袋擠出來的,有總比沒有好嘛。你們討論的我都認真看到,在效率和安全上 thunk 確實是很明顯的最佳選擇,我在考慮以后用這個了。在利益的誘惑下,心里的負罪感是可以克服的!
還是不敢選 GetWinowLongPtr 。它的好用是世人皆知的,就像一個大美女,人人都想染指一把,很難說在哪個月黑風高的晚上,哪個不愛看文檔的程序員就忍不住用了它,于是崩潰藍屏,整個世界清靜了。。。這樣的事一定會發生的,因為不愛看文檔的人太多了。
SetProp 雖然也有危險,但是那概率小得多。世上的 Windows 程序員有多少,這些 Windows 程序員當中寫 GUI 的又有多少,寫 GUI 的程序員們直接用 API 寫的又有多少,直接用 API 寫的用到 SetProp 的又有多少,用到 SetProp 偏偏又用到和我一樣參數的又有多少呢。我感覺遇到這樣的概率比走路時被不明飛行物砸中的概率還要小。
當然作為一個庫的作者,不能把完全安全性交給概率去解決。不像 GetWindowLongPtr 你只能眼睜等著崩潰,用 SetProp 為了增大安全性可以有很多手段。可以用一個 GUID 來 SetProp( guid .... ) ,可以用圓周率3.14159265。。。。,甚至可以用對女朋友的愛情宣言全文,愛是獨一無二的嘛!
MFC的窗口指針可以在線程間傳遞,只是除了 SendMessage 之外能干的事不多。這跟那個 TSS 表沒多大關系,GUI 很多 API 本身就是不能跨線程。真正不能在線程間傳遞的是 TSS 表本身,我們用 TSS 的目的就是避免線程間的牽扯,當然是不會在線程間傳來傳去的,而且這個表對 GUI 框架的客戶而言是看不到的,想傳也傳不了。TSS 映射表的速度也不如想像中的慢,一個再巨型的 GUI 軟件,能有多少窗口可供映射的呢。
這些方法我都用過。權衡起來,還是覺得 SetProp 是最簡單好用的一個,有興趣的同志可以測試一下在 SetProp 和 thunk 的實現效率差別有多大。我個人覺得在消息隊列吊車尾的情況下,差別不大。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 22:07 by
@cexer
如果不考慮效率,最好的辦法就是不用C++做gui了。
開發又慢又容易出錯,何必呢? 圖什么?
GetWindowLongPtr的問題真的很容易解決。
只要程序員用這個函數之前,看過GetWindowLongPtr的文檔。
如果一個庫的用戶繞過庫并且不看文檔而直接使用win32api,庫是怎么都做不到,也花太多心思防止他用錯的。
他甚至可以不用看這個gui庫的文檔 —— 因為你可以把this放在array的最后,而不是最前。
甚至還可以在this前后設置一些guard來檢測使用庫的人員是否出軌。
并且,如果將this放在最后,正好也可能需要一些padding來對齊。
SetProp和thunk的效率就不用比了……
同樣要分配內存。
thunk是直接調用,SetProp調用之后還有活要干……
當然,效率不是最主要的…… 也不是可以完全不顧的……
我覺得將this放在GetWindowLongPtr所在array最后,既安全又快速又簡便……
當然,最終權衡在你。
而且…… 只要這個方式不對用戶公開……
隨時可以改……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 22:18 by
@cexer
void f( CWnd* w)
{
CWnd* d = w->GetItem( ... );
d->GetWindowTitle( ... );
}
具體是不是叫這個名字不記得了。
由一個窗口得到上面的控件的窗口,然后取一下標題。
如果w來自另一個線程 …… 等著程序掛吧……
d是一個指針,有主動釋放嗎?
mfc的消息循環還會在odle時清除這種"臨時對象"的指針。
就為了滿足它的table機制,需要在動態內存中創建對象,并使用類似gc的機制去清理……
mfc的大、慢就是這么一點一點的積累出來的。
table這種方式絕不可取……
這個例子只是舉例,可能并不正確,因為我很久都不用mfc了……
太out……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 22:38 by
@OwnWaterloo
我可沒說不考慮效率哈。別說C++,就算用石頭寫程序,都得考慮效率的。只是效率不是影響一切的標準,特別是當效率的差別微乎其微的時候。我不是不考慮效率,而是覺得用 thunk 實現 GUI 框架的效率不一定就能高多少,因為真正吊車尾的是消息隊列, SetProp 和 thunk 這點微末的時間,相對系統得到事件,生成消息,翻譯消息,發送消息,排隊消息的這一大堆的內部操作的時間來說,短得不值一提。GUI 線程只是用來跑 GUI 的,不能用 GUI 線程來完成分步式計算啊。
用 GetWindowLongPtr 來作為實現框架確實也很簡單,效率也最高。但因為GWL_USERDATAR 的眾所周知而又獨一無二,這個問題是很嚴重的,而且很明顯,OwnWaterloo兄啊,何苦這么執著地為它辯護。。。。要是在廣告中說道,“此 GUI 框架物美價廉童叟無欺,但請不要使用 GWL_USERDATA 因為本框架要使用”,怎么賣得出去啊。
三種方式當中 thunk 和 SetProp 確實是前者優一點,我個人放棄效率而選擇標準一點的實現。至于 GetWindowLongPtr ,現在你就算用左輪手槍指著我的腦袋,我也還是堅持不能用的哈 。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 22:48 by
@OwnWaterloo
你說的:“
oid f( CWnd* w)
{
CWnd* d = w->GetItem( ... );
d->GetWindowTitle( ... );
}
具體是不是叫這個名字不記得了。
由一個窗口得到上面的控件的窗口,然后取一下標題。
如果w來自另一個線程 …… 等著程序掛吧……
d是一個指針,有主動釋放嗎?
mfc的消息循環還會在odle時清除這種"臨時對象"的指針。
就為了滿足它的table機制,需要在動態內存中創建對象,并使用類似gc的機制去清理……
mfc的大、慢就是這么一點一點的積累出來的。
table這種方式絕不可取……
”
你說的這個是 MFC 的實現嘛,MFC 還在系統中加了鉤子呢。它的這個指針之所以有諸如線程的限制,也是因為實現的功能太雜。我們的 TSS 表可是壓根沒想過要實現這么個“臨時指針”的功能。關于跨線程調用,應該是 API 是怎么樣,跨線程調用成員函數也應該能干啥。TSS 表只是消息到窗口類當中很小的一步,不該影響到窗口類本身的工作。所以函數調用跟 TSS 表一點關系都沒有的,如果一個函數調用因為 TSS 表而崩潰,那就是有問題的實現了。以前用過這種方式實現的,正是考慮到多線程才使用 TSS。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 22:57 by
@cexer
我一直都在強調……
GetWindowLongPtr, GWL_USERDATA是普遍被誤解了的……
我從學Windows開始…… 就把這個用錯了……
因為我的老師給我們的課件上用錯了……
網絡上【我沒見過用對的文章】,也全都用錯了:
p = (Window*)GetWindowLongPtr( hwnd , GWL_USERDATA );
不是這樣的……
GWL_USERDATA有其本身的含義(在對話框中)。
應該怎么用還是怎么用。
庫是不需要占用它的……
GetWindowLongPtr返回的是一個array中的元素。
1.
它可以是一個以0開始的,大小為cbExtra / sizeof(void*)的數組。
2.
或者是以0開始的,大小為cbExtra / sizeof(long) —— 如果以GetWindowLong取得。
3.
或者是一些系統定義的index, 比如GWL_USERDATA, GWL_WNDPROC —— 這些系統定義的index全是負數。
我上面寫的代碼全是:
GetWindowLongPtr( hwnd, 0 );
或者:
GetWindowLongPtr( hwnd, index );
而不是:
GetWindowLongPtr( hwnd, GWL_USERDATA );是錯的 —— 對于塞this來說的話 —— 確實會占用掉用可以留給用戶的機制。
庫只要將注冊窗口類的過程截獲,并且給cbExtra增加一點點 —— padding + sizeof(this) + guard。
庫的用戶還是可以自由使用GetWindowLongPtr, 只要他沒有越軌 —— 不超過他填入的cbExtra。
用戶要越軌,那就責任自負……
內存分配:
cbExtra應該是和window一起被分配,不需要專門分配thunk結構體或者SetProp用的數據。
速度:
直接通過index尋址array,也幾乎是最快的查找方式。
所以我說從整體上thunk和GetWindowLongPtr的效率可能差不多。
thunk如果one window one WNDCLASS,會注冊很多WNDCLASS。
如果多個window共享一個WNDCLASS,就需要轉發一次。
這些都是有效率損耗的。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 23:03 by
@cexer
我沒有細想過 HWND -> this的映射的方案……
因為它一開始就在我排除的范圍內……
——【一個設計缺陷不應該由另一個設計缺陷來彌補】。
幸好,最后我發現WndProc(hwnd, ... )還不算徹底的設計缺陷。
因為hwnd可以有cbExtra。
我再想想table(hwnd,this)是否一定需要mfc那樣的機制……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 23:04 by
@OwnWaterloo
你說:“我一直都在強調……
GetWindowLongPtr, GWL_USERDATA是普遍被誤解了的……”
哈哈,原來如此。看來我對 GetWindowLongPtr 真是有深深的誤解!和你一番討論挺有收獲的,這樣看來我得再多權衡一下了!這樣的 GetWindowLongPtr 是一個誘人的選擇。看來不看書,只聽道聽途說來的東西真是不行的。可以回頭去睡覺了,多謝了!
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 23:12 by
@cexer
哈哈,性感誘人吧? 所以我極力推薦這妞……
也正因為誤解很嚴重……
所以用戶代碼訪問 [0, length ) 的幾率還真不大……
抄一下msdn……
The GetWindowLongPtr function retrieves information about the specified window. 【The function also retrieves the value at a specified offset into the extra window memory.】
LONG_PTR GetWindowLongPtr
(
HWND hWnd,
int nIndex
);
nIndex
[in] Specifies the zero-based offset to the value to be retrieved. 【Valid values are in the range zero through the number of bytes of extra window memory, minus the size of an integer.】
To retrieve any other value, specify one of the following values.
... 后面就是其他很多系統定義index了。
GetWindowLongPtr的另一個作用 —— 0-based array —— 只有這么2句話……
還隱藏在對系統自定義index的大篇幅的介紹之中……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 23:14 by
還有一句:
Remarks
Reserve extra window memory by specifying a nonzero value in the cbWndExtra member of the WNDCLASSEX structure used with the RegisterClassEx function.
原來叫cbWndExtra…… 不叫cbExtra……
很有沒用…… 寫錯了……
也對,因為相對的還有cbClassExtra……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-19 23:21 by
@OwnWaterloo
你說:“性感誘人吧? 所以我極力推薦這妞……
也正因為誤解很嚴重……
所以用戶代碼訪問 [0, length ) 的幾率還真不大……”
確實非常性感誘惑人,我們不要宣揚了,好東西我們兩知道就好了,哈哈!MSDN真是有點猥瑣。。。實現這么好個功能,卻不知道大書特書,讓那 GWL_USERDATA 忽悠了不知道多少程序員。OwnWaterloo 兄弟,該睡覺了,身體是革命的本錢啊,再聊。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 00:13 by
@cexer
確實有點猥瑣……
沒這功能,WndProc的設計真的就是缺陷了……
有這功能,WndProc還是很麻煩……
我剛吃完晚飯…… 囧……
習慣沒養好…… 夜貓慣了…… 慢慢改吧……
可能哪天不寫代碼了,生活會規律一些……
那個array的index到底是怎樣,msdn我沒看怎么明白……
待會驗證一下……
tss + table是否可以調用成員函數, 我覺得有困難……
我再想想……
畢竟,要證明很容易 —— 想出一個實現。
要證偽 —— 證明不可能實現出來 —— 要困難不少……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 00:44 by
我說一下思路吧,不算證明 tss+table不行,只是說一下這個方案可能遇到的陷阱。
假設這就是注冊窗口類時使用的函數:
LRESULT CALLBACK proc(HWND hwnd, UINT msg, WPARAM, LPARAM )
{
if ( msg == WM_XXX ) // 處理某條消息時需要得到this
{
table = get_tss_table(); // 線程相關映射表
w = table.lookup( hwnd ); // 查找hwnd對應的object。
w->f() // 調用一個成員函數...
}
}
如果另一個成員函數:
C::g()
{
SendMessage( this->m_hwnd , WM_XXX , ... );
// 而且這個消息、WM_XXX、在proc處理過程中需要取得this
}
那這個成員函數g,就不能在另一個線程中使用。
因為table = get_tss_table();是線程相關的。
是否會有這種成員函數…… 就不知道了…… 沒這方面的經驗,也想不出例子……
mfc返回一個CWnd* ,可能也是基于這種考慮:
CWnd* w = father->getChild (...);
w->g(); // 這里可能需要查表。
mfc中也可以這樣:
HWND hwnd = ::getChind( father->m_hwnd, ... );
CWnd w;
w.Attach( hwnd ); // 插入表
w.g(); // 可查詢
w.Detach(); // 從表中刪除
可能afx考慮到最后的w.Detach會被忘記調用。一旦w離開作用域,那個hwnd -> object的表,就指向一個無效對象。
所以就引入了一個類似gc的方式…… 返回CWnd* 同時插入表中。
純猜測……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 01:53 by
關于GetWindowLongPtr。
1. 系統定義index
有配套的GWLP_xxx系列…… 以前真沒注意……
在32位上GWL_xxx和GWLP_xxx的值可能相同…… 但還是應該使用配套的……
自從我用Get/SetWindowLongPtr開始,就沒寫對一次過……
囧……
幸好cexer的某個回復中有GWLP_USERDATA……
順手再查了一下……
2. array index
array就是一個byte的array。
這個array的地址是不會返回給用戶的 —— 應該如此。
大致應該是如下偽代碼:
LONG_PTR GetWindowLongPtr( hwnd , index )
{
char* array = get_extra(hwnd);
LONG_PTR r;
if ( index + sizeof(r) > get_extra_size(hwnd) )
{
SetLastError(ERROR_INVALID_INDEX); // 1413
return 0;
}
memcpy(&r , array+index , sizeof(r) );
return r;
}
越界會返回0,LastError是無效索引,1413。
有效索引應該是:
GetWindowLong, [0, cbWndExtra - sizeof(LONG) ], msdn說-4,也算對。
GetWindowLongPtr, [0, cbWndExtra - sizeof(LONG_PTR) ] , msdn說-sizeof integer, 這就是我困惑的地方……
integer是啥? short算不算…… 應該是sizeof(LONG_PTR)才對……
對齊用戶(winapi的用戶)也不需要特別關心。
反正array的地址是不可能暴露出來的,上面有些地方可能說錯了。
SetWindowLong/Ptr也是會檢查無效索引。
3. 創建窗口時收到的消息:
測試時順便得到的:
WM_GETMINMAXINFO = 0x24;
WM_NCCREATE = 0x81;
WM_NCCALCSIZE = 0x83;
WM_CREATE = 0x1;
可能還和窗口風格什么的有關,以上不準,以msdn為準。
4. GetWindowLongPtr的其他限制
cbWndExtra在對話框中有一些規定。
MDI也有一些特殊規定 —— 針對CreateWindow是傳入context。
可能還有一些特殊情況……
總之…… win32 gui是很浩瀚的事情…………
cexer…… 精神上支持你……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 10:00 by
@OwnWaterloo
你說“那這個成員函數g,就不能在另一個線程中使用。因為table = get_tss_table();是線程相關的。”
首先用表肯定是可以的,比如說我們一個全局表。針對映射表的操作有三種,一種窗口創建時的插入,一種窗口銷毀時的刪除,還有就是窗口消息來時的查詢。創建和銷毀是在調用的 GUI 線程當中進行的,做不到跨線程的創建和銷毀。而查詢時是在 WNDPROC 中進行的,這個函數是由系統調用,也是在 GUI 線程中運行的。既然所有的操作都在同一個 GUI 線程,那何必要用全局表呢,還要加鎖,這不正是 TSS 派上用場的地方嘛。
你說:“GetWindowLongPtr的其他限制”
GetWindowLongPtr 確實有比較大的限制。除了你所說的,對話框,按鈕這類的已經注冊過的窗口類,其 cbWndExtra 都已經是固定的,要想使用 GetWindowLongPtr 來存取額外數據的話,就必須要超類化,這樣的就又麻煩了。所以綜合考慮,SetProp 和 thunk 是最優選擇。
你說:“總之…… win32 gui是很浩瀚的事情…………“
當然是的,不浩翰就沒有辟波斬浪的快感嘛。OwnWaterloo 晚上兩點還堅持在前線?深更半夜的,都在研究些什么高深課題呢?多好的睡覺時間啊,晚上睡得香,白天不嗑睡,早起早睡效率才更高!
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 14:47 by
@cexer
關于tss+table, 我理解錯了……
SendMessage( hwnd , .... );
如果調用SendMessage所屬線程T1和hwnd所屬線程T2不是同一個線程,則依然是由T2,而不是T1去執行hwnd的wndproc。
我想成T1去了……
哦,還有個GetWindowThreadProcessId……
如果tss真有問題,可以不用那么自動的tss,手動+GetWindowThreadProcessId也行。
那mfc又是怎么掛掉的呢……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 15:06 by
@cexer
【
GetWindowLongPtr 確實有比較大的限制。除了你所說的,對話框,按鈕這類的已經注冊過的窗口類,其 cbWndExtra 都已經是固定的,要想使用 GetWindowLongPtr 來存取額外數據的話,就必須要超類化,這樣的就又麻煩了。所以綜合考慮,SetProp 和 thunk 是最優選擇。
】
已經注冊過的窗口類,如果要塞context,都需要subclassing。
SetWindowLongPtr( hwnd, GWLP_WNDPROC, insert_context );
然后在insert_context中將context塞進去,SetProp或thunk。
只是GetWindowLongPtr塞不了。
對窗口類注冊不由自己控制的情況,GetWindowLongPtr確實不適合。
對這些預定義的窗口類需要做一些什么工作,有什么需求,我沒怎么細想……
用win32 api寫gui的事,我沒做幾回……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 15:08 by
@cexer
白天思維很亂…… 晚上思維清晰一些。
在想C++中怎么弄出匿名函數…… 無果……
如果沒有這種東西, gui的語法是不會漂亮的……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 15:16 by
也不算匿名函數吧。
就是,如果一個調用需要傳入一個函數作為回調,如何在調用點上創建這個回調函數,而不是在調用點所在函數外。
void handle_xxx( callback );
void f()
{
handle_xxx
( /* 1. 如何就地創建一個函數,傳遞給handle_xxx */
{ ... }
/* 估計C++0x才行 */
);
/* 2. 或者次一點,再handle_xxx的調用點所在函數中創建 */
my_handler () { ... }
handle_xxx( my_handler );
}
/* 3.最次的,就是在調用處所在函數外定義 */
my_handler() { ... }
void f()
{
handle_xxx( my_handler );
}
C++可以在函數內創建一個嵌套class, 并定義該class的函數 —— 可以是static的,但就是不能直接定義一個嵌套函數。
這可能是C++98、03的唯一手段了。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 16:42 by
@OwnWaterloo
你說:“已經注冊過的窗口類,如果要塞context,都需要subclassing。
SetWindowLongPtr( hwnd, GWLP_WNDPROC, insert_context );
然后在insert_context中將context塞進去,SetProp或thunk。
只是GetWindowLongPtr塞不了。”
可以塞的,只是需要超類化(superclassing)之后再塞。子類化(subclassing)在這里不行。但超類化是更需要謹慎使用的東西。
你說:“如何在調用點上創建這個回調函數,而不是在調用點所在函數外。“
C++不能創嵌套函數的,但可以在函數當中創建一個函數對象。你試試看?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 16:52 by
@cexer
超類化?
可以創建嵌套類,嵌套類中可以有靜態或非靜態成員。比較間接一點。
直接寫嵌套函數是不行的。
跟是否可以函數對象沒什么關系。
主要需求是定義這個回調(或者函數對象)的地方最好和使用這個回調的地方比較接近。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 16:57 by
@cexer
GetClassInfo( DIALOG , &wc );
wc.lpProc = my_proc;
不修改wc.name
RegClass( &wc );
這樣?
這……
如果在修改wc并注冊之前,已經創建了一些窗口呢?
會怎樣? 也會跟著被修改? 不會吧……?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 16:58 by
@OwnWaterloo
你說:“超類化?”
隨手找了篇文章:
http://hi.baidu.com/combojiang/blog/item/45e968b7b8a510f131add11c.html@OwnWaterloo你說:“跟是否可以函數對象沒什么關系。主要需求是定義這個回調(或者函數對象)的地方最好和使用這個回調的地方比較接近。”
我的意思是,可以定義函數對象代替函數來作為回調。實在是需要純粹的回調函數,也可以在外面定義一個公用的模板,然后在內部定義一個函數對象為參數來具現化這個模板,就獲得了真正的函數地址。不知這個是否與你的需求有所出入?
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 17:05 by
@OwnWaterloo
你說:“如果在修改wc并注冊之前,已經創建了一些窗口呢?
會怎樣? 也會跟著被修改? 不會吧……?”
確實不會。Windows 還沒實現那么神奇的功能,而且沒必要。如果需要同時修改已經創建的窗口,就只能列舉出來一個個地子類化。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 17:19 by
@cexer
那篇文章我晚點再看看。
我覺得是函數指針還是函數對象關系都不大,我并不排斥函數指針~_~
關鍵是那個共用的模板定義不出來啊……
假設,一個xxx控件,用來調節一個整數,它提供這么一個通知:
void xxx::on_change_add( void (*listener)(void* context,int pos), void* context );
我覺得最好的語法:
void f1()
{
xxx x;
int i = 0;
x.on_change_add( void (void* c,int pos){ *(int*)c = pos; }, &i );
wait();
printf("%d\n",i);
}
應該只有等C++0x的lambda才可以這樣寫了。
次之:
void f2()
{
xxx x;
int i = 0;
struct local { static void set_pos(void* c,int pos) { *(int*)c = pos; } };
x.on_change_add( &local::set_pos, &i );
wait();
printf("%d\n",i);
}
這是符合C++語法的,比較丑陋了。
但如果on_change_add是函數模板,就會很麻煩。
函數模板的實際類型參數不能是這種內嵌的類型。
最難看的:
void set_pos(void* c,int pos) { *(int*)c = pos; }
void f3()
{
xxx x;
int i = 0;
x.on_change_add( &set_pos, &i );
wait();
printf("%d\n",i);
}
set_pos只是一個示例而已。
代表的是一簇簡單(代碼就2、3行),但邏輯多變 ——根本不可能預定義一些函數對象或者函數模板 —— 只能是隨寫隨用、一處使用、不被復用的代碼來表現需要的邏輯。
類似的例子, std::for_each如果沒有lambda的支持, 就是雞肋。
當然,這只是個審美的問題……
也有人覺得第3種(定義到外部)還不錯……
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-20 17:33 by
@OwnWaterloo
你的這個需求有點像 java 的 AWT 的 添加 listener 的方式
addWindowListener ( new WindowListener(){
void windowOpened(){ ...... }
void windowClosing(){ ...... }
} );
也有點類似自動化腳本的
onclick = "javascript:{......}"
或者 lua 的
window.onClick = function( arg )
begin
--......
end
確實是很誘人的語法。可惜三種 C++ 都不能實現,boost::lamda 生成的函數對象好像是臨時的吧?也不能這樣保存起來以后再使用。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-20 17:59 by
boost.lambda好像只是表達式。 沒仔細研究過。
要保存可以有auto。。。 或者boost.typeof。
本來我需要的僅僅是一個函數,卻不得不定義一個類…… 很……
被其他語言慣壞了,就覺得c++寫gui非常不方便……
我打算等c++0x流行之后再考慮是不是要用c++寫gui了~_~
在哪之前,如果有得選擇,c++就不是第1人選……
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-20 23:29 by
@ OwnWaterloo
你用的是和atl一樣的方式?
將hwnd"替換"為this? 而非"插入"一個this?
是嗎?
====================
是的。因為這樣才能兼容x64平臺:x64前8個參數通過寄存器傳遞。
@ cexer
SetProp 結合 GUID更理想,而GUID可以用API獲取。
以后多交流!期待你的GUI框架下一篇...^_^
================
|||||||||||||||||||||
發現一個類中有6萬個指針時,如果指針初始化了,還是占用內存。
沒辦法,為了降低成本,不能在類中放那么多指針了。
所以,使用bitset來幫忙。
先看消息注冊(比原來的難看多了,沒找到好方法,詳見:
http://topic.csdn.net/u/20091119/15/07d7e755-c733-4195-8cc4-306560d6fbc4.html )
class MainFrm : public Frame
{
public:
MainFrm() : Frame(_T("Test Demo!"), _T("MainFrame")), m_testBtn(_T("Test!"))
{
Add(&m_testBtn);
m_testBtn.Get<EvtLButtonDown>(m_testBtn.IdLButtonDown).Bind(this, &MainFrm::OnLBtnClicked);
Get<EvtCreate>(IdCreate).Bind(this, &MainFrm::OnCreate);
}
~MainFrm()
{
Get<EvtCreate>(IdCreate).UnBind(this, &MainFrm::OnCreate);
}
qpEvt OnCreate(EvtCreate& evt)
{
qpDbgInt(evt.handled);
}
qpEvt OnLBtnClicked(EvtLButtonDown& evt)
{
qpDbgInt(evt.owner);
}
protected:
Button m_testBtn;
};
用bitset + map<short, void*> + enum來降低成本:
#define IMPLEMENT_EVENT(X, Y) \
public: \
template <typename T> \
Event<T>& Get(X id) \
{ \
if (m_evtMap.get() == NULL) \
{ \
qpNewPtrEx(p, EventMap); \
m_evtMap.reset(p); \
} \
if (m_evtFlag[id] == 0) \
{ \
qpNewPtrEx(p, Event<T>); \
if (p != NULL) \
{ \
m_evtFlag[id] = 1; \
m_evtMap->insert(std::make_pair(static_cast<short>(id), p)); \
return *static_cast<Event<T>*>(p); \
} \
} \
else \
{ \
EventMap::iterator it = m_evtMap->find(static_cast<short>(id)); \
if (it != m_evtMap->end()) return *static_cast<Event<T>*>(it->second); \
} \
qpASSERT(false); \
qpNewPtrEx(p, Event<T>); \
return *std::auto_ptr<Event<T>>(p); \
} \
protected: \
typedef std::map<short, void*> EventMap; \
std::auto_ptr<EventMap> m_evtMap; \
std::bitset<Y> m_evtFlag;
std::bitset的使用,可以判斷相應消息是否注冊,不用每一個消息都進入一次Event了,但由于使用map來查找消息,效率上還是下降了。
沒辦法:只能用時間換空間!
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-20 23:33 by
相應類的消息注冊類
class ButtonEvt
{
public:
enum EventId
{
IdLButtonDown,
TotalEvent
};
IMPLEMENT_EVENT(EventId, TotalEvent);
};
class FrameEvt : public EvtSender
{
protected:
FrameEvt(const String& keyEvt) : EvtSender(keyEvt) {}
public:
enum EventId
{
IdCreate,
TotalEvent
};
IMPLEMENT_EVENT(EventId, TotalEvent);
};
這樣,只需要往enum里放不占內存的消息ID就行了。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-21 02:04 by
@Loaden
同學生日,聚會喝了點酒…… 看代碼很暈…… 像不認識C++似的……
關于你在csdn上的問題。
函數模板可以通過實際參數來推導類型。
template<typename T>
T max(T v1,T v2);
max(1212 , 326); // max<int> T=int
max(1212.0 , 326.0); // max<double> T=double
max(1212, 326.0); // 歧義 T=int or double
max<double>(1212, 326.0); // 顯示指定 T=double
在模板參數列表中,最后一個不能被推導的參數以及它之前的所有參數都必須顯示指定:
template<typename D,typename S>
D union_cast(S src)
{
union
{
S src;
D dst;
} u = { src };
return u.dst;
}
union_cast<long long>(&C::f); // D= 一個成員指針,但S不能通過實際參數推導出來,必須顯式指定。
操作符重載,其實也是一個函數,只是通常不以函數的形式調用。
所以你那個需求,可能真的只能這么寫:
a.operator[]<T,U>( index );
如果真的想實現:
T* p = a[ i ];
肯定能實現,不過很惡心…… 也可能很危險……
class A;
struct proxy
{
A* a_;
template<typename D>
operator D*() const { return new D; }
};
class A
{
template<typename T>
proxy operator[](T i)
{
proxy p = { this };
return p;
}
};
A a;
proxy p = a[ i ]; // 調用a.operator[]<int>; 返回一個proxy
E<TBase>* o = p; // proxy.operator<D>
E<TBase>* o = a[ i ]; // 連起來就是這樣……
template<typename T>
operator T() 是很邪惡的東西,盡量少用……
【
是的。因為這樣才能兼容x64平臺:x64前8個參數通過寄存器傳遞。
】
確實,i386是個特例,主要通過棧來傳遞參數。
這樣,無論被partial application的函數的參數如何 —— 只要不是返回大結構體 —— 插入一個指針參數都可以使用相同的thunk結構體。
我考慮過ppc(我唯一能搞到的非i386的機器),主要通過寄存器傳遞。
視被partial application的函數的參數,插入一個指針需要不同的thunk結構體。
但替換某個參數,thunk結構體就和其他參數的數目無關了。
如果被partial application的函數的參數已經確定,為其設計一個thunk結構體,插入一個指針參數也應該是可行的。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-21 08:53 by
@ OwnWaterloo
謝謝!
我目前的方案,實現了每個可以發送消息的類,例如Button、Frame,至少需要8byte的代價,但:
50個事件供Bind:至少12字節
100個事件供Bind:至少20字節
1000個事件供Bind:至少132字節
10000個事件供Bind:至少1256字節
而我之前每個事件用一個指針的方法,分別對應:
200
400
4000
40000
代價下降了20倍。
不過每個注冊的事件,還要多一個short + void*。
由于用戶在一個程序中處理的事件不會很多,估計是總事件1/1000左右。
所以也是很值得的。
就算是一種低代價的實現吧。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-21 10:06 by
@ OwnWaterloo
現在在理論上還可以有一種辦法將:
m_testBtn.Get<EvtLButtonDown>(m_testBtn.IdLButtonDown).Bind(this, &MainFrm::OnLBtnClicked);
Get<EvtCreate>(IdCreate).Bind(this, &MainFrm::OnCreate);
簡化為:
m_testBtn.Get<EvtLButtonDown>().Bind(this, &MainFrm::OnLBtnClicked);
Get<EvtCreate>().Bind(this, &MainFrm::OnCreate);
即不傳入enum的id,改由Get函數根據typeid(T).name去判斷id的值。
但如何去實現呢?
struct Create
{
bool handled;
};
class A
{
public:
enum
{
IdCreate,
TotalEvent
};
template <typename T>
void Get()
{
// 這條語句輸出:struct Create
printf("%s", typeid(T).name());
// 可是,如何通過上面語句的輸出,來訪問: IdCreate呢?
// 從“struct Create中獲取Create,在前面再加上Id,可以得到字符串IdCreate
// 如何將這個字符串轉化為枚舉型變量的一個值,例如:IdCreate?
//
// 這兩行語句如何實現?
// int i = IdCreate;
// printf("%d", i);
}
};
int main()
{
A a;
a.Get<Create>();
return 0;
}
一種比較容易想到的方案是:
if (name=="A") id = ;
else if (name=="B") id = ;
但如果事件很多,這樣去比較,代碼寫起來很麻煩。
而且編譯后的程序體積也是個問題。
放到一個const字符串數組里,根據index判斷,由于const數組是占用內存的(VC占用,測試過,GCC不占用),所以這樣反而得不償失了。
# re: GUI框架:談談框架,寫寫代碼[未登錄] 回復 更多評論
2009-11-21 11:30 by
typeid.name()的問題,通過CSDN解決了。
帖一下Demo。
struct Create
{
bool handled;
template<class T>
static int GetEventId()
{
return T::IdCreate;
};
};
struct Close
{
bool handled;
template<typename T>
static int GetEventId()
{
return T::IdClose;
};
};
class A
{
public:
enum
{
IdCreate,
IdClose,
TotalEvent
};
template <typename T>
void Get()
{
printf("%d\n", T::GetEventId<A>());
}
};
int main()
{
A a;
a.Get<Create>();
a.Get<Close>();
return 0;
}
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2009-11-22 20:56 by
@OwnWaterloo
那么,ATL-style繼承的作用就是:實現靜態的"template method"?
建議看看boost里Singleton的模板,類似。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2010-04-29 09:04 by
All people deserve wealthy life and <a href="
http://lowest-rate-loans.com/topics/credit-loans">http://www.lowest-rate-loans.com</a> or just small business loan will make it better. Because people's freedom is grounded on money state.
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2010-05-09 10:43 by
牛人啊,膜拜了,回頭我也去嘗試著寫一個
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2010-08-25 12:24 by
牛人啊,膜拜了,學習了
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2010-12-19 01:12 by
來考古了,太多了~~~~囫圇看一遍都花這么久。
# re: GUI框架:談談框架,寫寫代碼 回復 更多評論
2014-12-11 19:08 by
真的很精彩!