分类: 系统运维
2009-04-16 16:57:33
我是个ACE菜鸟,前一阵子头痛于万事开头难的问题,很多问题相当SB,不过考虑到很多初学者和我一样被老板骂得焦头烂额,这里还是把学习 ACE第一周遇到的问题贴上来,希望对刚刚接触ACE的鸟伴有所帮助(-:<。本贴讨论的范围:
1、解决5.6版本的ACE使用 ACE_HAS_MFC 后提示WIN32_NT版本过低的问题。
2、MFC应用程序调用ACE库,不进行任何操作还存在内存泄露的问题;
3、MFC程序加载的动态链接库隐含调用ACE方法导致的内存泄露;
4、解决宽字符集和窄字符集的编译器自动兼容问题;
下面逐一解决:
1、解决Visual Studio 2008 编译5.6版本的ACE使用 ACE_HAS_MFC 后提示WIN32_WINNT版本过低的问题。
原因在config-win32-common的这一句:
#if !defined (_WIN32_WINNT)
# define _WIN32_WINNT 0x0400
#endif
改为
#if !defined (_WIN32_WINNT)
# define _WIN32_WINNT 0x0600
#endif
即可
2、MFC应用程序调用ACE库,不进行任何操作还存在内存泄露的问题;
这种情况需要解决两个问题:
首先是由于ACE的Object_Manager的工作方式造成不以main()入口的进程无法自动初始化和清理。在以main()函数为入口的程序(控制台之类),或者使用了宽字符的_w_main入口的程序中,ACE库会自动在入口处调用 ACE::init()完成对象的初始化,在结束时调用ACE::fini()进行对象清理。而MFC程序和以WinMain入口的程序,ACE不会自己调用。因此,需要手工添加。重载CWinApp的InitInstance()和ExitInstance(),在所有步骤开始前初始化ACE,在所有工作结束后终止ACE,范例:
BOOL CMyApp::InitInstance()
{
...
CWinApp::InitInstance();
...
ACE::init();
....
}
int CMyApp::ExitInstance()
{
ACE::fini();
return CWinApp::ExitInstance();
}
如果在Dll中,不要试图在DllMain之类的入口进行初始化,会导致内存泄露。在Dll中导出两个方法,一个用于初始化,一个用于终止,分别调用ACE::init();ACE::fini();就可以。范例:
int MY_EXT_DLL MY_init(void)
{
...
return ACE::init();
}
int MY_EXT_DLL MY_fini(void)
{
...
return ACE::fini();
}
主程序:
BOOL CMyApp::InitInstance()
{
...
CWinApp::InitInstance();
...
MY_init();
....
}
int CMyApp::ExitInstance()
{
MY_fini();
return CWinApp::ExitInstance();
}
其次,ACE编译的时候,默认的config.h中没有定义ACE_HAS_MFC,导致ACE的线程并不是派生自MFC的CWinThread,于是在程序结束时维护线程的额外信息无法清理。这需要在编译ACE时指名MFC选项。
#ifndef ACE_HAS_MFC
#define ACE_HAS_MFC 1
#endif
这种做法有缺点。这样做后,编译出的ACEDLL将需要MFC库,更要命的是,如果打算在DLL中使用,必须创建基于MFC的DLL。一般的适用案例:
MFCApp->ACE(MFC)
MFCApp->MFCDLL->ACE(MFC)
不过解决了这两个问题,一般就能解决MFC程序中的内存泄露了!
3、MFC程序加载的动态链接库隐含调用ACE方法导致的内存泄露;
首先,如果加载ACE的动态链接库是标准WindowsDll,而使用该Dll的是MFC程序,MFCApp->Win32Dll->ACE(Win32),就比较麻烦。一般这种应用方式带来的是一次性的内存泄露,不影响工作。只是让人很不爽罢了。如果还允许修改工程结构,则把调用过程改做:MFCApp->MFCDLL->ACE(MFC),其他步骤就如第一个问题中所述。
4、解决宽字符集和窄字符集的兼容问题;
自从Visual Studio 2005后,编译器默认字符集就是宽字符。包括CString在内的多数对象均采用Unicode字符集。一般为了在宽字符集和窄字符集中切换,程序员会使用一系列的模版方法,比如_tcscpy()与TCHAR混用来克服一致性的问题,使得在编译时切换字符集不必重写代码。
如果没有在ACE编译时指定ACE_USES_WCHAR,ACE所有函数入口都是窄字符的,将增加编程的复杂性。比如,我定义了一个ACE内存映射对象,需要初始化当前文件的名字,为了兼容Unicode字符集和窄字符可能这样写:
class CMyClass
{
...
ACE_Mem_Map m_memmap;
...
}
void CMyClass::MyFun1()
{
CString strDiskFileName(_T("NoName"));
//获取文件名
CFileDialog filedlg(FALSE,_T(".map"),0,4|2,_T("映像文件(*.map)|*.map|所有文件(*.*)|*.*|"));
if (filedlg.DoModal()==IDCANCEL)
return;
strDiskFileName = filedlg.GetPathName();
//打开文件
#ifdef _UNICODE
--> if (m_memmap.map(CW2A(strDiskFileName),nSize,O_RDWR | O_CREAT)==-1)
{
if (m_memmap.map(nSize)==-1)
return NULL;
}
#else
--> if (m_memmap.map(strDiskFileName,nSize,O_RDWR | O_CREAT)==-1)
{
if (m_memmap.map(nSize)==-1)
return NULL;
}
#endif
...
}
这段代码使用了MFC的CW2A类,如果程序中多次出现类似CW2A的转换,当突然需要改为窄字符时,就需要大改代码。何况,Win32API没有提供CW2A类,取而代之的是系统方法调用。还接着上一个例子,如果想在非MFCDLL中取得页面文件的名字并保存,代码变得十分可怕:
class CMyClass
{
...
ACE_Mem_Map m_memmap;
TCHAR m_strFileName[BUFLEN];
...
}
void CMyClass::MyFun2()
{
const char * pcstrFileName = m_memmap.filename();
#ifdef _UNICODE
//转换窄字符为宽字符,没有MFC只能调用API
size_t origsize = strlen(pcstrFileName) + 1;
size_t convertedChars = 0;
if (0==::MultiByteToWideChar(CP_THREAD_ACP,MB_PRECOMPOSED,pcstrFileName,origsize+1,m_strFileName,BUFLEN))
{
MessageBoxA(0,"代码页错误!","Error",MB_OK);
return ;
}
#else
_tcscpy(m_strFileName,pcstrFileName);
#endif
...
}
为了避免这种麻烦,采用如下手段:
首先,修改工程文件,除了Debug和Release外创建UnicodeDebug,UnicodeRelease编译方案,为他们设置"Use Unicode Character Set"字符集选项;输出文件为UnicodeRelease:ACEu.dll,ACEu.lib,UnicodeDebug:ACEud.dll,ACEud.lib,存放到固定的文件夹中。
而后,重写config.h,基本如下:
#ifdef _UNICODE
#ifndef ACE_USES_WCHAR
#define ACE_USES_WCHAR
#endif
#endif
#ifndef ACE_HAS_MFC
#define ACE_HAS_MFC 1
#endif
#include "ace/config-win32.h"
重新编译,将编译生成四组库,ACE/ACEd/ACEu/ACEud,可以同样修改QoS及其他工程。注意,Qos链接选项中的ACEdll要配合字符集选项设置。接着,创建$(ACEDIR)/ACEPreInclude.h,如下:
//本文件提供对ACE的智能引用,链接对应的库
#ifdef _UNICODE
#define ACE_USES_WCHAR
#ifdef _DEBUG
#pragma comment( lib, "aceud.lib")
#else
#pragma comment( lib, "aceu.lib")
#endif
#else
#ifdef _DEBUG
#pragma comment( lib, "aced.lib")
#else
#pragma comment( lib, "ace.lib")
#endif
#endif
最后,在要使用ACE库的项目中,Stdafx.h加入:
#include "ACEPreInclude.h"
即可。编译器将自动根据字符集选项选择相应的库进行连接。写好后,上述范例代码变为:
class CMyClass
{
...
ACE_Mem_Map m_memmap;
ACE_TCHAR m_strFileName[BUFLEN];
...
}
void CMyClass::MyFun2()
{
const ACE_TCHAR * pcstrFileName = m_memmap.filename();
_tcscpy(m_strFileName,pcstrFileName);
}
简单多了哦!