VC权威剖析:MFC原理、机制与开发实例—-读后感

基础概念和消息映射

Image
窗口:
按级别分:
桌面窗口
     顶级窗口
          子窗口
     重叠或弹出窗口
CS_PARENTDC:
     有此属性(并不是窗口风格),它可以继承父窗口的显示设备上下文。edit,button都是如此的。
WS_CHILD:
     纯子窗口,只有客户区,并且客户区的显示界面要在WM_PAINT消息处理中进行绘制。edit,button都是如此的。
WS_OVERLAPPED:
     若不同时指定WS_CHILD那么它是顶级窗口,总有标题栏WS_CAPTION和边框,并且自动设置WS_CLIPSIBLINGS。创建重叠窗口时可以指定默认大小选项CW_USEDEFAULT。
WS_POPUP:
     弹出窗口创建时,也会自动设置WS_CLIPSIBLINGS。但其它风格必须专门指定,包括WS_CAPTION,也不能指定默认大小选项。
检索窗口:可以跨进程找到窗口的句柄,因为用的是CWnd的静态函数,如下:
FindWindow()
GetWindow()
以下相对来说是内部查找了。
用于找父窗口的:
CWnd::GetParent(),可以省略CWnd
但有可能父又是别人的子,所以
CWnd::GetParentOwner()
用于找子窗口的:
CWnd::GetTopWindow()
CWnd::GetDlgItem()
CWnd::GetDescendantWindow()——–通过ID号取得窗口指针
CWnd::GetWindowFromPoint(POINT)函数取得占用父窗口客户区指定点的子窗口。实参POINT是客户区坐标。
消息:
Image
窗口消息(区别于线程消息):
     窗口消息:除了WM_COMMAND外,所有WM_开关的消息。与某类窗口紧密相关。
1,系统自动生成的窗口消息映射
2,通过类向导来生成的消息映射
3,通过宏ON_MESSAGE(message,memberFxn)
主要讲解以下三类消息,原因是类向导不能自动帮助生成,也没有系统建立好,要手工添加。包括自定义消息。
     命令消息
          菜单(工具栏,加速键)、按钮向窗口发送,要求执行某个功能操作的消息,与程序动作相关。
          用宏ON_COMMAND(id,memberFxn)来建立消息映射。加入数组的第二参数为,CN_COMMAND(内容是0,而通知消息则是非零值)
     通知消息
          MFC为每个通知消息都定义了宏,如果控件的内容变化ON_EN_CHANGE,ON_EN_UPDATE,鼠标滚动等。
          通过ON_CONTROL(wNotifyCode,id,memberFxn)来生成消息映射。其实它是ON_COMAND的重载而己。新控件都使用专门的ON_NOTIFY来建立了。它能传递更多的附加信息。如:
         纯正,新式的通知消息使用如下:
          ON_NOTIFY(TVN_KEYDOWN,IDC_TREE,OnKeydown)
          注:在两层面变化了,通知代码和处理的函数原型上,后者有实参,并引入新的结构了。
          不通过ON_CONTROL来消息映射的特殊通知消息。WM_CTLCOLOR
     反射消息
          由主窗口通过CWnd::ReflectLastMsg()发给子窗口,此函数调用了子窗口的OnChildNotify()—-ReflectChildNotify()
          在其中,就是原始的switch了。
               对旧式通知消息的反射消息和通知消息的反射消息时
               —–OnCmdMsg()
Image
命令ID:
     [0x8000,0xF000)———–全局命令,可由不同对象处理。菜单项的的ID值都在此范围内。
     小于0x8000—————-局部命令,按钮
MFC应用程序角度—-CWndApp分析

接开发者地气的些设计。它的父和子,都是一个程序从生到死的维护者。在继承的应用类实例中,把这爷仨的属性都设置了,找到相应的归宿。
有三种主窗口:单选的。事实看看MFC应用程序向导即可。
单,多文档的主窗口都是CFrameWnd(后者派生的)。而基于对话框的就不用多说了。
Image
m_pszAppName
向导中填写的工程名称,可以在CWndApp构造时传参指定。如果没有指定,会使用字符串资源“AFX_IDS_APP_TITLE”,如果还没设置这个串,将由可执行文件名代替。
属于CWndApp类
m_pszExeName
属于CWndApp类
m_pszProfileName
应用程序的INI配置文件名,在继承应用类中设置自己的配置文件 。
GetProfileString()等系列函数来配合。
属于CWndApp类(默认是执行程序.ini)
m_hInstance
模块在进程中的装入地址
属于CWndApp类
m_pMainWnd
在CSingleDocTemplate或CMultiDocTemplate构造时,就把资源文件绑定和主窗口框架类,文档类,视图类全都实例化,同时设置了此指针。基于对话框的也按其方式会设置此指针。注:没有命令行参数时,默认执行ID_FILE_NEW
文档,视图,都是MFC管理的,实例在堆上,不用手动回收。
当设置好,别忘记
ShowWindow(SW_SHOW);
UpdateWindow();
SetWindowText(“XXX程序”);
这样这个主窗口的消息循环,在Run时触发。
属于CWinThread。设置了此指针的UI窗口,在销毁时,都是安全的。
用向导也好,自己个性化改造也好,反正重载的initInstance函数至少有有模有样了。
命令行参数功能,有时在不同进程启动的相互调用时很有用。

