??xml version="1.0" encoding="utf-8" standalone="yes"?>
]]>
: 比如我有一个CMyButton的类Q我现在有他的一个handle
: ~译器怎么Ҏ(gu)q个句柄扑ֈCMyButton的代码的Q?/em>
??某某 的大作中提到: ?br>: q个和OS/Compiler没关p,是库L(fng)作用
: 以从某个文章里看的,说MFC用了(jin)一个大mapQ没验证q?br>: 有本讲GDI的书里,用了(jin)WNDCLASS里的extra bytes来实现的q个映射
MFC的应用里Q每个MFCU程Q必要使用MFC方式启动的线E)(j)都维护有一个MFC object和HWND之间?/p>
mappingQ整个MFC框架是使用q个机制来实现应用C++对象和系l原生H口内核对象之间的关联;
因ؓ(f)q个mapping是以U程为单位来l护的,每个U程间互不关联,所以,一个应用里对于涉及(qing)UIH口?/p>
d最好是都放在同一个线E里面,一般就是当前进E的ȝE,否则可能出现MFC object和HWND之间
兌不上的问题,而且q样的问题还很隐蔽?br>
至于WNDCLASSl构自带的extra bytes域,是以前缺乏应用框架的时代Q用Win32 API直接开发时Q让每个
H口c(q里的类Q不是C++ class的概念,而是WindowspȝH口定义时的一U数据结构)(j)都能有个?/p>
带一些额外的自定义数据的I间Q这个空间往往被用来存放与当前H口cȝ关的用户数据Q通常是指?/p>
某个内存区域的指针,当程序操作这个属于这个窗口类的窗口时可以根据这个附带的自定义数据(?/p>
者指针)(j)来操作对应的兌自定义数据;很多后来出现的框Ӟ也都使用?jin)这个extra bytes域,来存?/p>
框架本n的一些和H口cȝ兌的数据结构。从目前势看,直接使用WNDCLASS以及(qing)extra bytes的可?/p>
性是微乎其微?jin),但是如果要做好原生应用的开发,很多底层的实现细节最要还是要知道一下,以便?/p>
优化l构和性能Q以?qing)出错时的调试处理;因?f)无论是Winform/WPFQ还是跨q_的WTL/QT/WxWindows{?/p>
{新型的机制或者框架、类库,只要是在Windowsq_上搭建的Q那都是Z前面说过的这套最基本也是
最核心(j)的Win32 API基础之上?/p>
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.
2) 再从大多数应用中常见的类l承体系上看Q?br>除了(jin)整个l承体系所l一开攑և来的接口集(也就是由虚函数所l成Q,在承体pȝ每个层面另外?x)有大量的其他辅助成员函敎ͼ其数量通常比虚函数多的多)(j)Q这些成员函数完全没必要设计成虚函数Q?/p>
3) 从其他语a看,
即较新的虚拟机语言C#(Java是较老的虚拟a),反而定义了(jin)比C++更ؓ(f)严格更ؓ(f)昑ּ的成员方法实现或覆盖或重载或新徏的规则;q是非常重要的对C++以及(qing)Java设计思想的反思?/p>
4) 从语a的适用场合看,
我们现在的讨论,l大多数情况下带有一个非帔R要的默认前提Q那是在用h模式下使用C++Q如果放宽这个约束,在内核模式下使用C++Q那情况又完全不同了(jin)?br>引用下面q个文档的观点,http://www.microsoft.com/china/whdc/driver/kernel/KMcode.mspx
首先Q用h下非常廉h(hun)几乎不用考虑的资源,在内怸是非常昂늚Q比如内核堆栈一般就3个pageQ?/p>
在内怸能分?paging)时必M证将被执行的所有代码和数据必须有效的驻留在物理内存中,如果q时需要多ȝ几张虚表以及(qing)虚表指针那还是显得非常昂늚Q同时编译器函数Q模板等生成代码的方式,让开发h员很隄定要执行一个函数所需要的所有代码的所在位|,因此也无法直接控制用于安|这些代码的节(个h认ؓ(f)可能通过progma segment/datasegment/codesegment对于代码和数据进行集中控Ӟ(j)Q因此在需要这些代码时Q可能已l被page out?jin)?/p>
所有涉?qing)类层次l构Q模板,异常{等q样的一些语al构在内核态中都可能是不安全的Q最好是把类的用限定ؓ(f)PODc,回到我们的主题虚函数Q也是说内核态下c设计中没有虚函数?/p>
q个既不是应用本w的bugQ也不是pȝ的memory leak?/p>
当前资源监视器中关于pȝ物理内存Q有q么几个l计,可用、缓存、L、已安装Q其?~存"q项Q代表着已用于文件系l、网l等{子pȝ的数据缓冲存储的内存定wQ其中包含数量巨大的ȝ在物理内存中的数据页面。而这L(fng)物理内存消耗ƈ没有归入M一个进E列表显C的q程所占用的物理内存。这是Z么下面公式,
q程列表昄的所有进E所占用的物理内存之?+ 可用物理内存 < 物理内存L
Q成立的原因所在?/p>
Dq一现象的原因,从这个大规模计算E序的行为描q看Q基本可以断定是׃以下两点Q?br>1Q应用本w的大规模数据驻留物理内存,Dparser.exeq程庞大的working setQ?br>2Q大量频J的IO操作Q引起大量的物理内存为系l缓存所占用Q?/p>
对于1),必须注意QGC.Collect()只是讄使能垃圾攉的标志位Qƈ没有立即启动垃圾攉q程Q这个过E的实际启动时刻由CLR来动态决议;
所以如果要获得x的托内存的释放Qƈq一步释攄理内存以减小当前q程的working setQ可以用AppDomainq个.net下可以用来资源划分、获取和释放的,在概念上q似于轻量q程的编E语义;在AppDomain中获取的各种资源Q包括托内存、加载其中的各个assembly以及(qing)CCW{,在此AppDomain被释放时都被相应的及(qing)旉放(或者引用计数递减Q?/p>
对于2Q,重新观察先前的设计实现和模型Q考虑是否能把一些分散的IO操作合ƈhq行Q比?
for(long i=0; i < Count; ++i)
{
...
objIO.Operation(Data[i], 1);
...
}
修改?br>for(long i=0; i < Count; ++i)
{
...
...
}
objIO.Operation(Data, Count);
q样对于提高应用的IO效率以及(qing)提升pȝ~存利用率应当会(x)有帮助?/p>
对于2Q,pȝ~存随着q个大规模计应用的q行而逐步增大Qƈ最后导致整个系l无法获取的物理内存而无法l运行的现象Q估计即佉K用了(jin)在上文提出的Q在应用E序代码中尽可能合ƈIO操作Q减IOơ数的方法,也不?x)改善系l缓存占用物理内存数量过大的问题。这个问题本质上是Windows操作pȝ本n从NT时代到现在,一直存在的问题Q主要是围绕着Windows kernel中的Cache mananger以及(qing)memory manager核心(j)态组件的实现机制而生的?/p>
Ҏ(gu)目前的Cc(对Cache manager的简Uͼ在WindowsResourceKernel开源项目中QCache manager相关模块的函数都以Cc作ؓ(f)前缀Q比如CcCopyReadQCcFlushCache{,Memory manager也同L(fng)UMm)的实现机Ӟ所有对文gpȝ的访问,包括本地和网l,都会(x)首先由Cc对相关页面作~存映射Q随着频繁的IO的操作,被Cc~存的页面也q速递增Q而被~存面占用多少物理内存Q这是由Windows kernel中的Memory manager军_。目前在64位^CQ系l缓存最高可?TBQ所以这个应用进E的q行中出现分?G的缓存是完全可能的,但同旉题也随之而来Q那是pȝ~存占用?jin)过多的物理内存Q导致其他进E以?qing)内核本w无法申误够的物理内存Q最后致使系l?#8220;僉|”Q?/p>
对于q个问题Q微软提供了(jin)“Microsoft Windows Dynamic Cache Service”工具来提供对pȝ~存的工作集working set定wQ也是ȝ物理内存的大)(j)的控Ӟq个工具主要是对SetSystemFileCacheSize的封装,可以讄pȝ~存定w的上下限?/p>
但这只是一U(f)时的解决Ҏ(gu)Q因为应用虽然可以通过上面q个Dynamic Cache Service来设|和限制pȝ~存定w的大,但是如何定~存定w大小的非常困难,如果q小Q所有IO性能大受影响Q整个Cc如同虚设Q如果过?{h(hun)于不受限)Q那么系l缓存占用过多物理内存导致系l僵ȝ现象׃(x)重现?/p>
所以从Ҏ(gu)上看Q这个问题应由包括Cc和Mm在内的整个Windows kernel作出完整一致的调整Q但从目前的实现看要完成整个Ҏ(gu)改动很大Q据U这个改q可能会(x)考虑包含在Win7中发布?/p>
Microsoft Windows Dynamic Cache Service下蝲,
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=e24ade0a-5efe-43c8-b9c3-5d0ecb2f39af&displaylang=en
Microsoft Windows Dynamic Cache Service相关的介l,
http://blogs.msdn.com/b/ntdebugging/archive/2009/02/06/microsoft-windows-dynamic-cache-service.aspx
首先Q内核对象只能由在内核态下的例E才能直接访问,在我们日常的代码中,所调用的Windows APIQ比如CreateFile, Q注意调用刚开始时是处于用h下的)(j)Q一般都?x)在ntdll.dll中找到对应的内核函数或例E,接着pȝ切换到内核态,开始调用实际对应的内核函数(KiCreateFile)Q这个时候才?x)去讉K内核对象的实际地址Q然后徏立一个该内核对象对应当前q程的HandleQƈ把它q回lcallerQ同时切换回用户态;因此Q对于用h程序来_(d)只要且只能知道该内核对象在当前进E中的对应的Handle可以对其进行操作了(jin)Q?/p>
其次Q这L(fng)设计是出于对OS核心(j)数据l构Q当然包括我们正在讨论的内核对象Q的保护Q如果用h程序可以轻易的获取内核数据l构的实际地址Q那么对于整个OS的安全和E_昄构成很大的问题;一个用h的误操作可以轻易的引v整个OS的崩溃,而有?jin)这一层的保护Q崩溃的只是当前q程而不是整个系l;
接着上面q点Q也可以看出Q内核对象的如此设计辑ֈ?jin)接UOS本n的^滑演q的目的。从Windows 3.0?5/98Q从NT到Win2k/XPQ再到眼下的Vista/Win7QW(xu)indows操作pȝ本n发生?jin)巨大的变化和进步,采纳了(jin)无数的新技术新Ҏ(gu)Q但是它基本的系l应用编E接口,也就是我们所熟知的windows APIQ却q没有发生太大的改变Q很多Win 3.0 q个16位OS时代的程序代码只要当初设计规范编码规范,E许修改可以在最新版的OS上运行如飞;是什么做C(jin)q些Q也是所谓的极ؓ(f)重要的向后兼Ҏ(gu),我个为,把操作系l的重要/主要功能抽象成内核对象,q过一套极为solid的API暴露出来Q达成了(jin)q个目标?/p>
q是一U更高层ơ上的面向对象,把实现的l节Q把pȝ的复杂,单而优雅的装?jin)v来。你只要调用CreateFiled个文件或道或邮槽,不用担心(j)当前OS是Windows 3.0q是Win7Q获得的HandleQ你也不用去兛_(j)它以?qing)它所指向的内核对象是Windows 3.0的实现还是Win7的实现?/p>
Windows上所有的_ֽ几乎都是Zq套通过内核对象概念抽象q暴露的API基础之上QCOM/OLEQ这个二十年前震撼性的ABI和IPC范畴的技术规范,其中很多的设计思\也是植根于内核对象的设计理念Q如COM对象的引用计数和内核对象引用计数QIUnknown和W(xu)indows Handle(前者是指向某个二进制兼容的lg对象Q后者引用或间接指向某个内核对象Q都是对于某个复杂概늚一致性抽象表q?Q等{;
十年前的.netQ本来是作ؓ(f)COM的升U版本推出,把COM/OLE的实现复杂性封装在?jin)虚拟机q_CLR里面Q而从q个虚拟机的开源实现SSCLIQ我们可以看到大量的COM机制?net的具体实现里面v?jin)D重的作用。在q些VM中大量symbol有着COR的前~或者后~QCOR指代什么?Common Object Runtime, 原来CLR/SSCLI的设计思\也是把OS通过虚拟机VM的Ş式,q过common object向应用程序暴露功能?/p>
结一下,
OS内核对象APIQ三十年前系l别的对象抽象Q?br>COM/OLEQ二十年前二q制lgU别的对象抽象;
.net/CLR, 十年前虚拟机q_U别的对象抽象;
写到q里倒是引v?jin)我其他的一些思考,软g工业界一直以来对面向对象OO是热火朝天,特别是语a层面Q从C++/Java/C#到Python/JScriptQ不一而Q?/p>
但是我们有没有从Ҏ(gu)性的设计理念上对面向对象Q察U雅a?jin)呢Q?/p>
如果现在设计Windowsq套API的Q务放在大安前,?x)采用内核对?HandleҎ(gu)q是直接指向OS内部数据l构的方式来暴露功能Q?/p>
从三十年前的q套API的设计中Q我们真的可以学到很多?/p>