We maintain a library of filters and effects written in C++ that runs cross-platform on Windows, iOS and Android. Compile with Visual Studio 2013 or 2017 for Windows, Clang with Xcode for iOS, and GCC for Android.
Regardless of the platform differences between iOS and Android, clang and GCC behave very similar from a C++ perspective, with one compiling and running fine on mobile and the other basically fine. But the VS compiler behaves quite differently. This article compares some of the differences between the VS compiler and the CLang compiler, and notes some of the pitfalls we’ve stepped on.
1. Character encoding
In my development environment, the clang encoding defaults to UTF8 and VS to GB2312 (code page is 936), both of which are ASCII compatible.
If only English appears in the code file, both sides can compile. If the code appears in Chinese, the file encoding is UTF8, iOS compilation is ok, VS will generate a compilation error C2001. Encoding is not supported (utf16) Therefore, if the code is in Chinese, you need to change the source encoding to Unicode(UTF8 with signature) – code page 65001.
See VS compilation error C2001: Constant with line breaks Chinese cannot compile
In addition, if the string contains Chinese characters, read directly, the program is easy to run garbled. Something like this:
Const char* STR = "Hello, world ";Copy the code
Trying to render STR text correctly on different platforms is out of control. Cross-platform code should not use Chinese, never define strings in Chinese and then read them, or even write comments in Chinese. If you want to display Chinese strings, you should separate them from the program, write them in a UTF8-encoded configuration file, and then read them dynamically.
We stumbled. We meant that when we exported a Lua Api, we would automatically generate the corresponding documentation. There is code like this:
ADD_METHOD_WITH_DOC(Context, nv12ToRGBPass, "Get color space nv12 to RGB shader Program ", "3.2", "[Program](# Program)"," Program ", 0)Copy the code
It was later discovered that the compilation was successful on Windows, but not on iOS. After changing the encoding, it compiles on iOS but not on Windows. When both ends are compiled, but the code is garbled. Finally, we had to write them all in English, automatically generating English documents.
2. Int8_t and char
On VS, int8_t is actually a typedef for char, which means that int8_t and char are of the same type. But on iOS, int8_t and char are different types. The following code
printf("%d\n", (int)std::is_same<int8_t, char>::value);Copy the code
Output 1 on VS and 0 on iOS. This refreshes my knowledge that I always thought char and int8_t were the same. Because of this difference, I stepped on the pit again.
To make it easy to write lua exports, we use the LuaCpp library, which contains this code
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;Copy the code
Here we define a Typelist for Loki that contains types that support automatic conversions. Typelist see book C++ design new thinking.
LuaCpp is basically template code. If type T is SupportType, automatic conversion code can be executed, otherwise handwritten conversion is required. If there is no handwritten conversion, for type T, it will crash.
The code here is very old and has always worked. Until one of the interfaces appears int8_T, so VS works fine and iOS crashes.
Similarly, int8_t, uint8_t, int16_t, uint16_t also need to be noted.
For similar template code, it’s best to obediently use STD :: IS_Integral from the standard library. Don’t be smart enough to write a typelist by hand.
3. __VA_ARGS__
__VA_ARGS__ can be used for macros with indefinite arguments. But its behavior is different in VS and Clang. As shown below
#include <iostream>
#define MY_PRINT(format, ...) printf(format, __VA_ARGS__)
int main(int argc, const char* argv[])
{
MY_PRINT("Hello, World");
return 0;
}Copy the code
It compiles on VS. On Clang, the compiler’s __VA_ARGS__ cannot expand zero variable-length arguments. written
MY_PRINT("Hello, World, %d", 1);Copy the code
So you can expand it correctly. To expand 0 arguments, write ##__VA_ARGS__, defined as
#define MY_PRINT(format, ...) printf(format, ##__VA_ARGS__)Copy the code
See Variadic Macros with zero arguments
4. Static variables across DLL modules
A project often has multiple dynamic modules. On VS, dynamic modules are DLL files; Framework or Dylib on iOS. Default symbols are not exported when VS is used across modules. Clang default symbols are all exported.
On VS, when you want to define A class or A function in module A for use by module B, you need to use __declspec(dllexport) and __declspec(dllimport). Macros are usually defined, such as.
#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
#endifCopy the code
Then write functions or classes that need to be used across modules
class MODULE_A_API TestClass {
};
MODULE_A_API void myfunction(int a, int b);Copy the code
There is usually no problem, and if you forget to write the export, you will get a link error. But when it comes to templates and static variables, this platform difference can be a pit.
Template code is usually written directly in header files, such as the following code.
// myheader.h
template <typename T>
class TemplateClass {
public:
static std::string str;
};
template <typename T>
std::string TemplateClass<T>::str;Copy the code
In your module, you can use TemplateClass if you include the header file myheader.h. Let’s say module A sets the value of STR with A statement
TemplateClass<int>::str = "Hello, World";Copy the code
Module B then reads the value of STR.
std::string str = TemplateClass<int>::str;Copy the code
In VS, module A and module B both use TemplateClass<int>, but because they are not exported, they are actually two separate classes, and their static variables are not shared. Module A sets TemplateClass<int>:: STR, and module B reads the default null value.
In the CLang compiler, exports are done by default. So module A and module B see the same TemplateClass<int> and the static variables are shared. Module A sets TemplateClass<int>:: STR, module B reads “Hello, World”
This Bug is subtle and can compile and run properly, but the actual result is incorrect. We’ve been in pits like this before.
As mentioned earlier, we used the LuaCpp library to export Lua. This library is a template library that contains static variables for automatic registration. We registered A batch of Lua classes in module A. After that, the class object registered with lua virtual machine in module B runs normally on iOS, but is abnormal on Windows. As far as module B is concerned, these classes are not registered at all in LuaCpp’s records.
The conclusion is, do not export static variables directly across modules. Also try not to include static variables in your template code, because you don’t know when the template code will be contained by two different modules, and these static variables can become problematic.