windows核心编程(第5版)读书笔记一

第一章节 错误处理
几乎所有的windows API返回值都是下列之一:
VOID——不会失败,失败了骚扰bill gates去,
BOOL——不用说了吧,
HANDLE—失败则是NULL,当然有时是-1,
PVOID—–失败是NULL,成功就是指针喽,
LONG/DWORD—-要看具体的上下文环境了。
当调用失败,返回值会先指出已经发生错误。所以要先判断返回值。然后,使用GetLastError()来得到详细的错误提示。返回值是dword类型的,要得到描述,加入WinError.h.
技巧一:在监视中,$err,hr,就会看到调用的API错误的内容。
技巧二:使用VS的IDE小工具,error lookup
只你自己开发的模块想返回不一样的error时,可以使用SetLastError(DWORD)来写入,当然,错误代码要像点样,即32位中的29位要必须为1.而0是给系统用的。
第二章节 字符和字符串的处理
VC6——ANSI,DBCS——-/Zc:wchar_t(这个选项就会有定义wchar_t这个数据类型)
VS201X———–ANSI,UNICODE(指的是UTF-16)
事实上,typedef unsigned short wchar_t;
WinNT.h中统一了类型的名称。
TEXT(param)在unicode宏定义下,在param前加了L前缀。
所以,TCHAR也是个宏,可以配合TEXT(),_T() 来一起变脸。
unicode阵营的后缀或关键字母:
w—wide
L—-
ansi阵营的后缀或关键字母:
a—ansi
中性函数:或会变脸的,应用在两种编码下都可以编译场景,加入TChar.h配合String.h
_tcslen()会变脸成wcslen或strlen
C库的新版本的安全系列函数
1)字符串函数:加入StrSafe.h
_tcs拼加后面的选项即可cpy/cat/再拼接_s后才是安全字符串函数。第二参数为要处理的字符长度,用_countof宏(stdlib.h) 来得出字符数最好,注意不是字节数哟.
StringCch为前缀的也是安全函数。
注意:
使用微软包装的安全函数代替C库函数,好处是即时发现内存操作的异常,但有时也可能会出现“Debug Assertion Failed”对话框。当然要是debug模式下,当然也有办法屏了它,用release或定义InvailidParameterHandler函数,然后注册它,再程序运行开头加入个宏:_CrtSetReportMode(_CRT_ASSERT,0);
判断返回值必须等于宏S_OK,当不是这个值时必须要检查字符串的操作。
2)安全缓冲区函数:加入CrtDefs.h
memcpy_s——–wmemcpy_s
memmove_s——-wmemmove_s
第三章节 内核对象
有SECURITY_ATTRIBUTES结构为参数的生成才是内核对象,区别于GDI和其它的对象。
所以这也揭露了内核对象的本质是—-结构。而句柄则是—–由系统管理着的内存地址
CloseHandle()函数详析:
工作步骤:
1,验证自己主调进程中的句柄表,是否有权访问此句柄。如果有效,则获得内核对象的数据结构地址。
2,把内核对象结构使用计数减1,如果是0了,则销毁。
注意:
如果参数是无效的句柄,CloseHandle返回false,GetLastError返回ERROR_INVALD_HANDLE。如果在调试,则出现0xC0000008的异常抛出。
当函数返回前,清除当前进程的句柄表,所以以后代码不能再使用此句柄了。
当用变量保存过句柄的话,应该设置为NULL。
任务管理器是可以查看程序中的进程句柄表。
跨进程边界共享内核对象—略

windows核心编程(第5版)读书笔记二

