以下是引用uesoft在2007-11-21 15:14:29的发言: 原文作者:杨志军,长沙优易软件开发有限公司CAD部 AutoPDMS8.0源码使用高版本ObjectARX(2004/2007)编译的总结 一、开始编译之前 1. ObjectARX 2002工程向ObjectARX 2004工程升迁时的准备工作 1.1 编译工具 AutoPDMS8.0是在VC 6.0下开发的,使用ARX 2002(含)以下的版本时,用VC 6.0编译。 如果使用ARX 2004,则则需要使用VS .NET 2002编译,因为ARX 2004自身是用VS .NET 2002编译的,它的图形显示部分使用的MFC库高于VC6.0自带的版本。 同样,ARX2007及更高版本要求使用VS .NET 2005编译。 VS .NET 2002、2005等高版本的编译器可直接转换VC6.0生成的工程,这给编译时工程的配置带来很大的方便。 1.2 ARX动态库的变化 从网络查询得到的资料来看,每个引用了ARX动态库的工程配置需要改变。 首先,引用的ARX动态库名称为XXX15.lib的全部要改写成XXX16.lib(ARX 2004)或XXX17.lib(ARX 2007);动态库名称中没有带"15"字样的,保持不变。 其次,有一些ARX 2002里的动态库,在高版本的ARX SDK里已经整合到一起,需要在工程配置中去掉:acrx15.lib、acsiobj.lib、acutil15.lib。 再次,ARX 2007 是用Unicode方式编译的,这意味着它提供的接口函数的所有字符串类型参数、数据结构、返回类型都是宽字符的(wchar_t)。编译过程中必须要考虑这点,否则不能通过编译,更不能通过链接。关于这里的分析和处理过程,详见"字符转换"部分。 2. 确定哪些动态库可以共享 AutoPDMS8.0工程中,大部分直接依赖ARX的动态库在图形显示等模块,虽然很多工程都需要动态库acge15.lib,但我们只用到其中坐标信息等几个基本的数据结构(不涉及到字符串),它们在各版本的ARX中可以通用(后来编译时证明了周进的这个判断是正确的),在这个基础之上,我们是否可以将这些只依赖于acge15.lib或干脆一点也不依赖于ARX的工程,编译出一个版本让它们共用;直接依赖于ARX的工程针对不同版本的ARX编译出不同版本。然后让它们组合产生适应各版本Auto CAD平台的AutoPDMS 软件。这样维护的时候能简化很多工作。 编译时我们向这个目标前进,首先证实了VC 6.0的确不能编译ARX 2004,之后发现VS .NET 2002编译出的动态库在VC 6.0下不能正常使用,很可能是两种编译器产生的动态库导出函数命名规则不一样,VC 6.0按照自己的导出函数命名规则不能正确的解析VS .NET 2002生成的库文件(.lib)。反过来,VS .NET 2002也不能完全兼容VC 6.0的动态库,当VC 6.0生成的动态库中接口有字符串参数的话,VS .NET 2002链接时不能识别。再次,Auto PDMS2.0也不只是底层动态库直接依赖于ARX,一些设计部分的工程因特殊需求,也需要使用ARX动态库。这些将可能"共用"的部分限定到一些独立的可执行文件和少数不依赖于ARX的动态库,如持久层等。 二、编译时碰到的一些问题 VS .NET 2002/2005在编译期间,执行的语法检查比VC更严格。一些在VC 6.0下还能正确通过编译连接的不规范写法,到高版本的编译器里就直接报错了。下面提出的问题在VC6.0都是没有的,只在.Net 2002或 2005里出现。常见的有下面几种: 1. for循环循环变量作用域问题 for( int i = XX; ... ) { /* i的作用域 */;} /* for循环外 i 不能被引用 */。编译时VS .NET2005提示的错误里发现很多地方是因为for循环变量作用域问题。这种写法不规范,在VC6.0里居然是合法的。http://system/dispbbs.asp?boardID=10&ID=2279&page=1 2. 关于模板的问题 在VS .NET2005下编译GlobalShare时,出现较多的下面2.2、2.3列出的情况。 2.1 将模板类导出 在模板类前加了 _declspec(dllexport),这个在VS .NET 2002以后会直接报错。这个问题大家已经修正了。 2.2 缺失关键字 typename 在模板中使用模板参数定义出来的类型,需要加上 typename 关键字。typename关键字平时使用较少。MFC对它的解释是: The typename keyword tells the compiler that an unknown identifier is a type. Use this keyword only in template definitions. For example: // typename.cpp template class X { typename T::Y m_y; // treat Y as a type }; int main() { } 2.3 缺失模板声明 template<> 模板实例化时,需要加 template<>。如工程GlobalShare的UeObjectId.h里对ObjectId类哈希函数模板的实例化不规范,这是在VS .NET 2005里编译持久层时,提示使用CMap错误时发现的。 /*情况1:不规范的写法。"模板实例化"将不起作用,且编译器不报错!*/ AFX_INLINE UINT AFXAPI HashKey(UeObjectId key) { return key.HashCode(); } /*情况2:另一种不规范的写法,编译器会报错(比第一种稍好)*/ AFX_INLINE UINT AFXAPI HashKey(UeObjectId key) { return key.HashCode(); } /*情况3:规范的写法*/ template<> AFX_INLINE UINT AFXAPI HashKey(UeObjectId key) { // default identity hash - works for most primitive values //return (DWORD)(((DWORD_PTR)key)>>4); return key.HashCode(); } 另外在模板类(结构)中申明模板,就算不带新的模板参数,template<>不能省略。 如下面的代码段(自GlobalShare),在/*#1#*/处须加上"template<>",在/*#2#*/处须加上"typename"。 template struct ApplyImpl1 { template struct VC_WORKAROUND : public TypeWithNestedTemplate {}; /*#1#*/ struct VC_WORKAROUND { template struct In; }; template< typename T1 > struct Result : public VC_WORKAROUND< :oki:rivate::AlwaysFalse::value >::template In { typedef /*#2#*/ VC_WORKAROUND< :oki:rivate::AlwaysFalse::value >::template In Base; }; }; 3. const 问题 3.1 拷贝构造函数须声明为 XXX( const XXX& obj ) 3.2 声明这样的函数: void dosomething( const XXX*& ptr );在调用的地方VS .NET 2005报错。应该去掉 \'&\'。 还有 const 的一些其它不规范的地方,编译时未能记录下来。 4. 取函数地址问题 很多地方需要将函数或类的成员函数注册到容器里,注册取函数地址时,&不能省略。 如全局函数 void XXX( ... ),取其地址 &XXX。如果只写 XXX,VC 6.0和VS 2002都能通过编译,而VS 2005不能。 如果是类的成员函数,类似处理。如: class XXX { public: void DoSomething( ... ); } 在类XXX的某成员函数(通常是构造函数)取 DoSomething的地址: &XXX:oSomething(...)。 5. 定义函数指针类型时,不能带默认形参 如: typedef BaseNodeTransaction* (TransactionManager::* Fun)(BOOL bIsNew = TRUE );在VS .NET 2005中将指出错误。须将 " = TRUE "去掉。 6. C标准头文件与C++标准头文件 典型的如是C++为兼容C头文件方式而定义的头文件,它的C++版本是。在VS .NET 2005之后已只支持这种写法,写会提示找不到头文件。它里面声明的函数和类型都在名字空间 std 下。 7. 字符串的使用 字符串使用问题有两种。 7.1 程序中的运行时的提示信息没有放到资源中 软件如果发行多语言版本,将提示信息资源化是必须的。 7.2 程序中使用的字符类型或处理函数不能支持Unicode 如常量字符串"XXX"的形式要加 _T宏,应该使用TCHAR 类型系列替代char系列,应该使用支持 Ansi/Unicode的字符串处理宏函数。关于该问题的规定,见:http://system/dispbbs.asp?boardID=32&ID=2271&page=1 由于ARX 2007采用Unicode方式编译,和我们程序的编译时对字符串的处理方式不同,中间的转换过程在下面介绍。 8. 其它问题 8.1 函数未定义返回类型。在VS .NET 2005里已不再为其默认返回类型为 int,而是提示错误。 8.2 将对象赋值NULL。通常的形式是 vector::iterator it = NULL; 这种写法在 VS .NET 2005里不支持。 8.3 设备和管道事务层使用消息机制时,VS .NET 2002编译出来的arx库发生全局变量构造顺序冲突(VC200和5不发生)。 APDomainFrame\Inc\PDMSFrame.h中定义全局静态的常量字符串 和 注册消息时定义的全局驱动消息注册的对象发生冲突,加载这两个模块的Arx时,AutoCAD提示该Arx无效。暂时将这两个地方注册消息时改为字符串常量。 见到的最多的警告是:warning C4018: \'>=\' : signed/unsigned mismatch 除代码问题外,编译时发现一个编译器问题: 当用VS .NET 2002和2005编译时,所有的代码编译完毕没出现错误,之后编译资源时有时候会产生奇怪的错误: Generating Code... 正在编译资源... fatal error RC1047: too many -I# options, \'D:\PROJECT\AutoPDMS8.0\StartMoudle\APPDMSLogin\Inc\' 项目 : error PRJ0002 : 错误的结果 1 (从“D:\Microsoft Visual Studio.NET 2005\VC\bin\rc.exe”返回)。 上网仔细搜索了后才知道VS .NET 2002和2005的 C++ Include目录个数不能超过100个。我们AutoPDMS 2.0工程有95个以上,再加上编译器自身已有的一些包含文件,总数的确超过了100个。只需将已编译过的跟剩下项目不相关的包含目录去掉几个,需要时再加。问题就能解决。 三、编译ARX 2007时的字符串ANSI/UNICODE问题解决方案 ObjectARX 2007只提供Unicode的库和头文件。也就是说它的数据结构、字符串类型都只支持宽字节方式。当发现这个问题时,第一时间发表到RTX上征求意见和解决方法。大家讨论后得到两种方案:一是将我们的程序也编译成Unicode方式;一是我们程序仍用普通多字节方式编译,在涉及到ARX 2007接口的地方,做临时字符串转换。http://system/dispbbs.asp?boardID=52&ID=2234&page=1 方案一的优点是彻底,软件支持Unicode是迟早的。缺点是改动的地方较多,代码中很多地方还无法通过编译器选项,直接从普通多字节编译方式转换到宽字节方式编译。原因是上面的第7条。另外,就算能完成Unicode方式的编译工作,软件一些地方如PDMS数据导出、配置文件等地方可能会产生新的问题。总之,如果目前按这种方案实施,需要较长的时间的调整程序才能适应并稳定下来。 方案二的优点是不影响程序低版本ARX的编译,可以通过条件编译控制是否进行字符串转换。此外,凡是需要做转换的地方,编译时VS .NET 2005会一一指出(多字节字符<串>不能默认转换为多字节字符<串>),这为修改做了很好的提示作用,编译之前不需预先要了解那些地方使用了ARX的接口、数据结构,编译时逐个修改就行了。也正因为这样,和AutoCAD交互的部分也可能产生一些未知的问题,所以需要先完成转换,编译出一个版本做出安装包经测试没发现问题后才能将代码内字符串转换的修改检入到VSS。 就目前情况而言,方案二优于方案一。方案二实施起来有几个要注意的地方:字符串转换只能在使用了ARX 2007但程序又不是用Unicode方式编译的情况下。如果程序没使用ARX 2007或程序使用Unicode方式编译,则所有字符串转换的内容应该不再编译中出现。这可以用条件编译实现。在接受大家都建议后,最终将转换宏定为如下形式: #if defined ARX2007 && !defined _UNICODE//用了ARX2007或其以上版本,但程序不用UNICODE方式编译,做字符串转换 #define ConvertToAnsi(x) \ StringConvert::ConvertUnicodeToAnsi(x) #define ConvertToUnicode(x) \ StringConvert::ConvertAnsiToUnicode(x) #define ConvertReleaseMemA ConvertToAnsi(NULL) #define ConvertReleaseMemU ConvertToUnicode(NULL) #else//否则保持不变 #define ConvertToAnsi(x) x #define ConvertToUnicode(x) x #define ConvertReleaseMemA #define ConvertReleaseMemW #endif// #if defined ARX2007 && !defiend _UNICODE 其中转换函数 ConvertUnicodeToAnsi(x)和ConvertAnsiToUnicode(x) 的原型如下: namespace StringConvert { _declspec(dllexport) wchar_t* ConvertAnsiToUnicode( IN const char* ptrANSI ); _declspec(dllexport) char* ConvertUnicodeToAnsi( IN const wchar_t* pwUNICODE ); } 宏 ConvertToAnsi(x) 和 ConvertToUnicode(x)用于Ansi与Unicode方式字符串的相互转换。它们直接返回转换后的结果。 如函数acdb->getAt( strNeme, ... );在ARX2007下要求第一个参数为const wchar_t*类型,那么只需要做临时转换: acdb->getAt( ConvertToUnicode( strName ), ... ); 如果编译时没定义预处理宏ARX2007或使用Unicode方式编译,上述修改之后的代码将和修改之前的一样。 四、"字符串转换"的几种情况 字符串转换的实质是使目前的非Unicode方式编译的代码能使用ObjectARX 2007Unicode方式编译出的代码。有些地方只做字符串转换不够,如下面的3、4、5描述的情况。有些地方只修改我们的代码不够,还需要修改ARX 2007的头文件(如4)。 1. ARX接口函数[输入]类型的参数需要转换 如果只有一个参数需要转换,简单的使用宏 ConvertToUnicode(x)就行了。如果有多个参数要转换,可以像下面这样: #if defined ARX2007 && !defined _UNICODE wchar_t xx1[MAX_LEN]; wchar_t xx2[MAX_LEN]; ... wcscpy( xx1, ConvertToUnicode( param1 ) ); ... dosomething( xx1, .., xx2, ... ); //使用转换后的字符串做参数 #else dosomething( param1, .., param2, ... ); //保持原来的写法不变 #endif 2. ARX接口函数[返回值]或[用于返回的参数]里包含字符串 这种情况不能直接使用ConvertToAnsi(x)宏,而要在代码中视具体情况使用条件编译。 3. 派生自ARX里的导出类,基类的虚函数有参数或返回值类型是字符串 由于是重载虚函数,函数声明须一致,这种情况已经不能使用字符串转换了,ARX 2007导出的类成员的字符串类型参数返回值等都是宽字节,只能在程序中使用条件编译,在不同编译条件下产生不同的接口重载。 4. 使用或派生自AutoCAD提供的界面控件类,LPCREATESTRUCT结构的问题。 MFC提供的派生自CWnd的类,有很多统一的接口和数据结构。其中的一些会自动适应多字节方式和宽字符编译方式,如创建窗体用的结构LPCREATESTRUCT: #ifdef UNICODE typedef CREATESTRUCTW CREATESTRUCT; typedef LPCREATESTRUCTW LPCREATESTRUCT; #else typedef CREATESTRUCTA CREATESTRUCT; typedef LPCREATESTRUCTA LPCREATESTRUCT; #endif // UNICODE ARX 2007提供的界面控件类中,使用该结构时,都是采用如下形式: int CAcUiDockControlBar::OnCreate(LPCREATESTRUCT lpCreateStruct) 即ARX 2007的头文件里,声明接口时用的是LPCREATESTRUCT,如果采用Unicode方式编译,它会被解释成LPCREATESTRUCTW;如果采用多字节方式编译,它就是LPCREATESTRUCTA。ARX 2007采用Unicode方式编译的,对应的lib文件内导出的接口函数参数类型是LPCREATESTRUCTW。 我们程序如APArxInterface里有类UeDockControlBar派生自CAcUiDockControlBar,重载使用了类似LPCREATESTRUCT这样的能自适应字符串编译方式的MFC定义的数据结构的接口时,因我们不使用Unicode方式编译,又要适应以后的Unicode方式编译,所以需要对ARX 2007的头文件添加条件编译,且需要在我们代码里重载的地方做条件编译。 为了方便以后查找那些ARX的头文件被修改过,在修改过的头文件中都用#pragma message ("")在编译时做提示: used ARX2007 and unused UNICODE mode pragma project, some change has done in this file for ANSI->UNICODE convert 修改过的ARX 2007头文件有: [aduiBaseDialog.h] [aduidock.h] [acuiDialog.h] 5. ON_WM_CREATE()消息映射宏 该宏在MFC头文件 afxmsg_.h 中定义: // Message map tables for Windows messages #define ON_WM_CREATE() \ { WM_CREATE, 0, 0, 0, AfxSig_is, \ (AFX_PMSG) (AFX_PMSGW) \ (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) }, 从该定义中可以看到引起问题的还是LPCREATESTRUCT结构。 这个问题的代表是APArxInterface\Src\UeDockControlBar.cpp文件里的消息映射部分,编译时会产生如下错误提示: ..\Src\UeDockControlBar.cpp(27) : error C2440: \'static_cast\' : cannot convert from \'int (__thiscall UeDockControlBar::* )(LPCREATESTRUCTW)\' to \'int (__thiscall CWnd::* )(LPCREATESTRUCT)\' Cast from base to derived requires dynamic_cast or static_cast 使用条件编译定义新的消息映射宏 ON_WM_CREATE_ARX2007()替换该文件中的ON_WM_CREATE(): // UeDockControlBar #if defined ARX2007 && !defined _UNICODE #define ON_WM_CREATE_ARX2007() \ { WM_CREATE, 0, 0, 0, AfxSig_is, \ (AFX_PMSG) (AFX_PMSGW) \ (static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCTW) > ( &ThisClass :: OnCreate)) }, #else #define ON_WM_CREATE_ARX2007() ON_WN_CREATE() #endif 五、对"字符串转换"的总结 1. 因为我们的程序使用多字节方式编译,ObjectARX 2007使用宽字节方式编译,所以需要进行"字符串转换"。 2. 字符串转换只是临时的做法,它不影响软件的其它版本在普通多字节方式或Unicode方式的编译。 3. 只对ARX接口需要的地方做转换和条件编译,程序中已定义的变量类型和代码写法不改变。 4. "字符串转换"和让程序支持Unicode是不同的概念,否则就是方案1了。
|