这篇文章主要讲解了“C++跨平台开发遇到的问题有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++跨平台开发遇到的问题有哪些”吧!
在我的开发环境,clang 编码默认是 utf8, VS 是 GB2312(代码页是 936),它们都兼容 ASCII。
假如代码文件中只出现英文,两端都可编译。假如代码中出现中文,文件编码为 utf8, iOS 编译没有问题,VS 会出现编译错误 error C2001。假如设置编码为 utf16, VS 编译没有问题,而 iOS 会出现编译错误 encoding is not supported。因此假如代码有中文,需要将源文件编码修改为 Unicode(UTF8 带签名)- 代码页 65001。
参见 vs编译 error C2001: 常量中有换行符 中文无法通过编译
另外假如包含中文字符串,直接读取使用,程序运行起来很容易出现乱码。类似这样的代码:
const char* str = "你好啊,世界";
想将 str 的文字在不同平台都显示正确,是不可控的。跨平台代码不应该使用中文,绝对不能用中文定义字符串再读取,更严格的甚至不能用中文写注释。假如要显示中文字符串,应该将其从程序中分离出来,写在一个 utf8 编码的配置文件中,再动态读取。
我们就碰坑了,我们本意是在导出一个 lua Api 的时候,自动生成对应的文档。有类似这样的代码:
ADD_METHOD_WITH_DOC(Context,nv12ToRGBPass, "获取颜色空间 nv12 到 rgb 的着色器程序","3.2","[Program](#program)", "program",0)
后来发觉在 Windows 上编译通过,iOS 编译不过。修改编码后, iOS 编译过了,Windows 上又编译不过。当两端都编译过了,但又乱码了。最后只好都写成英文,自动生成英文文档。
在 VS 上,int8_t
实际上是 char 的 typedef,也就是说 int8_t
和 char 是同类型的。但是在 iOS 上,int8_t 和 char 是不同类型的。下列代码
printf("%d\n", (int)std::is_same<int8_t, char>::value);
在 VS 上输出 1,在 iOS 上输出 0。这刷新我认知,我一直以为 char 和 int8_t 是相同的。因为这区别,又踩坑了。
为了方便写 lua 导出,我们用了 LuaCpp 的库,里面有这代码
typedef LOKI_TYPELIST_15(bool, char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, float, double,std::string, luaObject, luatable, int64_t) SupportType;
这里定义了一个 Loki 的 typelist, 包含支持自动转换的类型。typelist 参见书籍C++设计新思维。
LuaCpp 基本都是模板代码,假如类型 T 属于 SupportType,就可执行自动转换的代码,不然就需要手写转换,假如没有手写转换,对于此类型 T, 就会直接崩掉。
这里的代码很老了,一直都运行正常。直到某个接口出现了 int8_t,于是 VS 上运行正常,iOS 上崩溃了。
同理,int8_t, uint8_t, int16_t, uint16_t 也需要注意。
类似的模板代码,最好还是乖乖地使用标准库中的 std::is_integral 吧。不要那么聪明自己手写 typelist 了。
__VA_ARGS__ 可用于不定参数的宏。但是它的行为在 VS 和 clang 上是有区别的。如下面代码
#include <iostream>#define MY_PRINT(format, ...) printf(format, __VA_ARGS__)int main(int argc, const char* argv[]){MY_PRINT("Hello, World");return 0;}
在 VS 上可以编译通过。但在 clang 上确实编译失败,clang 编译器的 __VA_ARGS__ 不能展开 0 个变长参数的。写成
MY_PRINT("Hello, World, %d", 1);
才可以正确展开。为了展开 0 个参数,需要写成 ##__VA_ARGS__, 定义为
#define MY_PRINT(format, ...) printf(format, ##__VA_ARGS__)
参考 Variadic macros with zero arguments
一个工程经常有多个动态模块。在 VS 上,动态模块为 dll 文件; iOS 上为 framework 或者 dylib。VS 在跨模块时,默认符号是不导出的。clang 默认符号都是导出的。
在 VS 上,当想在 A 模块中定义某个类或者某个函数,让 B 模块使用,就需要使用 __declspec(dllexport)
、__declspec(dllimport)
标明。通常会定义一些宏,比如。
#if defined(OF_WIN32) || defined(_WIN32) || defined(WIN32)# ifdef MODULE_A_API_LIB# define MODULE_A_API __declspec(dllexport)# else# define MODULE_A_API __declspec(dllimport)# endif#else# define MODULE_A_API#endif
之后需要跨模块使用的函数或者类写成
class MODULE_A_API TestClass {};MODULE_A_API void myfunction(int a, int b);
通常都没有问题,假如忘记写导出,就会链接错误。但一旦涉及到模板和静态变量,这种平台的差别,就会是个坑。
模板代码通常会直接写在头文件中,比如下代码。
// myheader.htemplate <typename T>class TemplateClass {public:static std::string str;};template <typename T>std::string TemplateClass<T>::str;
在模块中,包含了头文件 myheader.h,就可以使用 TemplateClass 了。假如这时模块 A 使用语句设置 str 的值
TemplateClass<int>::str = "Hello, World";
之后模块 B 读取 str 的值。
std::string str = TemplateClass<int>::str;
在 VS 中,模块 A 和模块 B 虽然都使用 TemplateClass<int>,但因为没有导出,实际是分离的两个类,他们的静态变量并不会共享。于是就是模块 A 设置了 TemplateClass<int>::str,模块 B 读取的还是默认的空值。
而在 clang 编译器中,默认是导出的。于是模块 A 和模块 B 看到的是相同的 TemplateClass<int>,静态变量是共享的。于是模块 A 设置了 TemplateClass<int>::str,模块 B 读取的是设置后的 "Hello, World"
这种 Bug 比较隐蔽,可以正常编译,也可以运行,但实际结果就是不对。我们就踩过类似的坑。
前文说过,我们使用了 LuaCpp 这个库来导出 lua。这个库是个模板库,它包含一些静态变量,用来实现自动注册。我们在模块 A 中注册了一批 lua 类。之后在模块 B 中往 lua 虚拟机压注册过的类对象,在 iOS 上运行正常,但在 Windows 上就异常。因为在模块 B 中看来,LuaCpp 的记录中,这些类根本就没有被注册过。
感谢各位的阅读,以上就是“C++跨平台开发遇到的问题有哪些”的内容了,经过本文的学习后,相信大家对C++跨平台开发遇到的问题有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。