第四章节 进程
进程内核对象在创建的时候总会处于未激发,无信号状态,但当进程终止时,系统自动会让进程对象变成激发状态,并且会永远保持这种状态。即回不到未激发状态。
C库会根据三个维度选择入口点函数:
编码,运行模式,运行环境
_tWinMain(WinMain)
wmainCRTStartup
_tmain(wMain)
_tmain(Main)
mainCRTStartup
wWinMainCRTStartup
WinMainCRTStartup
_tWinMain(wWinMain)
编码
ANSI
UNICODE
UNICODE
ANSI
ANSI
UNICODE
ANSI
UNICODE
运行模式
GUI
CUI
CUI
CUI
CUI
GUI
GUI
GUI
运行环境
WIN16
WIN32
WIN16
WIN16
WIN32
WIN32
WIN32
WIN16
在VC自有的C运行库的源代码中,crtexe.c中可以找到以上win16四个函数的源代码。
这些启动函数或者说入口函数的作用是:
1)获取指向新进程的完整命令行的一个指针
2)获取指向新进程的环境变量的一个指针
3)初始化C运行库的全局变量。如果包括了StdLib.h,就可以访问这些全局变量了。
4)初始化内存分配和IO底层
5)调用所有全局和静态C++类对象的构造函数。
系统接管,加载exe,dll到进程的一个地址空间(与链接器有关了),把这个地址返回给第一个入口函数的参数,即实例句柄。
下面可以玩这个句柄了,GetModuleFileName().
如果,你忘记实例句柄了,或没没放到全局里,不过没关系,可以用GetModuleHandle(param).参数可以是一个路径,最好是程序里用到的某个dll,要么,param就是NULL,这样就会得到运行着的当前进程的实例句柄。
命令行和环境变量,最好不要用C运行库初始化后的内容,万一时机没对,还没初始化你就读了,会出问题,所以window有厚道的API。GetCommandLine(),C运行库都在用,你凭啥找事儿,对吧。
技巧一:配合CommandLineToArgvW(GetCommandLine(),&nNum),可以得到友好的命令行参数。
这个配置和系统—》高级系统设置—》系统变量的内容是一致的。
入口点函数完后,就是进程的后事了,包括:
1)C运行库接管,调用自己的exit
2)调用_onexit()此前注册的任何一函数,假如你注册了的话。
3)调用所有全局和静态C++类对象的析构函数
4)如果有DEBUG则可以生成内存泄漏报告,可以深入去研究下。
5)C运行库来调用操作系统的ExitProcess函数,把入口点返回值以参数传入,即退出代码。这样系统就会杀死进程了,此函数返回值是void,它通知OS了,就应该无声息的over了。
注意,有时入口点返回,而有时线程可以调用ExitProcess和TerminalPorcess。
入口函数中显示使用ExitThread的函数,会让主线程退出,但如果其它线程还在工作呢?进程就不会退出,造成僵尸进程的凶手。
入口函数中显示使用ExitProcess的函数,会让C运行库函数没有执行清理资源的机会。
所以,最保险就是永远不显示的调用ExitX之类的函数。
更NB和危险的函数还有,那就是TerminalProcess,它厉害在它可以在某线程里被用,去干掉自己和别人的进程。

进程当前目录
这里要注意,因为,进程下的线程是有能力改写进程的当前目录,这也包括子进程。所以,要注意。
线程会通过GetCurrentDirectory()和SetCurrentDirectory()来存取路径。
做为进程,可以使用GetFullPathName()来看看现在的路径或者根据驱动器去环境变量里找到某路径。

windows核心编程(第5版)读书笔记三

第六章节 线程基础
创建线程,要使用_beginthreadex来代替WAPI提供的CreateThread。因为,要从编译器角度来创建。从用户角度至少看起来没有依赖OS。但事实上,里面还是用了CreateThread.
同样,杀线程,使用_endthreadex.
注:用以上两个库函数的原因是,因为,如果你用了signal函数,那么就要用_end函数来清理资源,可是那样,你就画不圆上下文了。同时使用时要注意有EX来标识的,因为存在着旧的弃用的函数。
CloseHandle是可以传已经被清理的或伪句柄的,只是它会返回false,并且对error置成ERROR_INVALID_HANDLE。
TerminalThread是异步的,只是发信号。要确定被杀的线程死了,要用WaitForSingleObject,向其传递线程句柄来守候。原理就是,线程的内核对象,当引用计数减为0时,会变成激发状态,有信号状态。
GetExitCodeThread是检查线程退出代码的。如果线程还没有退出,返回是STILL_ACTIVE宏的值。如果已经退出,返回true,并有相应值。
微软提供的C/C++库用于本机的开发。
libCMt.lib
库的静态链接发行版本
libCMtD.lib
库的静态链接调试版本
MSVCRt.lib
导入库,用于动态链接MSVCR80.dll(默认库)
MSVCRtD.lib
导入库,用于动态链接MSVCR80D.dll
MSVCMRt.lib
导入库,.net的托管/本机代码混合
MSVCURt.lib
导入库,.net的MSIL代码
线程句柄自获函数:
几乎所有的WAPI函数都要在第一参数传句柄的,所以,当你突然眼前一亮,想用WAPI了,这第一个参数必须难不住你呀。
HANDLE GetCurrentProcess();
HANDLE GetCurrentThread();
注意:以上两函数返回的是“伪句柄”
原因:
1)它们不会在进程句柄表中新建句柄,当然也不会影响引用计数。当然,CloseHandle()多释放一次,程序不死,只是它返回false。
2)它们返回的句柄要根据运行所在的进程和线程来关联。比如,在父线程中调用了,返回的句柄即使以参数传给子进程,则其指向仍然是子线程的。所以这一点有些像虚函数中的虚指针,只能意会不能言传了。
当然,有时用线程ID做些逻辑时,可以使用:
DWORD GetCurrentProcessId();
DWORD GetCurrentThreadId();
当然,如果你有了ID,你还可以换成句柄。
OpenThread(),下面示例一下转换办法并且,给出一个对进程优先级操作的技巧。
VOID SuspendProcess(DWORD dwProcessID, BOOL fSuspend)
{
     HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,dwProcessID);
     if(hSnapshot != INVALID_HANDLE_VALUE)
     {
          //walk the list of threads
          THREADENTRY32 te =  {sizeof(te)};
          BOOL fOK = Thread32First(hSnapshot, &te);
          for(; fOK; fOK = Thread32Next(hSnapshot, &te))
          {
              if(te.th32OwnerProcessID == dwProcessID)
               {
                    //主角登场
                    HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
                    //略过对hThread的判断
                    if(fSuspend)
                         SuspendThread(hThread);
                    else
                         ResumeThread(hThread);
                    CloseHandle(hThread);
               }
          }//endfor
          CloseHandle(hSnapshot);
     }
}
最后,再赠送两个函数用于获取进程,线程运行的时间。
GetProcessTimes(),GetThreadTimes()
第七章节 线程调度,优先级和关联性
线程可以自己挂起,但不能自己恢复,并且,挂几次要应该恢复几次(ResumeThread)。挂起可以在线程生成时以参数传入,也可以用SuspendThread来人工自由操作。
第八章节 用户模式下的线程同步
技巧:
原子操作的系列WAPI函数:InterLocked*
+ExchangeAdd
+Increment
+ExchangePointer
volatile关键字,用来声明,后面的变量不能被优化,要去不断从内存来读取。
进阶一段:CRITICAL_SECTION结构和EnterCriticalSection和LeaveCriticalSection
要声明个全局的CS变量,然后用前初始,用后删除。
TryEnterCriticalSection比较灵活,做人当如此,能锁定就锁定段,没有段可用时,就立刻通知调用者。
同时,也不要把初始化关键段看低,因为,能避免交给内核态就晚点交,不然,上下文切换的步骤很耗时,耗资源的。所以,给初始化传值或用Set来设置抢段的重试时间,InitializeCriticalSectionAndSpinCount(p1,p2),p1是句柄了,p2大约4000。
注意:
线程抢锁的顺序要一致,否则死锁,即在锁包锁的情况下。
关键段的使用只能在一个进程内来控制其中众线程的同步,并且不能根据时间来等待。
进阶二段:条件变量
线程想把锁释放并把自己阻塞,就使用条件变量。
注意,这时,你得玩两个内核对象才行,即条件变量,必须配合锁。
首先看看这个阻塞函数,我特喜欢微软用sleep来前缀,因为,可读性很高。体现了两层意思:1,这是阻塞的,2,这是有时间参数的,时间到了没拿到条件变量就是false.
SleepConditionVariableSRW或SleepConditionVariableCS看吧,后缀又说明了要配合的锁。够意思。
我不够意思了,不列参数,自己去查手册喽。
有了Sleep就得有Wake吧。它会使等待同一个条件变量被触发的线程得到锁并返回。然后就干活呗,但得到锁了就得释放它。但它不会同时唤醒其它正在等待同一个条件变量的线程。
要看自己的实际需要。反正也是有WakeAll*和共享锁和独占锁来设计。
第九章节 内核态用内核对象进行线程同步
进程和线程内核对象在操作系统的处理上一样设计的。见笔记二的进程描述。
本章节介绍了另外的内核对象来帮助我们玩转线程同步。
1,事件
2,可等待的计时器
3,信号量
4,互斥量
头大吧,不过,我们要想掌握它,必须用不同的维度和结构去3D它。
首先,了解基础,或者说这个是第一个维度。
前进了解了进程和线程,有信号和无信号的规则,那么,这四个对象呢,look
事件:
引用计数器
自动重置/手动重置———–初始参数方式,CreateEvent
是否被触发——————-函数控制,SetEvent,ResetEvent
手动重置事件:
被触发时:等待此事件的所有线程都是会变成可调度状态。
自动重置事件:
被触发时:等待此事件的一个线程,只有一个线程会变成可调度状态。
因为自动重置会在一个线程得到事件对象后,自动把自己变成未触发状态。系统代劳的。
可等待的计时器:
指定时间或每隔一定时间触发
引用计数器
自动重置/手工重置——-初始化方式,CreateWaitableTimer,和事件一样没有其它的设置方式
SetWaitTimer是生成了内核对象后,需要进一步应用时的必经之路。因为,创建后,计时器都是属于未触发状态。
信号量:
引用计数器
最大资源量————-池里的资源总数
当前资源数————-目前应用了资源的数量,用RealseSemaphore来递增
互斥量:
引用计数器
线程ID———–内部自己管理,当计数为1时,则用调用的当前线程ID来填充。
递归计数———是否要触发

windows核心编程(第5版)读书笔记四

