查看文章 |
《Windows用户态程序高效排错》1
2009-03-10 09:03
作者BLOG http://eparg.spaces.msn.com/blog C# struct实例 在stack 还是 heap上? --!address 《Windows用户态程序高效排错》 中涉及到的链接 "重现" Time Travel Debugging 代号iDNA http://blogs.msdn.com/cse/attachment/1077668.ashx reflector and profiler CLR Profiler for the .NET Framework 2.0 http://www.microsoft.com/downloads/details.aspx?FamilyID=a362781c-3870-43be-8926-862b40aa0cd0&DisplayLang=en http://msdn.microsoft.com/msdnmag/issues/05/01/CLRProfiler/default.aspx http://blogs.msdn.com/jmstall/archive/2004/10/22/246151.aspx 关于:平衡、取舍、双赢 RFC 1925 (RFC1925) http://www.faqs.org/rfcs/rfc1925.html http://blog.csdn.net/eparg/archive/2007/09/19/1791954.aspx ShellExecute 内部调用栈 ,需要维护自已的消息循环,通过DDE打开目标文件,其内部是依靠windowsMessage来完成进程间通信。
1.2.5结论 1。线索 2。对比 DWORD 文件长度 4G 0Xcdcdcdcd访问未初始化的内存。1.3.3 JavaScript维护事务逻辑 ,WEB开发大忌, 重定向最好用HTTP 302(如Response.redirect)。 第二章 汇编、异常、内存、同步和调试器--重要知识点和神兵利器 exception、内存布局、heap stack CRT handle Criticalsection/thread context/windbg/dump/ live debug/Dr Watson 两本书: Programming Application for Microsoft Windows ,Jeffrey Richter ,MS press 1999 Debugging Applications for Windows ,John Robbins,MS Press,2003 windbg: DebugInfo: http://www.debuginfo.com/ Windows Debuggers: http://www.codeproject.com/debug/windbg_part1.asp PDB包含类型及符号所处二进制地址。 !address检查对应内存页的属性,由操作系统维护,需要加载系统模块PDB。 x 显示符号的二进制地址(如函数),显示局部变量。 vertarget显示当前进程的大致信息。 !peb显示 process Environment Block lmvm 检查模块加载信息 .reload /!sym加载符号文件。 lmf 列出当前进程中加载的所有模块。 r显示和修改寄存器上的值 d 显示内存地址 e 修改内存地址上的值。 dd (DWORD),db(byte),du(Unicode),dc(char) s 搜索内存 !runaway 检查线程的CPU消耗 ~切换线程 ~0s ~*k显示所有线程的call stack u 反汇编 uf 直接反汇编整个函数 x查找符号的二进制地址 x ntdll!* dds 对应二进制地址 的符号 找到虚函数表中的具体的函数地址。 先x XXX!XXX::'vftable' 再dds结果地址。 .frame栈中切换以便检查局部变量 dt 格式化显示资料 -b -i 显示内部类 和数组 dt 0x00XXXX MyCls,将任意地址按指定类型来格式化显示。 2.1.6 live debug wt : watch and trace data ,可以跟踪一个函数的所有执行过程,并给出统计信息。 观察函数执行过程和分支。或者评估性能。 断点 和条件断点 bp +地址和函数名 ba 设定访问断点,在某地址被读写时断。 Exception断点,sx小结 sxe av Access Violation 停 sxn ld DLL LOAD仅输出 sxd eh C++ Exception 不处理 poi(取地址上的值)类C中&. 0n十进制前缀 ba w4 excep!i " j (true?false ) '.echo skip; g ' ;' .echo stop!' " Step out 的实现 定义是:target executes until the current function is complete." 可以简单地在当有函数的返回地址上设定断点。返回地址保存在函数入口时EBP+4上。如直接设, 1。无法区分递归,及其他线程对该地址的调用。 2。第一次触发后不会自动清除端点,可能会多次触发。 bp /1 /c @$csp @$ra;g /1使断点在触发后自动消除;避免问题2 /c @$csp 参数通过指定 callstack 最小深度避免问题1 @$ra 直接表示当前函数的返回地址。 远程调试 .server 本地创建TCP端口或通过named pipe,双方均可输入命令。执行结果都显示。只是简单的通过重定向方便远程检查,而实际调试工作都发生在目标机器上。 另一种更强大方法,是使用DbgSrv,是一个调试服务器,让必要的调试动作发生在目标机器上,而调试功能,如加载PDB符号,发生在调试员机器上。在其出现前,调试系统核心服务如,lsass.exe,要同时结合用户态和内核态调试。过程大为简化。 Debugging LSASS ... oh what fun, it is to ride.. http://blogs.msdn.com/spatdsg/archive/2005/12/27/507265.aspx 2.1.9 Dump文件 进程的内存镜像,保存程序的执行状态。 .dump /ma c:\a.dmp 2.1.10 CDB\ NTSD NTSD位于 system32目录下,不需要特别安装。 三者命令完全一样。 都使用同样的调试引擎dbgeng.dll Symbols and Crash Dumps http://msdn.microsoft.com/msdnmag/issues/02/06/Bugslayer/ 由于CDB和NTSD 采用命令行标准输入输出,通过重定向控制这两个工具。 典型用例,可以把用户态的调试重定向到kernel Debugger.仅需要一个Debugger Session就可同时控制核心态和用户态的调试例程。见帮助中CDB 小结。 2.1.11 Debug Tutorial Part 4: Writing WINDBG Extensions http://www.codeproject.com/debug/cdbntsd4.asp 2.3异常 和通知 异常的类型是通过异常代码标识。 弄清异常发生的时间、地点、导致异常的指令和异常结果,对排错至关重要。 1st chance ,2nd chance 是针对调试器而言的。 异常发生后,操作系统在调用用户态异常处理函数前,会检查是否有调试器加载,首先把异常信息发给,使其有第一次观察机会 。调试器处理完后,才让用户态程序处理。 如果用户态程序处理此异常,无关调试器,否则在unhandled excption崩溃前,操作系统给调试器第二次观察异常的机会。 操作系统提供的异常处理功能叫 Structrued Exception Handle (SEH),C++和其他高级语言的异常处理机制都是建立在SEH上的。如果要直接使用SEH,可在C/C++中使用__try __except关键字。 A Crash Course on the Depths of Win32™ Structured Exception Handling http://www.microsoft.com/msj/0197/Exception/Exception.aspx RaiseException http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/raiseexception.asp C++像C#一样打印函数调用栈 SEH,DEP, Compiler,FS:[0], LOAD_CONFIG and PE format http://eparg.spaces.msn.com/blog/cns!59BFC22C0E7E1A76!712.entryhttp://blog.csdn.net/eparg/archive/2007/09/19/1791966.aspx 2.3.2 Adplus ,抓取dump的方便工具 dump记录进程某一时间的具体信息,保存时机非常重要。 程序崩溃,应该选在引发崩溃的指令执行时,也就是1st chance,时获取,能够看到问题的直接原因。 VBS脚本 windows api MiniDumpWriteDump API ,可供生成MINI DUMP。 Description of the Dr. Watson for Windows (Drwtsn32.exe) Tool http://support.microsoft.com/?id=308538 Specifying the Debugger for Unhandled User Mode Exceptions http://support.microsoft.com/?id=121434 INFO: Choosing the Debugger That the System Will Spawn http://support.microsoft.com/?id=103861 IFEO劫持(Image File Execution Option Hacking) WINDOWS提供让指定进程随调试器启动的选项 1。IFEO下创建程序同名键 2。此键下创建Debugger字符串类型子键 3。设定Debugger=autodump.bat 4bat 内容: cscript.exe adplus.vbs -crash -o *.dmp -quiet -sc %1 Dr. Watson 1。运行drwtsn32.exe -i 注册 。 2。打开AeDebug 注册表,找到Debugger 项,值应为: drwtsn32 -p %ld -e %ld -g. 3修改为: windbg.exe -p %ld -e %ld -c ".dump /mfh *.dmp;q" 2.3.3 Debug Event 操作系统跟调试器交流的方法。 Controlling Exceptions and Events主题,有所有代号。 DLL 加卸载,线程创建和退出。 sxe ld:*.dll截获Moudle Load通知。 2.3.4 崩溃(二次机会时) 手动恢复exception context. 异常对话框是通过UnhandleExceptionFilter函数显示。其参数包含异常信息。此时,通过.exr .cxr可恢复。 kb UnhandleExceptionFilter首参数保存异常信息和异常上下文的地址。 dd 0x首参数 异常信息 异常上下文的地址 .exr 异常信息 地址 .cxr 异常上下文的地址, 切换上下文后, kb 看到异常发生时候的状态。 未处理异常发生后主动退出 使用此项技术的有COM+ ASP.NET 旺旺客户端 好处: 1。自定义接口。 2。异常详细信息保存以便分析。 3。挽救工作,重启。 实现: 1。使用SEH __try __except 2。使用UnhandleExceptionFilter,崩溃后发送异常。 缺点: 调试器无法接收2cn chance exception,如com+ crash。 避免调试器追踪 把代码放至SetUnhandleExceptionFilter设定的函数里。人为触发 Unhandle Exception。因为UnhandleExceptionFilter仅在调试器不加载时才会被调用。 2.4 Heap and Stack WIN API中有两类: 内存分配函数 VirtualAlloc和HeapAlloc。前一种向操作系统申请4K为边界的整块内存.后者是分配任意大小的内存块。区别在于后者的实现依赖于前者。 操作系统管理内存的最小单位是4K。用户态需要在此基础上,提供以字节为粒度的内存分配,释放功能,并平衡好时间利用率和空间利用率。 WINDOWS提供Heap Manager完成上述功能,其工作方式:首先分配足够大的4KB倍数的连续内存空间;在这块内存上开辟一小块区域来做薄计;将其分割成尺寸不等的小块,同时小块信息记表到薄计里,记录每一小块的起始地址和长度,以及是否已分配。 内存请求发生时,根据请求长度,在薄记里找大小最合适的空间,将其标为已分配后,返回其地址,完成一次内存分配。如找不到足够大的空闲小块,以4KB为粒度向系统申请更多内存。 1。分配的内存最好是在4字节边界上,提高内存访问效率。 2。做好线程同步,保证多线程同时分配内存时不出错。 3。灵活合并拆分块。 HeapFree释放的空间,继续操作不会访问违例,除非那块地址VirtualFree返还给操作系统了。 2。越界的写操作,可能破坏薄计。导致错。 3。同一地址多HeapFree。致错。 错误之所以没有在第一时间暴露: 1。Heap每块内存的界限是由Heap Manager定义的。而内存访问无效的界限,是操作系统定义的,哪怕访问越界,如果越界的地方已有映像上来的4KB内存页,程序不会崩溃。 2。为效率,Heap Manager不会主动检查自身数据结构是否破坏。 为方便检查,让现象尽早表现,Heap Manager应该这样管理内存: 1。把所有的Heap内存都分配到4KB页结尾。并把下一个4KB页面标记为不可访问。当越界时,访问无效,立崩溃。 2。主动检查数据结构是否被破坏。 使用 Pageheap。 Debug Tutorial Part 3: The Heap http://www.codeproject.com/debug/cdbntsd3.asp 2.4.2 Pageheap Heap Manager的确提供主动检查错误的功能。只需在注册表里修改,操作系统会根据设置改变其行为。 Pageheap是用来配置该注册表的工具。 How to use Pageheap.exe in Windows XP and Windows 2000 http://support.microsoft.com/kb/286470/en-us Pageheap Gflag Application Verifier都是修改注册表的工具。 内存碎片Fragmentation 内存被分割成很多小块,难找到连续的内存满足大块的内存申请。导致原因:加载过多的DLL,小块HEAP的频繁使用。 DLL分割常见情况:ASP。NET中的batch compilation没有打开,导致每个页面都会被编译成一个单独的DLL文件。 Stack overrun/corruption Stack overrun,一般是递归函数缺少结束条件导致,函数调用过深从而把stack地址用光。 Stack corruption 往往是stack buffer overflow导致,此BUG不单程序崩溃,还会严重威胁系统安全性。 当前计算机架构上,stack是保存运行信息的地方,损坏后,所有信息丢失,调试器无用武之地。 windbg 中EIP EBP指向非法地址,说明callstack信息已被冲毁。找不到任何线索调试。有效方法 ,可疑函数中写LOG。 Pageheap 别外一功能trace,作用记录Heap历史操作。激活此功能后,Heap Manager会的内存中开辟一块专门的空间记录每次Heap的操作。 激活Pageheap 后,Heap Manager检测到错误,会激发一个break point exception,debugger停下来。 !heap -p -a free函数参数 打印出Heap Manager保存的HEAP地址的历史操作, “dcda"-- heap内存标志位 利用这个特点来解决memory leak和fragmentation。 s -w 0 L?60030000 0xdcba dds poi( 上面结果地址 - 6) 找到指针分别对应的callstack. 如果是固定的callstack分配,很有可能是泄漏根源。 2.5排查问题的对应层次 几千行C程序少量内存泄漏。 简单高效的办法采用CRT 的debug heap。 Heap API本身没有提供检查内存泄露的功能。 CRT的实现中,包装Heap API完成这样的功能。 恰当的层次设计,能给排错带来灵活性。 2.5.3 BSTR Cache ,建立在Heap之上的COM字符串内存管理。 OLE的内存管理跟操作系统的HEAP是在两个层次上的。 pageheap保证操作系统上的heap问题及时暴露,但ole内存管理器包装了一次,OLE缓存了操作系统分配出来的heap,进行第二次分配和管理。 如果错误的第一现场是OLE内存管理,后导HEAP,就无法用pageheap。 在OLE层禁止CACHE,直接对应HEAP分配。 CRT也是二次包装。 2.6多线程对资源的竞争:同步和锁 2.6.1句柄泄露、死锁、线程争用。 同步是为了多线程下避免资源竞争。提高系统的整体性能和可伸缩性。 通过Kernel Object、Critical Section和windows Message来实现。 句柄泄露 Handle leak handle数量越来越多,没及时调用CloseHandle API, 如CreatThread后,未调用CloseHandle 。 解决思路:观察handle增长。找到增长的handle是何类型,如何创建出来的。 死锁(Deadlock) 在WaitForSingleObject或者是EnterCriticalSection上不返回。用户态多个线程互相依赖,互相等待。 hang归为死锁. 案例: MFC异步网络操作,使用CAsyncSocket类。对特定请求无返回,UI僵死。 CAsyncSocket的实现是依靠windows Message派发消息、调用客户自定义的网络处理函数。 在处理请求时,客户主线程会等级一个EVNET,这个EVENT会在客户的网络处理函数中激活。 问题在于客户调用WaitForSingleObject在主线程等待时,主线程的Windows Message队列就停止派发了,这样 CAsyncSocket接受到网络包但没办法通过Windows Message通知自定义处理函数执行。自定义处理函数不执行,Event就不触发。主线程就不会结束等待。 解决方法:等待Event时,用MsgWaitForSingleObject,保持消息队列。 死锁的共同点就是应该运行的线程,在等待某种资源,这个资源可能跟 CriticalSection相关,也可能跟Windows Message相关,可能跟网络等相关。CUP=0% 对死锁排错, 1。在等什么? 2。等待的时机是否恰当? 3。等待的东西什么时候释放? 线程争用 现象 1。启用太多工作线程 2。等待同一对象。 后果性能下降。CPU,切换线程上下文开销大, 线程争用常是设计上的缺陷导致。 System\context Switches/sec 2.6.2 windbg对应排错 !handle可获取整个进程或某个。 !htrace 中获一个handle创建时的call stack,功能类似pageheap,需修改注册表来激活。仅能用于live debug,dump不存。 !cs 获取一个 CriticalSection。 Application Veifier让操作系统主动提供关于handle,CriticalSection,heap等系统层面的调试信息。 ~~[]可把ID转换成线程号。 能够让WINDBG LIVE DEBUG时,通过break point机制找到误用的地方。尽早暴露问题。 !sos.savemodule保存模块。 2.7调试和设计 排错其实是抓取信息,分析信息的过程。入手的关键在于抓取恰当的信息。利用系统提供的功能来获取恰当的信息。 留下排错的方式,LOG或DEBUG版本。 为方便调试,开发应: 1。多使用assert,trace; 2。合理添加LOG; 3。每次编译后,把PDB分不同的版本,保存在安全的地方。 设计多线程环境下的安全而高性能的LOG操作不太现实。既要安全,又要高效,还要支持多线程,直接使用数据库来记录LOG。 第3章 .NET Framework的原理和SOS调试 CLR运行的关键,调试技巧。 3.1MetaData JIT GC Exception 最好的参考书:Essential.NET Volume 1 - The Common Language Runtime. 托管Assembly本身仅包含CLR可识别的元资料,不包含机器指令。 使用depends工具可观察到所有托管Assembly都跟随mscoree.dll绑定,功能是选择合适的CLR Excution Engine加载。还负责判断应该使用何种GC Flavor. 多版本CLR可共存。CLR的目录在C:\WINDOWS\Microsoft.NET\Framework. 当前系统中最新版本的CLR对应的mscoree.dll文件被拷贝到SYSTEM32目录下。 引擎加载后,首先初始化引擎需要的各种功能,要必要的全局变量,引擎需要的模块(ClassLoader\Assembly Loader\JitEngine\Context等)启动 Finalizer 和GC线程。创建System AppDomain &Shared AppDomain,创建RCDebuger,线程,加载CLR基础类(mscorlib.dll&system.dll)。 当CLR引擎初始化后,CLR会找当前EXE的元数据,找MIAN函数,编译MIAN 函数,执行。 JIT动态编译 无论函数是否已经编译成机器代码,call指令都是把执行指向跟函数相关的一个内存地址(stub),如果未被编译,stub 中的代码执行定向到CLR JitEngine,编译。 每种方法,只需要编译一次,编译完成后,CLR把编译好的机器代码拷贝到进程中的由CLR管理的某一块内存,然后,JitEngine把编译好的函数入口地址回填到stub中。第二次调存时,已指向已编译好地址。CLR支持NGen,库函数默认已NGen。 调用非托管代码两种情况, 一。系统API和DllImport,COM interop。 二。调存CLR 的功能,比如内存分配、异常派发。 也都使存stub技术。 第一种,不管是PInvoke还是COM Interop发生时,托管代码调用的都是CLR创建的stub。stub把控制权交给对应的非托管代码。把必要的函数参数拷贝到非托管的内存上,marshal必要的类型,锁住交互的托管内存区域,防止GC移动。如是COM,还要QueryInterface。当非托管调用完成后,执行权返回到stub,再回托管。 第二种情况,对CLR功能的调用往往是隐式发生的。 一类是编译器直接生成CLR stub的调用。比如 new /throw关键词。动态编译引擎对这些关键词的处理是生成函数调用到特殊的stub,它再把执行定位到CLR引擎中的关键函数。 另一类是通过把托管代码标示为internal call来编译。表示认托管函数其实是某unmanaged函数的映像,编译引擎在编译时,会直接跟unmanaged函数实现对应起来。该对应关系是在CLR的实现中通过C++的一张静态表定义的。 3.1.3 GC内存管理 CLR引擎初始化的时候会操作系统申请连续内存作为managed heap. 1.大多数情况下比非托管代码内存分配速度快。 2。GC发生时CLR可以对托管OBJECT进行随意移动,再修正保存object 的stub信息。保证托码不受影响。防止内存碎片。 3。当线程的执行状态不会受到GC影响的时候,该线程的PreEmptive GC属性是1,否则是0。此开关受CLR的控制,很多stub中的代码会操作这个开关。当线程idle的时候,PreEmptive 为1。当GC触发时,GC必须等到所有的线程都进入PreEmptive后,才可以发生。 3.1.4Exception Handing异常处理 CLR可以通过元数据采集所有的类型,在thread中通过多种机制记录运行状态。 经典!关于CLR类型加载,动态编译,异常处理等细节,及CLR内部类型(MethodTable,MethodDesc,EEClass,SharedDomain等), Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects http://msdn.microsoft.com/msdnmag/issues/05/05/JITCompiler/default.aspx CLR牛人的blog http://blogs.msdn.com/cbrumme/ 3.2 用Windbg探索CLR的实现 3.2.1 开源CLR实现,Rotor MS提供,稍简化的.net framework的实现。包括全部核心功能,和简化掉的外围功能。80%以上的源码跟发布版一致。可在WIN平台和FREEBSD平台编译。 Shared Source Common Language Infrastructure 2.0 Release http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en 3.2.2CLR重要部分包括: 1.AppDomain的创建和跨AppDomain的方法调用。 2.Reflection方法的实现。 3.Security的实现。 4.Assembly的寻找和加载。 5.ThreadPool的实现和管理。 6.托管object的类型信息和如何绑定到object上。 7.CLR中lock的实现。 8.异常的派发和处理。 9.stackwalk和frame的作用。 10.PInvoke和COM Interop。 Server Flavor 的GC Thread,Finalizer thread和DebuggerRCThread. Special threads in CLR http://blogs.msdn.com/yunjin/archive/2005/07/05/435726.aspx Things to ignore when debugging an ASP.NET hang http://blogs.msdn.com/tess/archive/2005/12/20/505862.aspx 阅读源代码窍门: 1。重要的文件比较长。 2。善于在调试器中设断点进行分析,查看函数间调用顺序。 3。编译rotor,使用来运行托管代码,进行源代码调试。 4。打开LOG。 5。加入自己的代码测试。 3.3通过SOS调试托管程序 3.3.1CLR让托管程序的调试简化。 CLR程序包含的元数据,包含描述性的类型信息,所以CLR程序可以获取类型信息、函数名,支持reflection. AppDomain是CLR的执行和访问边界,比process节省系统资源。 CLR可以做到,而C++不行: 1。反编译获得源码。 2。发生异常后CLR可打印callstack. 3.托管程序的symbol不再重要。 4。获取一个class的详细定义和所有方法。 5。列出内存中所有的CLR object. 6.对于任何CLR object,可以找到该object的类型信息。 CLR调试分种方式,一种VS IDE借助DebugRCThread通信,设定断点,查看程序变量。缺陷是无法利用CLR内存管理的优势来获取内存方面的信息。 3.3.2SOS的命令介绍 在CLR的安装目录下,可找到SOS.dll,随之发布的windbg debugger extension.SOS的调试命令通过读取目标进程中的CLR内部数据结构进行调试和分析。 !dumpstack -ee列出当前stack中所有托管方法地址,也能看到callstack. !dumpstackobjects.列出当前线程的stack 中保存的所有的托管object,包括地址和类型。 !dumpobj(!do) 指定object的详细信息。 !dumpmt跟函数地址作参数,打印函数表信息,加上-md参数可以打印出函数表中所有函数信息。 !eeheap列出managed heap的统计信息。 !dumpheap打印出managed heap中所有对象的址址,重点检查: 1内存是否被某一种类型的object 占用。 2。某一种类型的object数量是不是特别多。 3。不同类型的object 是否有倍数关系。说明类型间有引用关系。 !gcroot 对于任何一托管对象,是否能够被GC收集,取决于其是否有root.可以搜指定object的root。将整个引用链打印。 一个object 要成为root,可能如下: 1.class的static成员。CLR会把其保存在一个object Array中,用pinned handle保存。 2.维护程序运行的必要成员,比如AppDomain本身,这类object会被CLR直接用strong handle保护起来。 3.stack上的成员。说明该object还在被某一个thread使用。 4.被托管代码直接pin起来的object. !objsize该对象所引用的下级对象总大小。 !SyncBlk打印lock,对于RWLock或信号量等其他lock无效。 !soe在指定类型的CLR异常发生时中断下来执行自定义命令。 !dumplog StressLog, !savemodule把dump中指定的module,无论是托管的还是非托管的保存到本地文件。抓到dump,相当于抓到源码。 3.4SOS演示。 .load SOS.dll .cordll版本匹配信息。 CLR优秀的平台,通过关键的命令就可以找到关键的数据结构,定位引发问题的Object. !ip2md映射内存地址到托管函数名。 CLR debugging案例: If broken it is,fix it you should http://blogs.msdn.com/tess/ 3.5.1释放COM对象的两难 ReleaseCOMObject API 减少COM对象的引用计数。 如不及是调用,释放滞留到GC发生的时候。而GC发生无固定规律。COM无法释放,Finalizer thread会堵塞,导致内存持续增长。 如果使用多个COM,且之间有依赖关系,需按特定顺序释放的话,Finalizer thread无法保证正确的顺序,可能死锁。 开发人员不一定能确定正确调用ReleaseCOMObject 的时机,例如:传递COM 对象给另一个线程,同时继续使用,哪一个线程负责调用结束引用。 问题的关键在于CLR管理内存的机制。CLR通过检查对象是否还有root来判断可否释放,而COM通过引用计数来管理。 COM Interop Wrapper Object还在托管代码和非托管代码间传递多次,更复杂! 3.5.2PInvoke应该PIN住内存防止崩溃 CLR传递一块托管BUFFER到非托管API,异步填充。用C# fixed 或MC++ __pin关键字pin住这buffer对象。防止被GC移动导致访问无效或crash。 这类问题难寻根源,要有一个重现环境,配合LOG调试。 3.5.3pin住内存会碎片 在CLR上,不建议大量使用MC++开发的module,原因是存在不可避免的死锁可能。 在C#定义类型很麻烦,用MC++自动生成类型的简单方法: 用MC++直接写调用WIN API,使用windows.h中定义好的结构。编译后反编译成C#,可获取类型定义的代码。 增加对CLR的理解,5个著名的CLR Blog http://blogs.msdn.com/cbrumme http://www.cnblogs.com/flier http://blogs.msdn.com/maoni http://blogs.msdn.com/tess http://blogs.msdn.com/yunjin
|
最近读者: