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的类,我现在有他的一个handle
: 编译器怎么根据这个句柄找到CMyButton的代码的?
【 在 某某 的大作中提到: 】
: 这个和OS/Compiler没关系,是库起的作用
: 以从某个文章里看的,说MFC用了一个大map,没验证过
: 有本讲GDI的书里,用了WNDCLASS里的extra bytes来实现的这个映射
MFC的应用里,每个MFC线程(必须要使用MFC方式启动的线程)都维护有一个MFC object和HWND之间的
mapping,整个MFC框架就是使用这个机制来实现应用级C++对象和系统级原生窗口内核对象之间的关联;
因为这个mapping是以线程为单位来维护的,每个线程间互不关联,所以,一个应用里对于涉及UI窗口的
任务最好是都放在同一个线程里面,一般就是当前进程的主线程,否则可能出现MFC object和HWND之间
关联不上的问题,而且这样的问题还很隐蔽。
至于WNDCLASS结构自带的extra bytes域,是以前缺乏应用框架的时代,使用Win32 API直接开发时,让每个
窗口类(这里的类,不是C++ class的概念,而是Windows系统窗口定义时的一种数据结构)都能有个附
带一些额外的自定义数据的空间,这个空间往往被用来存放与当前窗口类相关的用户数据,通常是指向
某个内存区域的指针,当程序操作这个属于这个窗口类的窗口时就可以根据这个附带的自定义数据(或
者指针)来操作对应的关联自定义数据;很多后来出现的框架,也都使用了这个extra bytes域,来存放
框架本身的一些和窗口类相关联的数据结构。从目前趋势看,直接使用WNDCLASS以及extra bytes的可能
性是微乎其微了,但是如果要做好原生应用的开发,很多底层的实现细节最要还是要知道一下,以便于
优化结构和性能,以及出错时的调试处理;因为无论是Winform/WPF,还是跨平台的WTL/QT/WxWindows等
等新型的机制或者框架、类库,只要是在Windows平台上搭建的,那都是基于前面说过的这套最基本也是
最核心的Win32 API基础之上。
2011年2月27日
这道千分题,其实是挺有意思的一题:
提供了点(这里是junction)和点之间的距离(或代价);
要求以最短的距离(或最小代价)遍历所有的点,同时每个点可以多次访问;
初看之下,给人的感觉是图论相关的问题,比如旅行者问题、欧拉环游之类。
在思考这个问题的时候,忽然间联想到了图论中的最小生成树,虽然并不是真正要去得出最小生成树,
但是按照最小生成树所提供的思路--这点很重要--那就是图和树之间有着相当密切的关系:即使最小生
成树并不能直接解决这个问题,但是它们之间存在的这层关系的确提供了解决问题的一个有益的尝试方
向;
于是,思考进了一步,问题从“图”简化成了“树”--如何把当前这个问题采用树的结构和方法表达出
来:树的根节点,很自然地想到了由问题中旅行的起始节点来表达;然后,随着节点的不断加入,树就
自然地生成,此处的关键在于如何生成,或者说节点加入的规则,以及每个节点为了适应这个规则,所
必须持有的相关属性信息:最直接的,父子节点之间的关系需要维护,从父节点到子节点的距离(或代
价)必须要保留,其次,考虑到如果每个节点都维护相关的距离(代价)信息,那么从当前节点到根节
点的代价也就可以由此递推得出,进一步,我们所要求出的最短路径(或最小代价)不就可以从上述这
些节点中维护的距离信息中得出吗?这是非常关键的一步,它把当前我们构建的数据结构和问题的要求
之间建立起了相当直接的联系。这说明我们目前思考的方向是有价值的而且极有可能顺着这个方向前行
,可以得出相当不错的结果。
显然,既然要求最短路径(最小代价),那么我们目前构建出的这颗Junction树(因为其上的节点在题
中的物理含义是代表Junction,那这里我们就姑且称其为Junction Tree),树上的每个节点也应当保留
在其之下的子树的最短路径(最小代价),这就相当于把每个节点都作为根节点,然后求出各条子路径
的代价,并比较得出最短路径(最小代价),以及在这条最短路径上的直接子节点;
每加入一个子节点,就要对上述已构建出的这些数据结构中的信息进行维护,以调整每个节点当前的最
短路径代价和相应这条路径上的直接子节点;当所有原“图”中的“边”信息,也就是
(fromJunction,toJuction,ductLength)所代表的(起始点,终止点,长度代价),都按照上述方案加入
Juction Tree之后,我们可以知道从最初的起始节点(也就是Junction Tree的根节点)到最终节点的(
Junction Tree上的某条路径上的叶子节点)的最短(最小代价)路径了。
对于Juction Tree这个ADT抽象数据结构的具体实现,考虑到优先队列中二叉堆的经典实现往往使用数组
,同时也为了符合TC SRM一贯的简捷明快的程序设计风格,我们这里同时使用几个数组来维护前述构建
出的数据结构。
//////////////////////////////////////////////////////////////////////////////////////////
#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日
【 某网友讨论道: 】
: RT,反射的特性发现很少用啊
恰恰相反,有些反射的特性是经常会被使用到的。
反射总体上分成两大特性,一是自省,二是发射;
自省的能力极为重要,而且几乎会天天用到,很少见到过哪个.net应用中不使用attribute的,而attribute特性就是metadata通过在自省能力支撑下实现的;当然自省不单单是attribute特性的运用,只要是在运行时动态检视程序自身的特性都要由反射的自省能力来支持,比如Visual Studio的IDE(这个集成开发环境本身就是.net应用的好案例)对于.net组件的自动探测功能;同时,自省的能力也是基于虚拟机平台的语言,比如c#和java,区别于传统语言比如c和c++的重要特性之一,这提供了程序设计开发更为便利和安全的运行时环境;相对而言,在c++(当然是native而不是managed)的环境下,除了RTTI极为单薄的运行时自省,也就是QT这个库通过meta-object system部分模拟了自省的特性;
反射的另外一个重要特性就是发射,它让“程序可以写程序”了,简要的说就是在运行时动态生成MSIL并加载运行以及持久化动态生成的MSIL的能力;由这个特性的支持,让原先一些程序设计和开发领域相对困难和繁琐的工作,比如元编程meta programming,比如动态代理dynamic proxy,比如AOP中的基础设施weaver的实现,变得可能或相对易于实现;反射的特性,也是基于虚拟机平台CLR的支持,以metadata为基础来实现的,所以这也是虚拟机平台语言的特有优势,而在传统语言平台上,这是难以实现的;比如关于meta programming,c++就是通过模板特性实现的编译期meta programming,这与虚拟机平台上实现的运行时meta programming还是有比较大的差距(比如前者如何保证生成的代码的type-safe);
以上这两个特性,自省和发射,都有个共同点,他们都是围绕着metadata机制,并在虚拟机平台运行时环境CLR支持下实现的,前者是运行时检视相关的metadata,后者是运行时动态生成相关的metadata和MSIL;从这点也就可以看出,要想深入理解这些特性,就需要研究metadata和MSIL的实现,以及虚拟机运行时环境的实现(在java平台上,就是bytecode和JVM);
所以,反射,可能是虚拟机平台所提供的相对最为强劲,最为复杂,和平台运行时本身关系最密切,也是区别于传统语言和运行时最鲜明的特性。
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的实现比较难懂
: 这并不是COM本身复杂,而是C++已经落后于时代了。所以ATL看起来才会像天书一般
虽然对于全新的工程项目,推荐通过.net实现,但是,只要你工作在Windows平台上,必然会遇到和COM相关的技术和机制,无论是大量的legacy的工程和代码,还是作为OS重要功能以及native组件的首选交互形式和接口暴露方式,比如DirectX API,比如一些WMI的API;最有趣的是,即使是.net的核心CLR本身也是一个COM组件,可以通过Host相关接口让native应用来加载,以在当前进程中启动整个CLR的虚拟执行环境或者叫托管执行环境(managed executive environment)。
把握COM有两点很关键,
1)Interface-based design,从设计和编码思路上就是要完全基于接口;
2)VirtualTable-based binary compatibility, 实现上无论何种语言或者机制,只要符合基于虚表的二进制兼容规范,就都可以实施;
COM仅仅是个规范,基于COM的具体技术非常之多,OLE,Automation,Structural storage,ActiveX...汗牛充栋,还有COM+,这个是提供企业级开发必备的一些基础功能和设施,比如,事务管理机制,对象池,安全管理,消息队列...需要指出,目前即便是.net Framework也没有实现COM+所提供这些机制,只是简单的封装了后者。
COM技术中可能有一些比较困难的地方,接口的一致性,对象的聚合和生命周期,套间,跨套间的接口访问,名字对象,等等;这些并不是COM规范人为制造的困难,而是为了设计和提供,可以跨进程和机器边界,跨异构平台(当然必须实现了COM所规定的基础服务),透明化具体对象类型及对象生命周期,便于统一部署和版本管理的组件技术,所必须付出的代价,这个代价从开发人员角度看具体表现为,概念理解的困难以及具体二进制实现的困难;
不过从另一个角度看,COM已经很容易了,
a) COM规范已把要达致这些目标的系统,所必须提供的接口和特性抽象了出来,只不过为了表达这些抽象的概念而新造的术语名词有些陌生和突兀;如果让遇到相似问题的每一个设计和开发人员都自己来做抽象,未必会生成更好的方案;
b) 为了帮助设计和开发人员,人们提供了很多的开发库,以提高COM开发的正确性和效率;最显著的就是MFC中关于COM/OLE的辅助类和函数,以及为了COM而生的ATL;从本质上看,这些类库都是把COM规范中必须实现的,Windows平台本身没有提供,具体设计和开发人员实际实施时会重复实现的,同时又非常容易出错的那部分功能,集中到了这些类库里统一实现,让具体设计和开发人员以代码重用的形式来实现COM规范;
当然人们也意识到了COM这样的一些问题,特别是具体实现时设计和开发人员必须要关注几乎所有的二进制细节,于是.net就诞生了,把这些规范的许多复杂性都封装在了虚拟机里面,把这些目标功能(跨边界、透明性等等)通过一致而又平滑的平台接口和自描述的meta data,以一种让设计和开发人员更易接受的风格开放了出来;
COM的影响是非常广大的,比如XPCOM ,Firefox上的一种插件技术标准,就是根据COM的思想和原则制定的;许多评论说,Firefox的成功是因为它插件是如此的成功,这也算是COM本身所意料不到的贡献之一。
在.net的平台上,即使是.net CLR/SSCLI的具体实现也大量运用了COM的思想和机制,可以说.net就是搭建在COM二进制组件平台之上的虚拟机托管平台。
最后,.net开始时的内部编号是COM 2.0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*) 关于“名词太多”
这是要实现可以跨进程和机器边界,跨异构平台(当然必须实现了COM所规定的基础服务),透明化具体对象类型及对象生命周期,便于统一部署和版本管理的组件技术,所必须付出的代价。
COM规范已把要达致这些目标的系统,所必须提供的接口和特性抽象了出来,只不过为了表达这些抽象的概念而新造的术语名词有些陌生和突兀;如果让遇到相似问题的每一个设计和开发人员都自己来做抽象,未必会生成更好的方案;
举个例子,apartment,套间,就是为了抽象传统OS中进程和线程的实现而新造的术语名词和概念;任何人要抽象这样的一些概念,不新造术语,是非常困难的,对比.net,后者用了CLR虚拟机来封装了大多数的实现细节,并用让人更容易接受的风格来开放接口,可事实上仍然新造了一些名词和概念,如类似范畴的AppDomain;
*) 关于“基于ATL的实现比较难懂”
ATL主要使用了template技术,COM接口智能指针,用静态转换来模拟动态绑定,等等,实际并不是很复杂,只能算c++实现机制的中等难度,主要涉及Modern C++ design一书中一些相关设计理念的运用。对比Boost中某些库的实现,ATL很人道了。
*) 关于“这并不是COM本身复杂,而是C++已经落后于时代了”
首先COM的规范的确是复杂的,为啥?第一点已经说了,就是为了要抽象出跨边界和对象透明的组件技术;.net表象上看比较“简单容易”,风格亲近设计和开发人员,实际上复杂事务和实现细节都被划分到CLR那个层面上去实现了;去看一下CLR的开源实现SSCLI,你会发现,整个虚拟机平台的实现,大量运用了COM的思想和机制,就是一个巨型系统平台级的COM server;
其次,COM规范本身是独立于实现语言的,只要构建出的组件符合规范制定的二进制兼容,系统就可以运作,这和C++是否落后时代没有关系。如果开发人员认为,.net才够先进,也完全可以用.net中的托管语言,如C#来实现COM组件;
最后,每种语言都有其适用的范围,现在可以这么说“如果有一个全新的项目需求,要达致跨边界和对象透明组件,并且没有太过严苛的性能需求,那么.net平台及其上的托管语言来实现,比用C++及相关辅助类库来以COM组件形式来实现,要更合适,也更快速便捷和节省预算。”但是,在这个判断上我们加了很多严格的约束,一旦需求变更,特别是项目的非功能性需求,要求高性能运算或者更顺畅的与legacy的native系统相互,那么“使用native语言来实现性能关键以及legacy交互功能,通过COM封装,再由COMInterop交.net托管应用调用”可能是更现实的方案。C++是一门活的语言,不断发展的语言,即使在最新的托管时代里,C#成为标准主流,但C++/CLI仍然是托管语言里功能最完整的语言。
2010年12月13日
金山系列软件中的一部分代码open source了,自然地引起网络上以及IT业界的一片热评。其中关于已经开源的这部分外围代码的代码质量的问题,更是热中之热;以下是关于这个问题个人的一些思考:
从本质上说这里面就是个trade off,也就是平衡和取舍的问题。产品项目的预算投入,进度压力,各方面人员的协调,风格和习惯的统一,等等。
许多优秀开源项目,比如Boost,其中很多作者本身都是学者兼开发或者是带有研究性质的开发人员,在高校、非盈利组织或者商业企业的非直接盈利项目的资金支持下,在很少进度压力和商业压力的情况下,精雕细琢,多次迭代后,构建出的精品代码。用这样的标准来要求所有的软件产品,特别是商业产品(当然除去少数关系重大和长远的基础核心部分外)的构建,是不科学的,也是不合算的,因为及时占领市场和足够的盈利,以及获得用户的赞许才是商业软件最重要的目标。
回头来看金山目前开源的这些产品,比如这里讨论的金山卫士,其从推出就是免费的,是为了市场上的先期推出的同类工具软件及时比拼占领些许相关市场份额,其并不是金山的基础和核心产品;从这些先天的条件看,这个产品的商业投入不会很大同时又有快速推出的要求,能有目前这样的产品质量,是合理的,从企业角度和用户角度看也都是可以接受的。
说到这里,就感觉有必要涉及一下“重构”,这个现在大家都很重视同时也经常谈及的话题。为何大家都很重视?而且常常谈及?这其中当然有软件构建本身的特点,比如对需求理解的不断深入和调整、设计的不断改善和演进、代码风格的统一以及细节的完善等等;但是,有个大家在潜意识里都感觉到,平时却很少谈及的缘由--进度和成本,因为有了这些压力,产品的第一版往往不是很完美的,然后如果还做后续版本的话,那么就要引入重构;因为有了这些压力,在经过多年之后,如果这个产品还存在的话,那么就要进行大规模的重构。简单的说,重构之所以重要,不仅仅是软件构建本身特点所引发,也是商业压力之下的构建过程的有效应对之道。
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.
关于程序设计语言本身的设计有许多有趣的话题,比如,为何C++中的类成员函数没有采用类似Java中的“全虚”设计?
1) 从语言本身设计上看,
效率定然是c++当初设计时考虑的重点之一,举个例子,为了节省不必要的VTable开销,ATL用template技术静态转换来模拟动态绑定以支持COM特性的实现;和C的兼容,就VTable角度看,问题不大,因为后者可以用函数指针数组来模拟;
2) 再从大多数应用中常见的类继承体系上看,
除了整个继承体系所统一开放出来的接口集(也就是由虚函数所组成),在继承体系的每个层面另外会有大量的其他辅助成员函数(其数量通常比虚函数多的多),这些成员函数完全没必要设计成虚函数;
3) 从其他语言看,
即使较新的虚拟机语言C#(Java算是较老的虚拟机语言),反而定义了比C++更为严格更为显式的成员方法实现或覆盖或重载或新建的规则;这是非常重要的对C++以及Java设计思想的反思。
4) 从语言的适用场合看,
我们现在的讨论,绝大多数情况下带有一个非常重要的默认前提,那就是在用户态模式下使用C++,如果放宽这个约束,在内核模式下使用C++,那情况又完全不同了。
引用下面这个文档的观点,http://www.microsoft.com/china/whdc/driver/kernel/KMcode.mspx
首先,用户态下非常廉价几乎不用考虑的资源,在内核中是非常昂贵的,比如内核堆栈一般就3个page;
在内核不能分页(paging)时必须保证将被执行的所有代码和数据必须有效的驻留在物理内存中,如果这时需要多驻留几张虚表以及虚表指针那还是显得非常昂贵的,同时编译器为虚函数,模板等生成代码的方式,让开发人员很难确定要执行一个函数所需要的所有代码的所在位置,因此也无法直接控制用于安置这些代码的节(个人认为可能通过progma segment/datasegment/codesegment对于代码和数据进行集中控制),因此在需要这些代码时,可能已经被page out了;
所有涉及类层次结构,模板,异常等等这样的一些语言结构在内核态中都可能是不安全的,最好是把类的使用限定为POD类,回到我们的主题虚函数,也就是说内核态下类设计中没有虚函数。