第19–20章节 dll的基础和高级技术
入口点函数:DllMain()
一般用来执行一些进程,线程有关的初始化和清理工作。要么不实现它,要么实现正常,返回true,否则,dll可能就不能被使用。
注意:
区分大小写的,否则就是无用的。
隐式调用dll时,系统启动时就会用到DllMain(),而显式调用时,在LoadLibraryex中来调用。并且,切入点是DLL_PROCESS_ATTACH
当引用了dll的exe要建立个新线程时,会检查所以已经被调入进程空间的dll的DllMain。并传入DLL_THREAD_ATTACH。有一个例外就是主线程切入仍以process来通知dll.

VC6的环境小结

安装:
现在主要有win7,win10,但后者还没听说可以装成功。不过好在有虚机嘛。所以,只聊下win7的VC6安装。
VC6的安装源你总应该拿到,最好是SP6的。
如果是英文版本,安装会少些步骤,有一个是overwrite JIT setting的对话框,网查后我选择了否。
如果是中文版本,请寻找帖子,名为:彻底解决兼容问题:Windows 7下载安装 Visual C++ 6_0(VC6) 全程图解
一步步安装即可,VSS和MSDN都可跳过不安装。
最重要的就是插件了,这个才是彰显尊贵的手段。
filetool,wndtab,VA,VC6lineNumber
一个都不能少,全破解版(自己搜吧)。
配置:
1,用标准库,要注意<>的内容两边加空格 ,并且#pragma   warning(disable:4786)来屏闭VC6中的使用标准库的警告。
2,用C++运行程序时总是出现这个错误BSCMAKE: error BK1510 : corrupt .SBR file ‘.\Debug\StdAfx.sbr’,在C/C++—>gernal中取消生成浏览信息的选项。
3,PROJECT->SETING->C/C++->PREPROCESSOR->定义 _AFXDLL,也就是C/C++选项中定义了宏  用_AFCDLL 取代 USERDLL 能解决很多nafxcwn.lib开头的链接错误。
4,Link选项中ignore default lib,根据方式设定,手工加/NODEFAULTLIB:或者填库名。
5,VC6下导出类中的静态成员需要“壳子”才可以调用,而在VS2012中,却直接可用。当不同的环境生成的dll或客户端相互调用时,要注意字符集问题,VC6并不是unicode的。
6,要声明通用函数时,可以不加;在{}后面,但应该声明为static,这样可以避免重复定义的报错。
7,string 的split算法,replace,trim需要自己写算法,最后找到使用模板的。
8,在有unicode的项目中,在循环中使用W2A()可能会有报错,或者提示为,run-time check fail #2 stack …,内存操作越界之类,在VS2012中项目–C\C++设置中,改掉一个设置,“基本运行时检查”从“两者(/RTC1,等同于 /RTCsu) (/RTC1)”换成默认值。其实出现它的问题,还是在于使用scanf时,“%02x”的操作数组应该至少是3,要包含个’\0’的。
9,项目是要供别人调用的dll,要在配置中使用/MD.
10,要通过exe来调度dll时,要配置debug session和work directory
11,当出现外部链接错误时,要先检查link中的lib文件,是否存在于项目目录中。并不需要dll,因为只是链接步骤。
12,字符集问题,VC6是基于MBCS的(有宏定义,当然可以强制成unicode),对于中文用两字节,而字母和数字用一个字节。而VS201X,都是用unicode默认的,所以也就存在于宽字符的结构了,和一系列转来转来的操作。

tinyxml使用技巧

实在是好用呀,优点比如:
1,一次装载到内存,其它地方全用引用即可,一个小指针可以是一个元素,也可是一个节(内部很多子元素)。
2,不用管理元素的内存回收,我是没看源码,号称管理的很好。一个父元素删除,它包含的所有子都释放了。
3,api较少并且,一目了然。
遍历时基本就用三个算法:
1)同级的循环(没用遍历以防会误解包括子节点处理)
//pParent当然是父了
for (TiXmlNode *pSTATE = pParent->FirstChild( “STATE” ); pSTATE; pSTATE = pSTATE->NextSibling(“STATE” ))
{}
注意:要用TiXmlNode *结构,不要用TiXmlElement*
2)当同级的到了最后一个,NextSibling()会返回个空。这时你可以用跳了。
pParent->IterateChildren( pSTATE )
3)以上两个掌握了,就可以玩遍历了。不过,要注意,走到叶子节点时,要跳级。但如果你手工跳了,可能遍历会有重复情况。这时可以使用函数的静态变量喽。可以用静态变量记住父结点。可以用静态list来存放处理过的节点,跳过重复情况。示例代码如下:
void handleExportMap(TiXmlNode * pSrc)
{
if (!pSrc ) return;

static TiXmlNode *bodyItem;

if (“FRAME_BODY” == pSrc->ValueTStr())
{
bodyItem = pSrc ;
}
static TiXmlNode *curpItem;
int t = pSrc ->Type();
printf( “type %d \n” , t);

switch (t)
{
case TiXmlNode :: TINYXML_DOCUMENT:
printf( “Document” );
break ;

case TiXmlNode :: TINYXML_ELEMENT:
printf( “Element [%s]” , pSrc ->Value());
//
if (“CONTENT” == pSrc->ValueTStr())
{
curpItem = pSrc ;
}
if (“MAP” == pSrc->ValueTStr())
{
//跳过MAP的处理
pSrc = curpItem->IterateChildren(pSrc ->Parent());

if (!pSrc )
return ;
else
printf( “Element [%s]” , pSrc ->Value());
}
if (“STATE” == pSrc->ValueTStr() && pSrc == pSrc ->Parent()->LastChild())
{
//跳过STATE的处理
//map中的state
pSrc = curpItem->IterateChildren(pSrc ->Parent()->Parent());

if (!pSrc )
return ;
else
printf( “Element [%s]” , pSrc ->Value());
}

if (“ITEM” == pSrc->ValueTStr())
{

printf( “Element [%s]” , pSrc ->Value());

if (!pSrc ->NoChildren())
{
if (!bMoveItems(pSrc ))
return ;
}

} //end if (!pSrc->NoChildren())
if (“MESSAGE” == pSrc->ValueTStr())
{
curpItem = pSrc ;
if (!bMoveItems(pSrc ))
return ;
}
break ;

case TiXmlNode :: TINYXML_COMMENT:
printf( “Comment: [%s]” , pSrc ->Value());
break ;

case TiXmlNode :: TINYXML_UNKNOWN:
printf( “Unknown” );
break ;

case TiXmlNode :: TINYXML_TEXT:
printf( “Text: [%s]\n” , pSrc ->ToText()->Value());
break ;

case TiXmlNode :: TINYXML_DECLARATION:
printf( “Declaration” );
break ;
default :
break ;
}

if (pSrc ->NoChildren())
{
pSrc = pSrc ->NextSibling();

handleExportMap( pSrc );
}
else
{
//过滤,直到合法
while (pSrc = pSrc->FirstChild())
{
handleExportMap( pSrc );

}

}
}
在这个遍历的框架基础上,可以变化和设计出你的需求了,注意,要分化任务,只做遍历,和只做节点的处理,不要混在一起,不然,一个bug两三天呀。上面的bMoveItems就是当定位到了ITEM节点时,我调用其它的业务处理。因为,人只关注ITEM节点,所以它有一些子节点MAP或子子节点STATE,都在上面一一跳过了。

最后,附上“米”,好“下锅”,注意层次。
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<!– edited with XMLSpy v2013 (http://www.altova.com) by () –>
<LINE ID=”11″ VERSION=”1″>
<FRAME_HEADER>
<ITEM ID=”system_id” LENGTH=”1″ CURPOS=”0″ />
<ITEM ID=”total_length” LENGTH=”2″ CURPOS=”1″ MAX_VALUE=”1025″ />
<ITEM ID=”multi_flag” LENGTH=”1″ CURPOS=”3″ DEFAULT=”0″ />
</FRAME_HEADER>
<FRAME_BODY>
<MESSAGE ID=”NETWORK_ALIVE_STATUS” TYPE=”1″ TRANS=”O2N”>
<ITEM ID=”message_length” LENGTH=”2″ CURPOS=”4″ DESC=”消息长度” MIN_VALUE=”13″ interal_index=”1″ />
<ITEM ID=”time” LENGTH=”4″ CURPOS=”6″ DESC=”时间” FORMAT=”HHMMSS” interal_index=”2″>
<MAP NID=”time” MODE=”1″ SAME=”Y” NINDEX=”2″ KEY=”time|6|4″ DESC=”时间” EX_CODE_KEY=”” />
</ITEM>
<ITEM ID=”version” LENGTH=”2″ CURPOS=”10″ DESC=”版本” DEFAULT=”01H” interal_index=”3″ />
<ITEM ID=”msg_id” LENGTH=”2″ CURPOS=”12″ DESC=”报文ID” interal_index=”4″ />
<CONTENT ID=”data0″ DEFAULT_LEN=”3″>
<ITEM ID=”line_id” LENGTH=”2″ CURPOS=”14″ DESC=”线路号” interal_index=”5″>
<MAP NID=”line_id” MODE=”1″ SAME=”Y” NINDEX=”5″ KEY=”line_id|14|2″ EX_CODE_KEY=”” />
</ITEM>
<ITEM ID=”status” LENGTH=”1″ CURPOS=”16″ DESC=”状态字节:1:代表与当前线路号的信息源(server)连接正常;0:代表断开。” interal_index=”6″>
<MAP NID=”xt_pkg” MODE=”1″ SAME=”N” NINDEX=”6″ KEY=”xt_pkg|16|1″ EX_CODE_KEY=””>
<STATE ID=”11_STATUS_CODE_1″ RAWVALUE=”0x00″ HINT=”通信中断” SAME=”Y”>0x80</STATE>
<STATE ID=”11_STATUS_CODE_2″ RAWVALUE=”0x01″ HINT=”通信正常” SAME=”Y”>0x02</STATE>
</MAP>
</ITEM>
</CONTENT>
</MESSAGE>
<MESSAGE ID=”LOAD_DEVICE_STATUS” TYPE=”1″ TRANS=”N2O”>
<ITEM ID=”message_length” LENGTH=”2″ CURPOS=”4″ DESC=”消息长度” MIN_VALUE=”12″ interal_index=”1″ />
<ITEM ID=”time” LENGTH=”4″ CURPOS=”6″ DESC=”时间” FORMAT=”HHMMSS” interal_index=”2″ />
<ITEM ID=”version” LENGTH=”2″ CURPOS=”10″ DESC=”版本” DEFAULT=”01H” interal_index=”3″ />
<ITEM ID=”msg_id” LENGTH=”2″ CURPOS=”12″ DESC=”报文ID” interal_index=”4″ />
<CONTENT ID=”data0″ DEFAULT_LEN=”2″>
<ITEM ID=”line_id” LENGTH=”2″ CURPOS=”14″ DESC=”线路号” interal_index=”5″ />
</CONTENT>
</MESSAGE>
</MESSAGE>
</FRAME_BODY>
</LINE>

保存和另存,一个TiXmlDocument的SaveFile()函数就搞定了。
生成新的XML时,会用一个api就成了。parent->LinkEndChild(TiXmlNode *);根据不用我解释了吧。加同级,用parent()找父指针嘛。

VS2013字符集小结

VS新版本打开旧项目,即当升级一个vcproject文件时,可能会涉及从“多字节字符集”(可能是utf-8)到unicode字符集的转换。
所以总结一下,代码端如何适应转换。

unicode字符集下,微软引入了一个宽字符集的概念(多讨厌)
所以,有些winapi就是以宽字符集来传参的。

第一类问题:messagebox()函数
所以字符串常量前,加一个L,即L“原来的字符串”;
L的意思是小端序。当为unicode时,长度要么1要么2(汉字),所以,再加上小端序,不会有歧义了。

第二类问题:strcpy()函数要求多字符集,事实此时的CSting已经入乡随俗了用了宽字符集,而直接强制转换就会出现让你头大的提示,
居然const char * 和 CString不等价了。

char SendBuf[512] = {0};
strcpy(SendBuf,strEdit); //CString参数strEdit,报错说不能转换CString到const char *
——————————–
办法一:用新的缓冲区放置,并且必须再转换一下。
char SendBuf[512] = {0};
strcpy(SendBuf,( const char *)strEdit.GetBuffer());
办法二:复杂,此时要做的是把“变质的”CString给救回来。

char SendBuf[512] = {0};
wchar_t temp[512];
MultiByteToWideChar(CP_ACP, NULL, (LPCCH)strEdit.GetBuffer(), 512, temp, 512);
strcpy(SendBuf,(const char *)temp);
m_Conn.Send(SendBuf,strlen(SendBuf));

第三类问题,atoi转int:
把atoi换成_ttoi即可,这是神器,在啥字符集下,都可编译。

第四类问题,CString->std::string 举例如下:
CString strMfc=“test“;
std::string strStl;

#ifdef _UNICODE

USES_CONVERSION
strStl=W2A(strMfc.LockBuffer());

strMfc.UnlockBuffer();

#else

strStl = strMfc.GetBuffer(0);

strMfc.ReleaseBuffer();

#endif
—————-
有时,CString可以做为桥梁,把乱码的UTF-8字符串转成string打印正确。思路是先用下面的函数转换乱码,再把返回的CString转string.
CString UTF8toUnicode(const char* utf8Str, UINT length)
{
CString unicodeStr;
unicodeStr = _T(“”);
if (!utf8Str)
return unicodeStr;

if (length == 0)
return unicodeStr;

//转换
WCHAR chr = 0;

for (UINT i = 0; i std::string 举例如下
BSTR bstrTest = ::SysAllocString(L”Test”);

_bstr_t bstr_t(bstrTest);

std::strStl = bstr_t;

SysFreeString(bstrTest );

std::string -> BSTR 举例如下

std::string name = “nisb”;

_bstr_t bstr_t(name.c_str());

BSTR ret_val = bstr_t.GetBSTR();

————————————————-
转来一篇原理编码的原理:

1:使用CString,要包含文件afx.h,比如在Win32 Console Application中Alt+F7选择Use MFC in a Static Liberary,然后再添加#include就可以使用CString了。

2:WCHAR ch = L’中’;与CHAR ch = ’中’;的区别是第一种使用UNICODE编码,第二种方式一般不经常用到,比如:
WCHAR strA [ 2 ] = { L’中’ , 0 } ;//打开VC的Options菜单,选中Debug选项卡中的Display unicode strings后,可以看到strA的值。
WCHAR strB [ 2 ] = { ‘中’ , 0 } ;

CString strC ;

strC+ = ( ( char * ) strB ) [ 1 ] ;
strC+ = ( ( char * ) strB ) [ 0 ] ;//strC==”中”

3:CString的AllocSysString ( )成员函数;可以方便的把一个字符串转换成UNICODE形式。记得使用完该UNICODE字符串后要调用::SysFreeString()函数释放字符串。

4: CString::AllocSysString ( )或者::SysAllocString得到的字符串并不是普通的UNICODE字符串,它之前的四个字节会存放申请的字符串的长度:

CString strD = ”asdf”;
BSTR strD = strC.AllocSysString( ) ;
long i =* ( ( long * ) strD – 1 ) ; // i == 8;一个UNICDE字符的长度是2字节,所以strD的长度为8个字节。

4:UTF-8码转换为一般的字符串:

#include ” Windows.h ”

int main(void)
{
char str [ 256 ] = {( char )0xE4, ( char ) 0xBD, ( char ) 0xA0, ( char ) 0xE5 ,
( char)0xA5 ,(char)0xBD, (char)0x61, (char)0x62 ,(char)0x63,(char)0} ; //一段UTF-8编码
WCHAR* strA;
int i= MultiByteToWideChar ( CP_UTF8 , 0 ,(char*) str ,-1 ,NULL,0);
strA = new WCHAR[i];
MultiByteToWideChar ( CP_UTF8 , 0 ,( char * ) str, -1, strA , i );
i= WideCharToMultiByte(CP_ACP,0,strA,-1,NULL,0,NULL,NULL);
char *strB=new char[i];
WideCharToMultiByte (CP_ACP,0,strA,-1,strB,i,NULL,NULL);
//strB即为所求
delete []strA;
delete []strB;
return 0;
}

5:在转换方面_bstr_t是最最灵活的,他提供了UNICODE到一般字符串的直接转换:
#include
_bstr_t strA;
char *strB=”中国人”;
strA=strB;
WCHAR *strC=strA;
long i =* ( ( long * ) strC – 1 ) ;// i 亦是字符串的字节长度
char *strD=strA;
return 0;

宽窄字串转换!
两种方法:1、CString 2、_bstr_t

#include “comutil.h”
//注意在Project-》Setting-》Link中加入comsupp.lib,从而使_bstr_t可用

void CAaaDlg::OnButton1()
{
// TODO: Add extra cleanup here
_bstr_t a(L”sdfsafds”);
a+=”zzz”;
char * b = (char *)a;//获得内部BSTR的char*指针,勿做修改和释放
WCHAR *c=(WCHAR *)a;//获得内部BSTR的WCHARr*指针,勿做修改和释放
CString p = L”asdfsa”;
p+=”ppp”;
WCHAR *y =p.AllocSysString(); //获得WCHAR*指针,使用完请释放,否则有内存泄漏。
char *z = p.GetBuffer(p.GetLength()+1);//内部Buffer指针,z不要释放

::SysFreeString(y);
}

程序员的自我修养

四大神器,解决依赖:

ldd是用来查看执行文件或.a,.so的依赖关系利器(从链接的角度)。
nm———————查看静态符号表,观察依赖关系
-sD *.so
objdump -p t_sql.o | grep NEEDED

库命令:
file *.o—————看ELF信息
objdump -[看列表] *.o——看目标文件的结构和内容
基本信息(-h)
更多信息(-x)
依赖信息(-p)objdump -p t_sql.o | grep NEEDED

动态链接的可执行文件中包括got,有时有调用别模块时的共享对象也存在此段。

查看一个so是否PIC(地址无关性)编译的:
readelf -d .so | grep TEXTREL
如果有输出,用的是装载时重定位(虽多进程调用可以,但牺牲了效率),否则,证明编译时加了-fPIC,是地址无关。

readelf—————-看elf详细信息,包括(系统位,大小端,是否可执行)
文件头信息(-h)
详细的信息(-S)较权威的段表信息
从linker角度来查看链接器使用的dynamic段的信息(-d *.so)链接库的elf信息,同于ldd(宏观模块体现)。
-sD *.so查看动态符号表(从库提供服务或使用的角度),针对段.dynsym观察依赖关系
-l ../bin/devcomm | grep interpreter(链接器使用的段)查看生成它的链接器(可执行文件)
-r *.so查看重定向符号表

链接的秘密:

目标文件静态重定向—动态链接文件重定向——可执行动态链接文件重定向
rel.text——————rel.dyn———–针对数据引用,修正位于数据段中的got(全局变量的引用地址)
rel.data——————rel.plt————针对函数引用,修正got.plt

全局符号表

二元信号量:

信号量(多元):

互斥量:不同于二元信号量:哪线程存得,哪线程释放
相同二元信号量:都是约束线程排队的

临界区:在一点处与互斥量不同:其它进程不可见。(和内核对象名称不太相符)

条件变量:同时阻塞和唤醒多个线程