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