2011年12月11日
In the MFC environment, normally, thread should be launched with AfxBeginThread for taking usage of MFC multiple-thread mechanism; In such mechanism, those datastructures, such as AFX_MODULE_STATE, would be used by MFC framework to maintain related thread information. It runs well when threads, launched with AfxBeginThread, quit before the main thread, which is responsible for initializing C run-time, but if such main thread quit before any other thread launched by AfxBeginThread, the current application would crash.
Such crash comes from the _afxThreadData (CThreadSlotData* _afxThreadData, which is defined in AFXTLS.cpp as global data structure) has been destructed while the main thread quits and it will invoke related function to clean up global data structures, including _afxThreadData definitely.
Consequently, serious developer should prepare for such case (other worker thread quits before main thread).
The reasonable resolve for such issue, would ensure any other threads should quit before the main thread.
.h file
/////////////////////////////////////////////////////////////////////////////
// CSafeEnterLeaveThread thread
class CSafeEnterLeaveThread : public CWinThread
{
DECLARE_DYNCREATE(CSafeEnterLeaveThread)
protected:
CSafeEnterLeaveThread(); // protected constructor used by dynamic creation
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSafeEnterLeaveThread)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CSafeEnterLeaveThread();
// Generated message map functions
//{{AFX_MSG(CSafeEnterLeaveThread)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
.cpp file
/////////////////////////////////////////////////////////////////////////////
// CSafeEnterLeaveThread
IMPLEMENT_DYNCREATE(CSafeEnterLeaveThread, CWinThread)
CSafeEnterLeaveThread::CSafeEnterLeaveThread()
{
}
CSafeEnterLeaveThread::~CSafeEnterLeaveThread()
{
}
BOOL CSafeEnterLeaveThread::InitInstance()
{
// TODO: perform and per-thread initialization here
ASSERT(this->m_hThread);
CMainApp::RegisterMFCThread(this->m_hThread);
return TRUE;
}
int CSafeEnterLeaveThread::ExitInstance()
{
// TODO: perform any per-thread cleanup here
ASSERT(this->m_hThread);
CMainApp::UnRegisterMFCThread(this->m_hThread);
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(CSafeEnterLeaveThread, CWinThread)
//{{AFX_MSG_MAP(CSafeEnterLeaveThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
And in the CMainApp,
set<HANDLE> g_ThreadHandleSet;
HANDLE g_ThreadHandleArray[MAXIMUM_WAIT_OBJECTS];
CCriticalSection g_csGlobalData;
void CAccgbApp::CheckAllOtherMFCThreadsLeave()
{
int count = g_ThreadHandleSet.size();
if (count == 0) return;
set<HANDLE>::iterator it;
int idx = 0;
for (it = g_ThreadHandleSet.begin(); it != g_ThreadHandleSet.end() && idx < MAXIMUM_WAIT_OBJECTS; it++, idx++)
{
g_ThreadHandleArray[idx] = *it;
}
if (count > idx) count = idx;
::WaitForMultipleObjects(count, g_ThreadHandleArray, TRUE, INFINITE);
}
void CAccgbApp::CleanupGlobalData()
{
g_csGlobalData.Lock();
g_ThreadHandleSet.empty();
g_csGlobalData.Unlock();
}
BOOL CAccgbApp::RegisterMFCThread(HANDLE hThread)
{
if (hThread == NULL) return FALSE;
g_csGlobalData.Lock();
if (g_ThreadHandleSet.find(hThread) == g_ThreadHandleSet.end())
g_ThreadHandleSet.insert(hThread);
g_csGlobalData.Unlock();
return TRUE;
}
void CAccgbApp::UnRegisterMFCThread(HANDLE hThread)
{
if (hThread == NULL) return;
g_csGlobalData.Lock();
if (g_ThreadHandleSet.find(hThread) != g_ThreadHandleSet.end())
g_ThreadHandleSet.erase(hThread);
g_csGlobalData.Unlock();
}
2011年4月4日
【 在 某 的大作中提到: 】
: 比如我有一個CMyButton的類,我現(xiàn)在有他的一個handle
: 編譯器怎么根據(jù)這個句柄找到CMyButton的代碼的?
【 在 某某 的大作中提到: 】
: 這個和OS/Compiler沒關(guān)系,是庫起的作用
: 以從某個文章里看的,說MFC用了一個大map,沒驗證過
: 有本講GDI的書里,用了WNDCLASS里的extra bytes來實現(xiàn)的這個映射
MFC的應(yīng)用里,每個MFC線程(必須要使用MFC方式啟動的線程)都維護有一個MFC object和HWND之間的
mapping,整個MFC框架就是使用這個機制來實現(xiàn)應(yīng)用級C++對象和系統(tǒng)級原生窗口內(nèi)核對象之間的關(guān)聯(lián);
因為這個mapping是以線程為單位來維護的,每個線程間互不關(guān)聯(lián),所以,一個應(yīng)用里對于涉及UI窗口的
任務(wù)最好是都放在同一個線程里面,一般就是當前進程的主線程,否則可能出現(xiàn)MFC object和HWND之間
關(guān)聯(lián)不上的問題,而且這樣的問題還很隱蔽。
至于WNDCLASS結(jié)構(gòu)自帶的extra bytes域,是以前缺乏應(yīng)用框架的時代,使用Win32 API直接開發(fā)時,讓每個
窗口類(這里的類,不是C++ class的概念,而是Windows系統(tǒng)窗口定義時的一種數(shù)據(jù)結(jié)構(gòu))都能有個附
帶一些額外的自定義數(shù)據(jù)的空間,這個空間往往被用來存放與當前窗口類相關(guān)的用戶數(shù)據(jù),通常是指向
某個內(nèi)存區(qū)域的指針,當程序操作這個屬于這個窗口類的窗口時就可以根據(jù)這個附帶的自定義數(shù)據(jù)(或
者指針)來操作對應(yīng)的關(guān)聯(lián)自定義數(shù)據(jù);很多后來出現(xiàn)的框架,也都使用了這個extra bytes域,來存放
框架本身的一些和窗口類相關(guān)聯(lián)的數(shù)據(jù)結(jié)構(gòu)。從目前趨勢看,直接使用WNDCLASS以及extra bytes的可能
性是微乎其微了,但是如果要做好原生應(yīng)用的開發(fā),很多底層的實現(xiàn)細節(jié)最要還是要知道一下,以便于
優(yōu)化結(jié)構(gòu)和性能,以及出錯時的調(diào)試處理;因為無論是Winform/WPF,還是跨平臺的WTL/QT/WxWindows等
等新型的機制或者框架、類庫,只要是在Windows平臺上搭建的,那都是基于前面說過的這套最基本也是
最核心的Win32 API基礎(chǔ)之上。
2011年2月27日
這道千分題,其實是挺有意思的一題:
提供了點(這里是junction)和點之間的距離(或代價);
要求以最短的距離(或最小代價)遍歷所有的點,同時每個點可以多次訪問;
初看之下,給人的感覺是圖論相關(guān)的問題,比如旅行者問題、歐拉環(huán)游之類。
在思考這個問題的時候,忽然間聯(lián)想到了圖論中的最小生成樹,雖然并不是真正要去得出最小生成樹,
但是按照最小生成樹所提供的思路--這點很重要--那就是圖和樹之間有著相當密切的關(guān)系:即使最小生
成樹并不能直接解決這個問題,但是它們之間存在的這層關(guān)系的確提供了解決問題的一個有益的嘗試方
向;
于是,思考進了一步,問題從“圖”簡化成了“樹”--如何把當前這個問題采用樹的結(jié)構(gòu)和方法表達出
來:樹的根節(jié)點,很自然地想到了由問題中旅行的起始節(jié)點來表達;然后,隨著節(jié)點的不斷加入,樹就
自然地生成,此處的關(guān)鍵在于如何生成,或者說節(jié)點加入的規(guī)則,以及每個節(jié)點為了適應(yīng)這個規(guī)則,所
必須持有的相關(guān)屬性信息:最直接的,父子節(jié)點之間的關(guān)系需要維護,從父節(jié)點到子節(jié)點的距離(或代
價)必須要保留,其次,考慮到如果每個節(jié)點都維護相關(guān)的距離(代價)信息,那么從當前節(jié)點到根節(jié)
點的代價也就可以由此遞推得出,進一步,我們所要求出的最短路徑(或最小代價)不就可以從上述這
些節(jié)點中維護的距離信息中得出嗎?這是非常關(guān)鍵的一步,它把當前我們構(gòu)建的數(shù)據(jù)結(jié)構(gòu)和問題的要求
之間建立起了相當直接的聯(lián)系。這說明我們目前思考的方向是有價值的而且極有可能順著這個方向前行
,可以得出相當不錯的結(jié)果。
顯然,既然要求最短路徑(最小代價),那么我們目前構(gòu)建出的這顆Junction樹(因為其上的節(jié)點在題
中的物理含義是代表Junction,那這里我們就姑且稱其為Junction Tree),樹上的每個節(jié)點也應(yīng)當保留
在其之下的子樹的最短路徑(最小代價),這就相當于把每個節(jié)點都作為根節(jié)點,然后求出各條子路徑
的代價,并比較得出最短路徑(最小代價),以及在這條最短路徑上的直接子節(jié)點;
每加入一個子節(jié)點,就要對上述已構(gòu)建出的這些數(shù)據(jù)結(jié)構(gòu)中的信息進行維護,以調(diào)整每個節(jié)點當前的最
短路徑代價和相應(yīng)這條路徑上的直接子節(jié)點;當所有原“圖”中的“邊”信息,也就是
(fromJunction,toJuction,ductLength)所代表的(起始點,終止點,長度代價),都按照上述方案加入
Juction Tree之后,我們可以知道從最初的起始節(jié)點(也就是Junction Tree的根節(jié)點)到最終節(jié)點的(
Junction Tree上的某條路徑上的葉子節(jié)點)的最短(最小代價)路徑了。
對于Juction Tree這個ADT抽象數(shù)據(jù)結(jié)構(gòu)的具體實現(xiàn),考慮到優(yōu)先隊列中二叉堆的經(jīng)典實現(xiàn)往往使用數(shù)組
,同時也為了符合TC SRM一貫的簡捷明快的程序設(shè)計風格,我們這里同時使用幾個數(shù)組來維護前述構(gòu)建
出的數(shù)據(jù)結(jié)構(gòu)。
//////////////////////////////////////////////////////////////////////////////////////////
#include<cstdlib>
#include<vector>
#include<set>
using namespace std;
const int NIL = -1;
const int MAX = 50;
int Cost[MAX];
int ParentNode[MAX];
int MaxSubNode[MAX];
int MaxSubCost[MAX];
class PowerOutage
{
public:
int estimateTimeOut(vector<int> fromJunction, vector<int> toJunction, vector<int>
ductLength)
{
if (!CheckParameter(fromJunction, toJunction, ductLength)) return NIL;
Ini();
int count = fromJunction.size();
for (int i = 0; i < count; i++)
{
AddNode(fromJunction[i], toJunction[i], ductLength[i]);
}
return CalculateMinCost(fromJunction, toJunction, ductLength);
}
private:
void Ini()
{
memset(Cost, NIL, sizeof(int) * MAX);
memset(ParentNode, NIL, sizeof(int) * MAX);
memset(MaxSubNode, NIL, sizeof(int) * MAX);
memset(MaxSubCost, 0, sizeof(int) * MAX);
}
bool CheckParameter(const vector<int>& fromJunction, const vector<int>& toJunction,
const vector<int>& ductLength)
{
if (fromJunction.size() != toJunction.size() || toJunction.size() !=
ductLength.size())
return false;
return true;
}
void AddNode(int parent, int child, int cost)
{
if (parent < 0 || child < 0 || cost < 0) return;
Cost[child] = cost;
ParentNode[child] = parent;
int curParent = parent, curChild = child;
bool adjustParentCost = true;
while (adjustParentCost && curParent != NIL)
{
int candidateParentMaxSubCost = Cost[curChild] + MaxSubCost
[curChild];
if (MaxSubCost[curParent] < candidateParentMaxSubCost)
{
MaxSubCost[curParent] = candidateParentMaxSubCost;
MaxSubNode[curParent] = curChild;
curChild = curParent;
curParent = ParentNode[curParent];
}
else
{
adjustParentCost = false;
}
}
}
int CalculateMinCost(const vector<int>& fromJunction, const vector<int>&
toJunction, const vector<int>& ductLength)
{
int len = fromJunction.size();
int minCost = 0;
set<int> minCostPath;
minCostPath.insert(0);
int curNode = MaxSubNode[0];
while(curNode != NIL)
{
printf("%d;",curNode); // print the min cost path
minCostPath.insert(curNode);
curNode = MaxSubNode[curNode];
}
for (int i = 0; i < len; i++)
{
if (minCostPath.find(toJunction[i]) == minCostPath.end())
minCost += 2 * ductLength[i];
else
minCost += ductLength[i];
}
return minCost;
}
};
2011年2月12日
【 某網(wǎng)友討論道: 】
: RT,反射的特性發(fā)現(xiàn)很少用啊
恰恰相反,有些反射的特性是經(jīng)常會被使用到的。
反射總體上分成兩大特性,一是自省,二是發(fā)射;
自省的能力極為重要,而且?guī)缀鯐焯煊玫剑苌僖姷竭^哪個.net應(yīng)用中不使用attribute的,而attribute特性就是metadata通過在自省能力支撐下實現(xiàn)的;當然自省不單單是attribute特性的運用,只要是在運行時動態(tài)檢視程序自身的特性都要由反射的自省能力來支持,比如Visual Studio的IDE(這個集成開發(fā)環(huán)境本身就是.net應(yīng)用的好案例)對于.net組件的自動探測功能;同時,自省的能力也是基于虛擬機平臺的語言,比如c#和java,區(qū)別于傳統(tǒng)語言比如c和c++的重要特性之一,這提供了程序設(shè)計開發(fā)更為便利和安全的運行時環(huán)境;相對而言,在c++(當然是native而不是managed)的環(huán)境下,除了RTTI極為單薄的運行時自省,也就是QT這個庫通過meta-object system部分模擬了自省的特性;
反射的另外一個重要特性就是發(fā)射,它讓“程序可以寫程序”了,簡要的說就是在運行時動態(tài)生成MSIL并加載運行以及持久化動態(tài)生成的MSIL的能力;由這個特性的支持,讓原先一些程序設(shè)計和開發(fā)領(lǐng)域相對困難和繁瑣的工作,比如元編程meta programming,比如動態(tài)代理dynamic proxy,比如AOP中的基礎(chǔ)設(shè)施weaver的實現(xiàn),變得可能或相對易于實現(xiàn);反射的特性,也是基于虛擬機平臺CLR的支持,以metadata為基礎(chǔ)來實現(xiàn)的,所以這也是虛擬機平臺語言的特有優(yōu)勢,而在傳統(tǒng)語言平臺上,這是難以實現(xiàn)的;比如關(guān)于meta programming,c++就是通過模板特性實現(xiàn)的編譯期meta programming,這與虛擬機平臺上實現(xiàn)的運行時meta programming還是有比較大的差距(比如前者如何保證生成的代碼的type-safe);
以上這兩個特性,自省和發(fā)射,都有個共同點,他們都是圍繞著metadata機制,并在虛擬機平臺運行時環(huán)境CLR支持下實現(xiàn)的,前者是運行時檢視相關(guān)的metadata,后者是運行時動態(tài)生成相關(guān)的metadata和MSIL;從這點也就可以看出,要想深入理解這些特性,就需要研究metadata和MSIL的實現(xiàn),以及虛擬機運行時環(huán)境的實現(xiàn)(在java平臺上,就是bytecode和JVM);
所以,反射,可能是虛擬機平臺所提供的相對最為強勁,最為復雜,和平臺運行時本身關(guān)系最密切,也是區(qū)別于傳統(tǒng)語言和運行時最鮮明的特性。
2011年2月8日
In VC++ 8.0, while code compiled with /clr or /clr:pure, static destructors sometimes would not being properly called before process exites in multiple threads.
CRT incorrectly set a lock at _global_unlock which resulted in such issue.
In CLR-mixed mode, during the inialization of static local object, CRT would call _atexit_m(_CPVFV func) in msilexit.cpp to register a special __clrcall callback function which would be called back to destroy such static object when the current AppDomain quited.
In the multithread environment, _atexit_helper which was invoked by _atexit_m, could register such callbace function successfully because it had been guarded by __global_lock() and __global_unlock(). But in the same environment, the _atexit_m would fail to assign the correct value to __onexitbegin_m and __onexitend_m.
__onexitbegin_m and __onexitend_m were shared by the different threads; It's the key point of such issue. For example, the following statements,
__onexitbegin_m = (_CPVFV *)_encode_pointer(onexitbegin_m);
__onexitend_m = (_CPVFV *)_encode_pointer(onexitend_m);
should also guarded by __global_lock() and __global_unlock() or other syn primitives.
__global_lock();
__onexitbegin_m = (_CPVFV *)_encode_pointer(onexitbegin_m);
__onexitend_m = (_CPVFV *)_encode_pointer(onexitend_m);
__global_unlock();
extern "C" int __clrcall _atexit_m(_CPVFV func)
{
MANAGED_ASSERT(AppDomain::CurrentDomain->IsDefaultAppDomain(), "This fuction must be called in the default domain");
__global_lock();
_CPVFV* onexitbegin_m = (_CPVFV*)_decode_pointer(__onexitbegin_m);
_CPVFV* onexitend_m = (_CPVFV*)_decode_pointer(__onexitend_m);
__global_unlock();
int retval = _atexit_helper((_CPVFV)_encode_pointer(func), &__exit_list_size, &onexitend_m, &onexitbegin_m);
__global_lock();
__onexitbegin_m = (_CPVFV*)_encode_pointer(onexitbegin_m);
__onexitend_m = (_CPVFV*)_encode_pointer(onexitend_m);
__global_unlock();
return retval;
}
2010年12月19日
Linking issue
- While different modules (.obj) using istreambuf_iterator/ostreambuf_iterator, compiled with different options on HID/no-HID and SCL/no-SCL, these modules could not be linked successfully;
The error comes directly from the CLR when a type has multiple definitions that are not consistent based upon the ODR, one-definition-rule for types. And, the linker itself isn't involved.
For example, with one module compile with /D_SECURE_SCL=0, while another is compiled with _SECURE_SCL=1;
At first, it's found that with _SECURE_SCL, the only thing that could be different as following,
#if _SECURE_SCL
typedef _Range_checked_iterator_tag _Checked_iterator_category;
#endif
But, actually, it's not the typedef that changed the layout the these iterators (istreambuf_iterator/ostreambuf_iterator), and further they don't really use the extra pointer that _SECURE_SCL adds.
Finally, it's found the root cause is that, these iterators, istreambuf_iterator/ostreambuf_iterator had been moved from <xutility> to <streambuf>, and their ultimate base class had been changed from _Iterator_base_secure to _Iterator_base. And, the layout of _Iterator_base would be different between HID and no-HID, and between SCL and no-SCL. It is the cause where the issue comes from.
What we can learn from such issue,
These iterators really shouldn't derive from either _Iterator_base_secure or _Iterator_base, because these classes contain data members (pointers) which are entirely unused. It would result in unnecessary bloat and extra work being performed in ctor/dtor etc.
Introduce a new class, _Iterator_base_universal, which is defined identically regardless of HID/no-HID and SCL/no-SCL. It would contains the three internal typedefs that all standard iterators need to have, and nothing else. And _Iterator_base (in all of its variants) and _Iterator_base_secure now should derive from _Iterator_base_universal to get these typedefs.
Now, when an iterator wants these typedefs, but not the data members of _Iterator_base and _Iterator_base_secure, it could derive from _Iterator_base_universal. And istreambuf_iterator and ostreambuf_iterator are now as small as possible, and keep identical representations or layout across HID/no-HID, SCL/no-SCL.
【 某某提到: 】
: 一般說COM復雜,首先是名詞太多,其次是基于ATL的實現(xiàn)比較難懂
: 這并不是COM本身復雜,而是C++已經(jīng)落后于時代了。所以ATL看起來才會像天書一般
雖然對于全新的工程項目,推薦通過.net實現(xiàn),但是,只要你工作在Windows平臺上,必然會遇到和COM相關(guān)的技術(shù)和機制,無論是大量的legacy的工程和代碼,還是作為OS重要功能以及native組件的首選交互形式和接口暴露方式,比如DirectX API,比如一些WMI的API;最有趣的是,即使是.net的核心CLR本身也是一個COM組件,可以通過Host相關(guān)接口讓native應(yīng)用來加載,以在當前進程中啟動整個CLR的虛擬執(zhí)行環(huán)境或者叫托管執(zhí)行環(huán)境(managed executive environment)。
把握COM有兩點很關(guān)鍵,
1)Interface-based design,從設(shè)計和編碼思路上就是要完全基于接口;
2)VirtualTable-based binary compatibility, 實現(xiàn)上無論何種語言或者機制,只要符合基于虛表的二進制兼容規(guī)范,就都可以實施;
COM僅僅是個規(guī)范,基于COM的具體技術(shù)非常之多,OLE,Automation,Structural storage,ActiveX...汗牛充棟,還有COM+,這個是提供企業(yè)級開發(fā)必備的一些基礎(chǔ)功能和設(shè)施,比如,事務(wù)管理機制,對象池,安全管理,消息隊列...需要指出,目前即便是.net Framework也沒有實現(xiàn)COM+所提供這些機制,只是簡單的封裝了后者。
COM技術(shù)中可能有一些比較困難的地方,接口的一致性,對象的聚合和生命周期,套間,跨套間的接口訪問,名字對象,等等;這些并不是COM規(guī)范人為制造的困難,而是為了設(shè)計和提供,可以跨進程和機器邊界,跨異構(gòu)平臺(當然必須實現(xiàn)了COM所規(guī)定的基礎(chǔ)服務(wù)),透明化具體對象類型及對象生命周期,便于統(tǒng)一部署和版本管理的組件技術(shù),所必須付出的代價,這個代價從開發(fā)人員角度看具體表現(xiàn)為,概念理解的困難以及具體二進制實現(xiàn)的困難;
不過從另一個角度看,COM已經(jīng)很容易了,
a) COM規(guī)范已把要達致這些目標的系統(tǒng),所必須提供的接口和特性抽象了出來,只不過為了表達這些抽象的概念而新造的術(shù)語名詞有些陌生和突兀;如果讓遇到相似問題的每一個設(shè)計和開發(fā)人員都自己來做抽象,未必會生成更好的方案;
b) 為了幫助設(shè)計和開發(fā)人員,人們提供了很多的開發(fā)庫,以提高COM開發(fā)的正確性和效率;最顯著的就是MFC中關(guān)于COM/OLE的輔助類和函數(shù),以及為了COM而生的ATL;從本質(zhì)上看,這些類庫都是把COM規(guī)范中必須實現(xiàn)的,Windows平臺本身沒有提供,具體設(shè)計和開發(fā)人員實際實施時會重復實現(xiàn)的,同時又非常容易出錯的那部分功能,集中到了這些類庫里統(tǒng)一實現(xiàn),讓具體設(shè)計和開發(fā)人員以代碼重用的形式來實現(xiàn)COM規(guī)范;
當然人們也意識到了COM這樣的一些問題,特別是具體實現(xiàn)時設(shè)計和開發(fā)人員必須要關(guān)注幾乎所有的二進制細節(jié),于是.net就誕生了,把這些規(guī)范的許多復雜性都封裝在了虛擬機里面,把這些目標功能(跨邊界、透明性等等)通過一致而又平滑的平臺接口和自描述的meta data,以一種讓設(shè)計和開發(fā)人員更易接受的風格開放了出來;
COM的影響是非常廣大的,比如XPCOM ,F(xiàn)irefox上的一種插件技術(shù)標準,就是根據(jù)COM的思想和原則制定的;許多評論說,F(xiàn)irefox的成功是因為它插件是如此的成功,這也算是COM本身所意料不到的貢獻之一。
在.net的平臺上,即使是.net CLR/SSCLI的具體實現(xiàn)也大量運用了COM的思想和機制,可以說.net就是搭建在COM二進制組件平臺之上的虛擬機托管平臺。
最后,.net開始時的內(nèi)部編號是COM 2.0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*) 關(guān)于“名詞太多”
這是要實現(xiàn)可以跨進程和機器邊界,跨異構(gòu)平臺(當然必須實現(xiàn)了COM所規(guī)定的基礎(chǔ)服務(wù)),透明化具體對象類型及對象生命周期,便于統(tǒng)一部署和版本管理的組件技術(shù),所必須付出的代價。
COM規(guī)范已把要達致這些目標的系統(tǒng),所必須提供的接口和特性抽象了出來,只不過為了表達這些抽象的概念而新造的術(shù)語名詞有些陌生和突兀;如果讓遇到相似問題的每一個設(shè)計和開發(fā)人員都自己來做抽象,未必會生成更好的方案;
舉個例子,apartment,套間,就是為了抽象傳統(tǒng)OS中進程和線程的實現(xiàn)而新造的術(shù)語名詞和概念;任何人要抽象這樣的一些概念,不新造術(shù)語,是非常困難的,對比.net,后者用了CLR虛擬機來封裝了大多數(shù)的實現(xiàn)細節(jié),并用讓人更容易接受的風格來開放接口,可事實上仍然新造了一些名詞和概念,如類似范疇的AppDomain;
*) 關(guān)于“基于ATL的實現(xiàn)比較難懂”
ATL主要使用了template技術(shù),COM接口智能指針,用靜態(tài)轉(zhuǎn)換來模擬動態(tài)綁定,等等,實際并不是很復雜,只能算c++實現(xiàn)機制的中等難度,主要涉及Modern C++ design一書中一些相關(guān)設(shè)計理念的運用。對比Boost中某些庫的實現(xiàn),ATL很人道了。
*) 關(guān)于“這并不是COM本身復雜,而是C++已經(jīng)落后于時代了”
首先COM的規(guī)范的確是復雜的,為啥?第一點已經(jīng)說了,就是為了要抽象出跨邊界和對象透明的組件技術(shù);.net表象上看比較“簡單容易”,風格親近設(shè)計和開發(fā)人員,實際上復雜事務(wù)和實現(xiàn)細節(jié)都被劃分到CLR那個層面上去實現(xiàn)了;去看一下CLR的開源實現(xiàn)SSCLI,你會發(fā)現(xiàn),整個虛擬機平臺的實現(xiàn),大量運用了COM的思想和機制,就是一個巨型系統(tǒng)平臺級的COM server;
其次,COM規(guī)范本身是獨立于實現(xiàn)語言的,只要構(gòu)建出的組件符合規(guī)范制定的二進制兼容,系統(tǒng)就可以運作,這和C++是否落后時代沒有關(guān)系。如果開發(fā)人員認為,.net才夠先進,也完全可以用.net中的托管語言,如C#來實現(xiàn)COM組件;
最后,每種語言都有其適用的范圍,現(xiàn)在可以這么說“如果有一個全新的項目需求,要達致跨邊界和對象透明組件,并且沒有太過嚴苛的性能需求,那么.net平臺及其上的托管語言來實現(xiàn),比用C++及相關(guān)輔助類庫來以COM組件形式來實現(xiàn),要更合適,也更快速便捷和節(jié)省預算。”但是,在這個判斷上我們加了很多嚴格的約束,一旦需求變更,特別是項目的非功能性需求,要求高性能運算或者更順暢的與legacy的native系統(tǒng)相互,那么“使用native語言來實現(xiàn)性能關(guān)鍵以及l(fā)egacy交互功能,通過COM封裝,再由COMInterop交.net托管應(yīng)用調(diào)用”可能是更現(xiàn)實的方案。C++是一門活的語言,不斷發(fā)展的語言,即使在最新的托管時代里,C#成為標準主流,但C++/CLI仍然是托管語言里功能最完整的語言。
2010年12月13日
金山系列軟件中的一部分代碼open source了,自然地引起網(wǎng)絡(luò)上以及IT業(yè)界的一片熱評。其中關(guān)于已經(jīng)開源的這部分外圍代碼的代碼質(zhì)量的問題,更是熱中之熱;以下是關(guān)于這個問題個人的一些思考:
從本質(zhì)上說這里面就是個trade off,也就是平衡和取舍的問題。產(chǎn)品項目的預算投入,進度壓力,各方面人員的協(xié)調(diào),風格和習慣的統(tǒng)一,等等。
許多優(yōu)秀開源項目,比如Boost,其中很多作者本身都是學者兼開發(fā)或者是帶有研究性質(zhì)的開發(fā)人員,在高校、非盈利組織或者商業(yè)企業(yè)的非直接盈利項目的資金支持下,在很少進度壓力和商業(yè)壓力的情況下,精雕細琢,多次迭代后,構(gòu)建出的精品代碼。用這樣的標準來要求所有的軟件產(chǎn)品,特別是商業(yè)產(chǎn)品(當然除去少數(shù)關(guān)系重大和長遠的基礎(chǔ)核心部分外)的構(gòu)建,是不科學的,也是不合算的,因為及時占領(lǐng)市場和足夠的盈利,以及獲得用戶的贊許才是商業(yè)軟件最重要的目標。
回頭來看金山目前開源的這些產(chǎn)品,比如這里討論的金山衛(wèi)士,其從推出就是免費的,是為了市場上的先期推出的同類工具軟件及時比拼占領(lǐng)些許相關(guān)市場份額,其并不是金山的基礎(chǔ)和核心產(chǎn)品;從這些先天的條件看,這個產(chǎn)品的商業(yè)投入不會很大同時又有快速推出的要求,能有目前這樣的產(chǎn)品質(zhì)量,是合理的,從企業(yè)角度和用戶角度看也都是可以接受的。
說到這里,就感覺有必要涉及一下“重構(gòu)”,這個現(xiàn)在大家都很重視同時也經(jīng)常談及的話題。為何大家都很重視?而且常常談及?這其中當然有軟件構(gòu)建本身的特點,比如對需求理解的不斷深入和調(diào)整、設(shè)計的不斷改善和演進、代碼風格的統(tǒng)一以及細節(jié)的完善等等;但是,有個大家在潛意識里都感覺到,平時卻很少談及的緣由--進度和成本,因為有了這些壓力,產(chǎn)品的第一版往往不是很完美的,然后如果還做后續(xù)版本的話,那么就要引入重構(gòu);因為有了這些壓力,在經(jīng)過多年之后,如果這個產(chǎn)品還存在的話,那么就要進行大規(guī)模的重構(gòu)。簡單的說,重構(gòu)之所以重要,不僅僅是軟件構(gòu)建本身特點所引發(fā),也是商業(yè)壓力之下的構(gòu)建過程的有效應(yīng)對之道。
Fusion is one of the most importants features among ones in the runtime implementation of CLI.
In the fusion, or any other components or modules, how to retrieve the execution engine instance and how to generate such engine?
UtilExecutionEngine, implemented as COM object, support Queryinterface/AddRef/Release, and exposed via interface IExecutionEngine.
With SELF_NO_HOST defined,
BYTE g_ExecutionEngineInstance[sizeof(UtilExecutionEngine)];
g_ExecutionEngineInstance would be the singleton instance of current execution engine,
otherwise, without SELF_NO_HOST, the 'sscoree' dll would be loaded and try to get the exported function, which is named 'IEE' from such dll. Here, it is the well-known shim, in .net CLR, such module is named 'mscoree'. Further, if 'IEE' could not be found in such dll, system would try to locate another exported function, named 'LoadLibraryShim', and use such function to load the 'mscorwks' module, and try to locate the 'IEE' exportd functionin it.
It's very obvious that Rotor has implemented its own execution engine, but it also gives or make space for implementation of execution engine from 3rd party. Here, .net CLR is a good candidate definitely, Rotor might load the mscorwks.dll module for its usage.
PAL, PALAPI, for example, HeapAlloc, one famous WIN32 API, has been implemented as one PALAPI (defined in Heap.c), to make it possible that the CLI/Rotor be ported smoothly to other OS, such freebsd/mac os.
CRT routines are also reimplemented, such as memcpy, it has been implemented as GCSafeMemCpy
There're many macros in fuctions, such as SCAN_IGNORE_FAULT/STATIC_CONTRACT_NOTHROW/STATIC_CONTRACT_NOTRIGGER, they are for static analysis tool to scan, analyse and figour out the potential issues in code.
From view point of the execution model by CLI, the act of compiling (including JIT) high-level type descriptions would be separated from the act of turning these type descriptions into processor-specific code and memory structures.
And such executino model, in other word, the well-known 'managed execution', would defer the loading, verification and compilation of components until runtime really needs; At the same time, the type-loading is the key trigger that causes CLI's tool chain to be engaged at runtime. Deferred compilation(lead to JIT)/linking/loading would get better portability to different target platform and be ready for version change; The whole deferred process would driven by well-defined metadata and policy, and it would be very robust for building a virtual execution environment;
At the top of such CLI tool chain, fusion is reponsible for not only finding and binding related assemblies, which are via assembly reference defined in assembly, fusion also takes another important role, loader, and its part of functionality is implemented in PEAssembly, ClassLoader classes. For example, ClassLoader::LoadTypeHandleForTypeKey.
For types in virtual execution environment of CLI, rotor defines four kinds of elements for internal conducting,
ELEMENT_TYPE_CLASS for ordinary classes and generic instantiations(including value types);
ELEMENT_TYPE_ARRAY AND ELEMENT_TYPE_SZARRAY for array types
ELEMENT_TYPE_PRT and ELEMENT_TYPE_BYREF for pointer types
ELEMENT_TYPE_FNPTR for function pointer types
every type would be assigned unique ulong-typed token, and such token would be used to look up in m_TypeDefToMethodTableMap (Linear mapping from TypeDef token to MethodTable *)which is maintained by current module; If there it is, the pointer to method table of such type would be retrieved, or it would look up in the loader module, where the method table should exist in while it's JIT loaded, not launched from NGEN image;
And all the unresolved typed would be maintained in a hash table, PendingTypeLoadTable; Types and only those types that are needed, such as dependencies, including parent types, are loaded in runtime, such type is fully loaded and ready for further execution, and other unresolved types would be kept in the previous hash table.
關(guān)于程序設(shè)計語言本身的設(shè)計有許多有趣的話題,比如,為何C++中的類成員函數(shù)沒有采用類似Java中的“全虛”設(shè)計?
1) 從語言本身設(shè)計上看,
效率定然是c++當初設(shè)計時考慮的重點之一,舉個例子,為了節(jié)省不必要的VTable開銷,ATL用template技術(shù)靜態(tài)轉(zhuǎn)換來模擬動態(tài)綁定以支持COM特性的實現(xiàn);和C的兼容,就VTable角度看,問題不大,因為后者可以用函數(shù)指針數(shù)組來模擬;
2) 再從大多數(shù)應(yīng)用中常見的類繼承體系上看,
除了整個繼承體系所統(tǒng)一開放出來的接口集(也就是由虛函數(shù)所組成),在繼承體系的每個層面另外會有大量的其他輔助成員函數(shù)(其數(shù)量通常比虛函數(shù)多的多),這些成員函數(shù)完全沒必要設(shè)計成虛函數(shù);
3) 從其他語言看,
即使較新的虛擬機語言C#(Java算是較老的虛擬機語言),反而定義了比C++更為嚴格更為顯式的成員方法實現(xiàn)或覆蓋或重載或新建的規(guī)則;這是非常重要的對C++以及Java設(shè)計思想的反思。
4) 從語言的適用場合看,
我們現(xiàn)在的討論,絕大多數(shù)情況下帶有一個非常重要的默認前提,那就是在用戶態(tài)模式下使用C++,如果放寬這個約束,在內(nèi)核模式下使用C++,那情況又完全不同了。
引用下面這個文檔的觀點,http://www.microsoft.com/china/whdc/driver/kernel/KMcode.mspx
首先,用戶態(tài)下非常廉價幾乎不用考慮的資源,在內(nèi)核中是非常昂貴的,比如內(nèi)核堆棧一般就3個page;
在內(nèi)核不能分頁(paging)時必須保證將被執(zhí)行的所有代碼和數(shù)據(jù)必須有效的駐留在物理內(nèi)存中,如果這時需要多駐留幾張?zhí)摫硪约疤摫碇羔樐沁€是顯得非常昂貴的,同時編譯器為虛函數(shù),模板等生成代碼的方式,讓開發(fā)人員很難確定要執(zhí)行一個函數(shù)所需要的所有代碼的所在位置,因此也無法直接控制用于安置這些代碼的節(jié)(個人認為可能通過progma segment/datasegment/codesegment對于代碼和數(shù)據(jù)進行集中控制),因此在需要這些代碼時,可能已經(jīng)被page out了;
所有涉及類層次結(jié)構(gòu),模板,異常等等這樣的一些語言結(jié)構(gòu)在內(nèi)核態(tài)中都可能是不安全的,最好是把類的使用限定為POD類,回到我們的主題虛函數(shù),也就是說內(nèi)核態(tài)下類設(shè)計中沒有虛函數(shù)。