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日
【 在 某 的大作中提到: 】
: 比如我有一個(gè)CMyButton的類,我現(xiàn)在有他的一個(gè)handle
: 編譯器怎么根據(jù)這個(gè)句柄找到CMyButton的代碼的?
【 在 某某 的大作中提到: 】
: 這個(gè)和OS/Compiler沒關(guān)系,是庫(kù)起的作用
: 以從某個(gè)文章里看的,說MFC用了一個(gè)大map,沒驗(yàn)證過
: 有本講GDI的書里,用了WNDCLASS里的extra bytes來實(shí)現(xiàn)的這個(gè)映射
MFC的應(yīng)用里,每個(gè)MFC線程(必須要使用MFC方式啟動(dòng)的線程)都維護(hù)有一個(gè)MFC object和HWND之間的
mapping,整個(gè)MFC框架就是使用這個(gè)機(jī)制來實(shí)現(xiàn)應(yīng)用級(jí)C++對(duì)象和系統(tǒng)級(jí)原生窗口內(nèi)核對(duì)象之間的關(guān)聯(lián);
因?yàn)檫@個(gè)mapping是以線程為單位來維護(hù)的,每個(gè)線程間互不關(guān)聯(lián),所以,一個(gè)應(yīng)用里對(duì)于涉及UI窗口的
任務(wù)最好是都放在同一個(gè)線程里面,一般就是當(dāng)前進(jìn)程的主線程,否則可能出現(xiàn)MFC object和HWND之間
關(guān)聯(lián)不上的問題,而且這樣的問題還很隱蔽。
至于WNDCLASS結(jié)構(gòu)自帶的extra bytes域,是以前缺乏應(yīng)用框架的時(shí)代,使用Win32 API直接開發(fā)時(shí),讓每個(gè)
窗口類(這里的類,不是C++ class的概念,而是Windows系統(tǒng)窗口定義時(shí)的一種數(shù)據(jù)結(jié)構(gòu))都能有個(gè)附
帶一些額外的自定義數(shù)據(jù)的空間,這個(gè)空間往往被用來存放與當(dāng)前窗口類相關(guān)的用戶數(shù)據(jù),通常是指向
某個(gè)內(nèi)存區(qū)域的指針,當(dāng)程序操作這個(gè)屬于這個(gè)窗口類的窗口時(shí)就可以根據(jù)這個(gè)附帶的自定義數(shù)據(jù)(或
者指針)來操作對(duì)應(yīng)的關(guān)聯(lián)自定義數(shù)據(jù);很多后來出現(xiàn)的框架,也都使用了這個(gè)extra bytes域,來存放
框架本身的一些和窗口類相關(guān)聯(lián)的數(shù)據(jù)結(jié)構(gòu)。從目前趨勢(shì)看,直接使用WNDCLASS以及extra bytes的可能
性是微乎其微了,但是如果要做好原生應(yīng)用的開發(fā),很多底層的實(shí)現(xiàn)細(xì)節(jié)最要還是要知道一下,以便于
優(yōu)化結(jié)構(gòu)和性能,以及出錯(cuò)時(shí)的調(diào)試處理;因?yàn)闊o論是Winform/WPF,還是跨平臺(tái)的WTL/QT/WxWindows等
等新型的機(jī)制或者框架、類庫(kù),只要是在Windows平臺(tái)上搭建的,那都是基于前面說過的這套最基本也是
最核心的Win32 API基礎(chǔ)之上。
2011年2月27日
這道千分題,其實(shí)是挺有意思的一題:
提供了點(diǎn)(這里是junction)和點(diǎn)之間的距離(或代價(jià));
要求以最短的距離(或最小代價(jià))遍歷所有的點(diǎn),同時(shí)每個(gè)點(diǎn)可以多次訪問;
初看之下,給人的感覺是圖論相關(guān)的問題,比如旅行者問題、歐拉環(huán)游之類。
在思考這個(gè)問題的時(shí)候,忽然間聯(lián)想到了圖論中的最小生成樹,雖然并不是真正要去得出最小生成樹,
但是按照最小生成樹所提供的思路--這點(diǎn)很重要--那就是圖和樹之間有著相當(dāng)密切的關(guān)系:即使最小生
成樹并不能直接解決這個(gè)問題,但是它們之間存在的這層關(guān)系的確提供了解決問題的一個(gè)有益的嘗試方
向;
于是,思考進(jìn)了一步,問題從“圖”簡(jiǎn)化成了“樹”--如何把當(dāng)前這個(gè)問題采用樹的結(jié)構(gòu)和方法表達(dá)出
來:樹的根節(jié)點(diǎn),很自然地想到了由問題中旅行的起始節(jié)點(diǎn)來表達(dá);然后,隨著節(jié)點(diǎn)的不斷加入,樹就
自然地生成,此處的關(guān)鍵在于如何生成,或者說節(jié)點(diǎn)加入的規(guī)則,以及每個(gè)節(jié)點(diǎn)為了適應(yīng)這個(gè)規(guī)則,所
必須持有的相關(guān)屬性信息:最直接的,父子節(jié)點(diǎn)之間的關(guān)系需要維護(hù),從父節(jié)點(diǎn)到子節(jié)點(diǎn)的距離(或代
價(jià))必須要保留,其次,考慮到如果每個(gè)節(jié)點(diǎn)都維護(hù)相關(guān)的距離(代價(jià))信息,那么從當(dāng)前節(jié)點(diǎn)到根節(jié)
點(diǎn)的代價(jià)也就可以由此遞推得出,進(jìn)一步,我們所要求出的最短路徑(或最小代價(jià))不就可以從上述這
些節(jié)點(diǎn)中維護(hù)的距離信息中得出嗎?這是非常關(guān)鍵的一步,它把當(dāng)前我們構(gòu)建的數(shù)據(jù)結(jié)構(gòu)和問題的要求
之間建立起了相當(dāng)直接的聯(lián)系。這說明我們目前思考的方向是有價(jià)值的而且極有可能順著這個(gè)方向前行
,可以得出相當(dāng)不錯(cuò)的結(jié)果。
顯然,既然要求最短路徑(最小代價(jià)),那么我們目前構(gòu)建出的這顆Junction樹(因?yàn)槠渖系墓?jié)點(diǎn)在題
中的物理含義是代表Junction,那這里我們就姑且稱其為Junction Tree),樹上的每個(gè)節(jié)點(diǎn)也應(yīng)當(dāng)保留
在其之下的子樹的最短路徑(最小代價(jià)),這就相當(dāng)于把每個(gè)節(jié)點(diǎn)都作為根節(jié)點(diǎn),然后求出各條子路徑
的代價(jià),并比較得出最短路徑(最小代價(jià)),以及在這條最短路徑上的直接子節(jié)點(diǎn);
每加入一個(gè)子節(jié)點(diǎn),就要對(duì)上述已構(gòu)建出的這些數(shù)據(jù)結(jié)構(gòu)中的信息進(jìn)行維護(hù),以調(diào)整每個(gè)節(jié)點(diǎn)當(dāng)前的最
短路徑代價(jià)和相應(yīng)這條路徑上的直接子節(jié)點(diǎn);當(dāng)所有原“圖”中的“邊”信息,也就是
(fromJunction,toJuction,ductLength)所代表的(起始點(diǎn),終止點(diǎn),長(zhǎng)度代價(jià)),都按照上述方案加入
Juction Tree之后,我們可以知道從最初的起始節(jié)點(diǎn)(也就是Junction Tree的根節(jié)點(diǎn))到最終節(jié)點(diǎn)的(
Junction Tree上的某條路徑上的葉子節(jié)點(diǎn))的最短(最小代價(jià))路徑了。
對(duì)于Juction Tree這個(gè)ADT抽象數(shù)據(jù)結(jié)構(gòu)的具體實(shí)現(xiàn),考慮到優(yōu)先隊(duì)列中二叉堆的經(jīng)典實(shí)現(xiàn)往往使用數(shù)組
,同時(shí)也為了符合TC SRM一貫的簡(jiǎn)捷明快的程序設(shè)計(jì)風(fēng)格,我們這里同時(shí)使用幾個(gè)數(shù)組來維護(hù)前述構(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)常會(huì)被使用到的。
反射總體上分成兩大特性,一是自省,二是發(fā)射;
自省的能力極為重要,而且?guī)缀鯐?huì)天天用到,很少見到過哪個(gè).net應(yīng)用中不使用attribute的,而attribute特性就是metadata通過在自省能力支撐下實(shí)現(xiàn)的;當(dāng)然自省不單單是attribute特性的運(yùn)用,只要是在運(yùn)行時(shí)動(dòng)態(tài)檢視程序自身的特性都要由反射的自省能力來支持,比如Visual Studio的IDE(這個(gè)集成開發(fā)環(huán)境本身就是.net應(yīng)用的好案例)對(duì)于.net組件的自動(dòng)探測(cè)功能;同時(shí),自省的能力也是基于虛擬機(jī)平臺(tái)的語言,比如c#和java,區(qū)別于傳統(tǒng)語言比如c和c++的重要特性之一,這提供了程序設(shè)計(jì)開發(fā)更為便利和安全的運(yùn)行時(shí)環(huán)境;相對(duì)而言,在c++(當(dāng)然是native而不是managed)的環(huán)境下,除了RTTI極為單薄的運(yùn)行時(shí)自省,也就是QT這個(gè)庫(kù)通過meta-object system部分模擬了自省的特性;
反射的另外一個(gè)重要特性就是發(fā)射,它讓“程序可以寫程序”了,簡(jiǎn)要的說就是在運(yùn)行時(shí)動(dòng)態(tài)生成MSIL并加載運(yùn)行以及持久化動(dòng)態(tài)生成的MSIL的能力;由這個(gè)特性的支持,讓原先一些程序設(shè)計(jì)和開發(fā)領(lǐng)域相對(duì)困難和繁瑣的工作,比如元編程meta programming,比如動(dòng)態(tài)代理dynamic proxy,比如AOP中的基礎(chǔ)設(shè)施weaver的實(shí)現(xiàn),變得可能或相對(duì)易于實(shí)現(xiàn);反射的特性,也是基于虛擬機(jī)平臺(tái)CLR的支持,以metadata為基礎(chǔ)來實(shí)現(xiàn)的,所以這也是虛擬機(jī)平臺(tái)語言的特有優(yōu)勢(shì),而在傳統(tǒng)語言平臺(tái)上,這是難以實(shí)現(xiàn)的;比如關(guān)于meta programming,c++就是通過模板特性實(shí)現(xiàn)的編譯期meta programming,這與虛擬機(jī)平臺(tái)上實(shí)現(xiàn)的運(yùn)行時(shí)meta programming還是有比較大的差距(比如前者如何保證生成的代碼的type-safe);
以上這兩個(gè)特性,自省和發(fā)射,都有個(gè)共同點(diǎn),他們都是圍繞著metadata機(jī)制,并在虛擬機(jī)平臺(tái)運(yùn)行時(shí)環(huán)境CLR支持下實(shí)現(xiàn)的,前者是運(yùn)行時(shí)檢視相關(guān)的metadata,后者是運(yùn)行時(shí)動(dòng)態(tài)生成相關(guān)的metadata和MSIL;從這點(diǎn)也就可以看出,要想深入理解這些特性,就需要研究metadata和MSIL的實(shí)現(xiàn),以及虛擬機(jī)運(yùn)行時(shí)環(huán)境的實(shí)現(xiàn)(在java平臺(tái)上,就是bytecode和JVM);
所以,反射,可能是虛擬機(jī)平臺(tái)所提供的相對(duì)最為強(qiáng)勁,最為復(fù)雜,和平臺(tái)運(yùn)行時(shí)本身關(guān)系最密切,也是區(qū)別于傳統(tǒng)語言和運(yùn)行時(shí)最鮮明的特性。
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復(fù)雜,首先是名詞太多,其次是基于ATL的實(shí)現(xiàn)比較難懂
: 這并不是COM本身復(fù)雜,而是C++已經(jīng)落后于時(shí)代了。所以ATL看起來才會(huì)像天書一般
雖然對(duì)于全新的工程項(xiàng)目,推薦通過.net實(shí)現(xiàn),但是,只要你工作在Windows平臺(tái)上,必然會(huì)遇到和COM相關(guān)的技術(shù)和機(jī)制,無論是大量的legacy的工程和代碼,還是作為OS重要功能以及native組件的首選交互形式和接口暴露方式,比如DirectX API,比如一些WMI的API;最有趣的是,即使是.net的核心CLR本身也是一個(gè)COM組件,可以通過Host相關(guān)接口讓native應(yīng)用來加載,以在當(dāng)前進(jìn)程中啟動(dòng)整個(gè)CLR的虛擬執(zhí)行環(huán)境或者叫托管執(zhí)行環(huán)境(managed executive environment)。
把握COM有兩點(diǎn)很關(guān)鍵,
1)Interface-based design,從設(shè)計(jì)和編碼思路上就是要完全基于接口;
2)VirtualTable-based binary compatibility, 實(shí)現(xiàn)上無論何種語言或者機(jī)制,只要符合基于虛表的二進(jìn)制兼容規(guī)范,就都可以實(shí)施;
COM僅僅是個(gè)規(guī)范,基于COM的具體技術(shù)非常之多,OLE,Automation,Structural storage,ActiveX...汗牛充棟,還有COM+,這個(gè)是提供企業(yè)級(jí)開發(fā)必備的一些基礎(chǔ)功能和設(shè)施,比如,事務(wù)管理機(jī)制,對(duì)象池,安全管理,消息隊(duì)列...需要指出,目前即便是.net Framework也沒有實(shí)現(xiàn)COM+所提供這些機(jī)制,只是簡(jiǎn)單的封裝了后者。
COM技術(shù)中可能有一些比較困難的地方,接口的一致性,對(duì)象的聚合和生命周期,套間,跨套間的接口訪問,名字對(duì)象,等等;這些并不是COM規(guī)范人為制造的困難,而是為了設(shè)計(jì)和提供,可以跨進(jìn)程和機(jī)器邊界,跨異構(gòu)平臺(tái)(當(dāng)然必須實(shí)現(xiàn)了COM所規(guī)定的基礎(chǔ)服務(wù)),透明化具體對(duì)象類型及對(duì)象生命周期,便于統(tǒng)一部署和版本管理的組件技術(shù),所必須付出的代價(jià),這個(gè)代價(jià)從開發(fā)人員角度看具體表現(xiàn)為,概念理解的困難以及具體二進(jìn)制實(shí)現(xiàn)的困難;
不過從另一個(gè)角度看,COM已經(jīng)很容易了,
a) COM規(guī)范已把要達(dá)致這些目標(biāo)的系統(tǒng),所必須提供的接口和特性抽象了出來,只不過為了表達(dá)這些抽象的概念而新造的術(shù)語名詞有些陌生和突兀;如果讓遇到相似問題的每一個(gè)設(shè)計(jì)和開發(fā)人員都自己來做抽象,未必會(huì)生成更好的方案;
b) 為了幫助設(shè)計(jì)和開發(fā)人員,人們提供了很多的開發(fā)庫(kù),以提高COM開發(fā)的正確性和效率;最顯著的就是MFC中關(guān)于COM/OLE的輔助類和函數(shù),以及為了COM而生的ATL;從本質(zhì)上看,這些類庫(kù)都是把COM規(guī)范中必須實(shí)現(xiàn)的,Windows平臺(tái)本身沒有提供,具體設(shè)計(jì)和開發(fā)人員實(shí)際實(shí)施時(shí)會(huì)重復(fù)實(shí)現(xiàn)的,同時(shí)又非常容易出錯(cuò)的那部分功能,集中到了這些類庫(kù)里統(tǒng)一實(shí)現(xiàn),讓具體設(shè)計(jì)和開發(fā)人員以代碼重用的形式來實(shí)現(xiàn)COM規(guī)范;
當(dāng)然人們也意識(shí)到了COM這樣的一些問題,特別是具體實(shí)現(xiàn)時(shí)設(shè)計(jì)和開發(fā)人員必須要關(guān)注幾乎所有的二進(jìn)制細(xì)節(jié),于是.net就誕生了,把這些規(guī)范的許多復(fù)雜性都封裝在了虛擬機(jī)里面,把這些目標(biāo)功能(跨邊界、透明性等等)通過一致而又平滑的平臺(tái)接口和自描述的meta data,以一種讓設(shè)計(jì)和開發(fā)人員更易接受的風(fēng)格開放了出來;
COM的影響是非常廣大的,比如XPCOM ,F(xiàn)irefox上的一種插件技術(shù)標(biāo)準(zhǔn),就是根據(jù)COM的思想和原則制定的;許多評(píng)論說,F(xiàn)irefox的成功是因?yàn)樗寮侨绱说某晒Γ@也算是COM本身所意料不到的貢獻(xiàn)之一。
在.net的平臺(tái)上,即使是.net CLR/SSCLI的具體實(shí)現(xiàn)也大量運(yùn)用了COM的思想和機(jī)制,可以說.net就是搭建在COM二進(jìn)制組件平臺(tái)之上的虛擬機(jī)托管平臺(tái)。
最后,.net開始時(shí)的內(nèi)部編號(hào)是COM 2.0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*) 關(guān)于“名詞太多”
這是要實(shí)現(xiàn)可以跨進(jìn)程和機(jī)器邊界,跨異構(gòu)平臺(tái)(當(dāng)然必須實(shí)現(xiàn)了COM所規(guī)定的基礎(chǔ)服務(wù)),透明化具體對(duì)象類型及對(duì)象生命周期,便于統(tǒng)一部署和版本管理的組件技術(shù),所必須付出的代價(jià)。
COM規(guī)范已把要達(dá)致這些目標(biāo)的系統(tǒng),所必須提供的接口和特性抽象了出來,只不過為了表達(dá)這些抽象的概念而新造的術(shù)語名詞有些陌生和突兀;如果讓遇到相似問題的每一個(gè)設(shè)計(jì)和開發(fā)人員都自己來做抽象,未必會(huì)生成更好的方案;
舉個(gè)例子,apartment,套間,就是為了抽象傳統(tǒng)OS中進(jìn)程和線程的實(shí)現(xiàn)而新造的術(shù)語名詞和概念;任何人要抽象這樣的一些概念,不新造術(shù)語,是非常困難的,對(duì)比.net,后者用了CLR虛擬機(jī)來封裝了大多數(shù)的實(shí)現(xiàn)細(xì)節(jié),并用讓人更容易接受的風(fēng)格來開放接口,可事實(shí)上仍然新造了一些名詞和概念,如類似范疇的AppDomain;
*) 關(guān)于“基于ATL的實(shí)現(xiàn)比較難懂”
ATL主要使用了template技術(shù),COM接口智能指針,用靜態(tài)轉(zhuǎn)換來模擬動(dòng)態(tài)綁定,等等,實(shí)際并不是很復(fù)雜,只能算c++實(shí)現(xiàn)機(jī)制的中等難度,主要涉及Modern C++ design一書中一些相關(guān)設(shè)計(jì)理念的運(yùn)用。對(duì)比Boost中某些庫(kù)的實(shí)現(xiàn),ATL很人道了。
*) 關(guān)于“這并不是COM本身復(fù)雜,而是C++已經(jīng)落后于時(shí)代了”
首先COM的規(guī)范的確是復(fù)雜的,為啥?第一點(diǎn)已經(jīng)說了,就是為了要抽象出跨邊界和對(duì)象透明的組件技術(shù);.net表象上看比較“簡(jiǎn)單容易”,風(fēng)格親近設(shè)計(jì)和開發(fā)人員,實(shí)際上復(fù)雜事務(wù)和實(shí)現(xiàn)細(xì)節(jié)都被劃分到CLR那個(gè)層面上去實(shí)現(xiàn)了;去看一下CLR的開源實(shí)現(xiàn)SSCLI,你會(huì)發(fā)現(xiàn),整個(gè)虛擬機(jī)平臺(tái)的實(shí)現(xiàn),大量運(yùn)用了COM的思想和機(jī)制,就是一個(gè)巨型系統(tǒng)平臺(tái)級(jí)的COM server;
其次,COM規(guī)范本身是獨(dú)立于實(shí)現(xiàn)語言的,只要構(gòu)建出的組件符合規(guī)范制定的二進(jìn)制兼容,系統(tǒng)就可以運(yùn)作,這和C++是否落后時(shí)代沒有關(guān)系。如果開發(fā)人員認(rèn)為,.net才夠先進(jìn),也完全可以用.net中的托管語言,如C#來實(shí)現(xiàn)COM組件;
最后,每種語言都有其適用的范圍,現(xiàn)在可以這么說“如果有一個(gè)全新的項(xiàng)目需求,要達(dá)致跨邊界和對(duì)象透明組件,并且沒有太過嚴(yán)苛的性能需求,那么.net平臺(tái)及其上的托管語言來實(shí)現(xiàn),比用C++及相關(guān)輔助類庫(kù)來以COM組件形式來實(shí)現(xiàn),要更合適,也更快速便捷和節(jié)省預(yù)算。”但是,在這個(gè)判斷上我們加了很多嚴(yán)格的約束,一旦需求變更,特別是項(xiàng)目的非功能性需求,要求高性能運(yùn)算或者更順暢的與legacy的native系統(tǒng)相互,那么“使用native語言來實(shí)現(xiàn)性能關(guān)鍵以及l(fā)egacy交互功能,通過COM封裝,再由COMInterop交.net托管應(yīng)用調(diào)用”可能是更現(xiàn)實(shí)的方案。C++是一門活的語言,不斷發(fā)展的語言,即使在最新的托管時(shí)代里,C#成為標(biāo)準(zhǔn)主流,但C++/CLI仍然是托管語言里功能最完整的語言。
2010年12月13日
金山系列軟件中的一部分代碼open source了,自然地引起網(wǎng)絡(luò)上以及IT業(yè)界的一片熱評(píng)。其中關(guān)于已經(jīng)開源的這部分外圍代碼的代碼質(zhì)量的問題,更是熱中之熱;以下是關(guān)于這個(gè)問題個(gè)人的一些思考:
從本質(zhì)上說這里面就是個(gè)trade off,也就是平衡和取舍的問題。產(chǎn)品項(xiàng)目的預(yù)算投入,進(jìn)度壓力,各方面人員的協(xié)調(diào),風(fēng)格和習(xí)慣的統(tǒng)一,等等。
許多優(yōu)秀開源項(xiàng)目,比如Boost,其中很多作者本身都是學(xué)者兼開發(fā)或者是帶有研究性質(zhì)的開發(fā)人員,在高校、非盈利組織或者商業(yè)企業(yè)的非直接盈利項(xiàng)目的資金支持下,在很少進(jìn)度壓力和商業(yè)壓力的情況下,精雕細(xì)琢,多次迭代后,構(gòu)建出的精品代碼。用這樣的標(biāo)準(zhǔn)來要求所有的軟件產(chǎn)品,特別是商業(yè)產(chǎn)品(當(dāng)然除去少數(shù)關(guān)系重大和長(zhǎng)遠(yuǎn)的基礎(chǔ)核心部分外)的構(gòu)建,是不科學(xué)的,也是不合算的,因?yàn)榧皶r(shí)占領(lǐng)市場(chǎng)和足夠的盈利,以及獲得用戶的贊許才是商業(yè)軟件最重要的目標(biāo)。
回頭來看金山目前開源的這些產(chǎn)品,比如這里討論的金山衛(wèi)士,其從推出就是免費(fèi)的,是為了市場(chǎng)上的先期推出的同類工具軟件及時(shí)比拼占領(lǐng)些許相關(guān)市場(chǎng)份額,其并不是金山的基礎(chǔ)和核心產(chǎn)品;從這些先天的條件看,這個(gè)產(chǎn)品的商業(yè)投入不會(huì)很大同時(shí)又有快速推出的要求,能有目前這樣的產(chǎn)品質(zhì)量,是合理的,從企業(yè)角度和用戶角度看也都是可以接受的。
說到這里,就感覺有必要涉及一下“重構(gòu)”,這個(gè)現(xiàn)在大家都很重視同時(shí)也經(jīng)常談及的話題。為何大家都很重視?而且常常談及?這其中當(dāng)然有軟件構(gòu)建本身的特點(diǎn),比如對(duì)需求理解的不斷深入和調(diào)整、設(shè)計(jì)的不斷改善和演進(jìn)、代碼風(fēng)格的統(tǒng)一以及細(xì)節(jié)的完善等等;但是,有個(gè)大家在潛意識(shí)里都感覺到,平時(shí)卻很少談及的緣由--進(jìn)度和成本,因?yàn)橛辛诉@些壓力,產(chǎn)品的第一版往往不是很完美的,然后如果還做后續(xù)版本的話,那么就要引入重構(gòu);因?yàn)橛辛诉@些壓力,在經(jīng)過多年之后,如果這個(gè)產(chǎn)品還存在的話,那么就要進(jìn)行大規(guī)模的重構(gòu)。簡(jiǎn)單的說,重構(gòu)之所以重要,不僅僅是軟件構(gòu)建本身特點(diǎn)所引發(fā),也是商業(yè)壓力之下的構(gòu)建過程的有效應(yīng)對(duì)之道。
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è)計(jì)語言本身的設(shè)計(jì)有許多有趣的話題,比如,為何C++中的類成員函數(shù)沒有采用類似Java中的“全虛”設(shè)計(jì)?
1) 從語言本身設(shè)計(jì)上看,
效率定然是c++當(dāng)初設(shè)計(jì)時(shí)考慮的重點(diǎn)之一,舉個(gè)例子,為了節(jié)省不必要的VTable開銷,ATL用template技術(shù)靜態(tài)轉(zhuǎn)換來模擬動(dòng)態(tài)綁定以支持COM特性的實(shí)現(xiàn);和C的兼容,就VTable角度看,問題不大,因?yàn)楹笳呖梢杂煤瘮?shù)指針數(shù)組來模擬;
2) 再?gòu)拇蠖鄶?shù)應(yīng)用中常見的類繼承體系上看,
除了整個(gè)繼承體系所統(tǒng)一開放出來的接口集(也就是由虛函數(shù)所組成),在繼承體系的每個(gè)層面另外會(huì)有大量的其他輔助成員函數(shù)(其數(shù)量通常比虛函數(shù)多的多),這些成員函數(shù)完全沒必要設(shè)計(jì)成虛函數(shù);
3) 從其他語言看,
即使較新的虛擬機(jī)語言C#(Java算是較老的虛擬機(jī)語言),反而定義了比C++更為嚴(yán)格更為顯式的成員方法實(shí)現(xiàn)或覆蓋或重載或新建的規(guī)則;這是非常重要的對(duì)C++以及Java設(shè)計(jì)思想的反思。
4) 從語言的適用場(chǎng)合看,
我們現(xiàn)在的討論,絕大多數(shù)情況下帶有一個(gè)非常重要的默認(rèn)前提,那就是在用戶態(tài)模式下使用C++,如果放寬這個(gè)約束,在內(nèi)核模式下使用C++,那情況又完全不同了。
引用下面這個(gè)文檔的觀點(diǎn),http://www.microsoft.com/china/whdc/driver/kernel/KMcode.mspx
首先,用戶態(tài)下非常廉價(jià)幾乎不用考慮的資源,在內(nèi)核中是非常昂貴的,比如內(nèi)核堆棧一般就3個(gè)page;
在內(nèi)核不能分頁(paging)時(shí)必須保證將被執(zhí)行的所有代碼和數(shù)據(jù)必須有效的駐留在物理內(nèi)存中,如果這時(shí)需要多駐留幾張?zhí)摫硪约疤摫碇羔樐沁€是顯得非常昂貴的,同時(shí)編譯器為虛函數(shù),模板等生成代碼的方式,讓開發(fā)人員很難確定要執(zhí)行一個(gè)函數(shù)所需要的所有代碼的所在位置,因此也無法直接控制用于安置這些代碼的節(jié)(個(gè)人認(rèn)為可能通過progma segment/datasegment/codesegment對(duì)于代碼和數(shù)據(jù)進(jìn)行集中控制),因此在需要這些代碼時(shí),可能已經(jīng)被page out了;
所有涉及類層次結(jié)構(gòu),模板,異常等等這樣的一些語言結(jié)構(gòu)在內(nèi)核態(tài)中都可能是不安全的,最好是把類的使用限定為POD類,回到我們的主題虛函數(shù),也就是說內(nèi)核態(tài)下類設(shè)計(jì)中沒有虛函數(shù)。