WEBINAR: On-demand webcast
How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >
1. Who is the target reader of this article?
Recently I faced one very interesting task. I had to port an application from one platform (Windows) to another (Linux). It is an interesting topic. First, knowledge of several platforms and writing the code for them is a good experience for every developer. Secondly, writing an application for different platforms makes it widespread and needed by many. So, I would like to share my impressions concerning this process. This article is intended for everybody who wants to write a cross-platform application.
2. Our task
Receiving the project specification, we usually see only one target platform in the "Platforms" section (e.g., Windows) and that is why we enjoy its advantages and disadvantages. Let's imagine that you receive the task where it is proposed to run the application on the other platform (e.g., Linux). Or imagine that you have to use the code, which was written for one platform, on another platform. From this point you start to face difficulties. You start to plan the porting taking into account all the specifics of the program architecture. And if the architecture was wrong from the very beginning (I mean that it did not expect the porting between the platforms), it can turn out that you have to remake a lot. Let's examine the example of the code (file attached to this article). B This program opens the PhysicalDrive0, acquires MBR and writes it to the file, then defines the disk configuration and saves it to a separate file. The code was written only for Windows with all its consequences.
There must be no difficulties in such small example. But you can meet problems even here. The code is very simple and does not require many checks, deletion processing, etc. This code is not a standard but it lets to show what you should do during the porting of your application from Windows OS to Linux OS.
3. Compilers and IDE
When porting from Windows OS to Linux OS is performed, the porting from Microsoft Visual C++ to GCC(G++) is the most commonly used. Many of you may think that GCC and G++ are two different compilers. But it is not so. GCC (B+GNU C CompilerB;) was created in 1987 by Richard Stallman and it could compile only C code at that time. With time the compiler developed and supported not only C and C++ codes but also other programming languages. Now the GCC is interpreted as B+GNU Compiler CollectionB;. G++ is a part of GCC and is used to compile *.cpp files. GCC is used, in its turn, to compile *.Q files. Though, you can compile the *.cpp file using GCC by indicating specific flags. GCC and G++ compile the C++ code in the same way.
Let's return to the porting from Visual C++ to GCC (G++). It is worth paying attention to the difference between them. GCC is stricter to the standard than the Microsoft compiler. It means that in some situation the GCC compiler returns an error message and does not compile the source code while the Microsoft compiler just returns the warning message. Let's examine some moments that are frequently met during the porting from Visual C++ to GCC. You can google it but I would like to repeat it myself.
- Use #ifndef/#define/#endif instead of #pragma once. GCC understands the #pragma once directive beginning from the version 3.4. That is why check the version of your compiler. If it is lower than 3.4, there is no need to correct the following code during the porting.
#ifndef SOME_HEADER_H #define SOME_HEADER_H // code. #endif // SOME_HEADER_H
- Used types. During the porting, watch the types you use because GCC doesn't understand all of them. It is better to create a separate file in the project (e.g., types.h file) and to put there all types that GCC does not understand.
HANDLE //typedef void * HANDLE; in winnt.h DWORD //typedef unsigned long DWORD; in WinDef.h BYTE //typedef unsigned char BYTE; in WinDef.h UINT //typedef unsigned int UINT; in WinDef.h
- Assembler insertions. Be careful during the porting of ASM insertions to the project for GCC. They have another appearance and syntax. GCC uses AT&T ASM. It differs from Intel ASM, which is used in Visual C++. An example is provided below (the following example is taken from http://asm.sourceforge.net//articles/linasm.html; for more information about AT&T ASM also see this reference).
movB B al,bl
B B B B B B B B B
P.S. For conversion, you can use the Intel2gas utility (see http://www.niksula.hut.fi/~mtiihone/intel2gas/). But to my opinion, it is more convenient only for the high volume of the ASM code. And anyway you should bring the code to the compilable state. B
- Using the #pragma comment directive (lib, "libname.lib"). It is convenient to hook libraries in the project with the help of this directive in Visual C++. But GCC does not contain such one. That is why, for GCC, you should define the libraries you want to hook in the command line. For example:
g++ -o TestExe.exe *.o -Llib -lm -ldl -w "lib_1.a" "lib_2.a" "lib_3.a"
As you can see, Linux libraries have *.a extension and not *.lib extension as in Windows OS. Dynamic libraries also have another extension (*.so instead of *.dll) and they link not in such way as in Windows OS. For more information about the types of libraries in Linux OS see http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html.
- Macros. Macros in GCC differ from the analogous ones in Visual C++. During the code porting to GP!P!, check the macros in the code. It's possible that the implementation of such macro will not be performed. In such situation, it is better to find its implementation in Visual P!++ and port it to GCC. The following table provides examples of frequently used macros.
__DATE__ __DATE__ __FILE__ __FILE__ __LINE__ __LINE__ __STDC__ __STDC__ __TIME__ __TIME__ __TIMESTAMP__ __TIMESTAMP__ __FUNCTION__ __FUNCTION__ __PRETTY_FUNCTION__ MSC_VER __GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__
Let's examine them. The first six macros are implemented in both compilers in the same way and are often used in logging. The differences begin from __FUNCTION__. In Windows OS, it writes not only the name of the function (as in Linux OS) but also the namespace and the class from where the call was performed. Its analog in Linux OS is not __FUNCTION__ but __PRETTY_FUNCTION__. MSC_VER and __GNUC__ / __GNUC_MINOR__ / __GNUC_PATCHLEVEL__ also have different types of returned data. For example, MSC_VER = 1500 for Visual C++, for GNU 3.2.2 it is __GNUC__ = 3, __GNUC_MINOR__ = 2, __GNUC_PATCHLEVEL__ = 2.
If the target platform does not include the needed macro, find its implementation and port it. Macro can be also written in ASM (for the code optimization). Do not hurry and rewrite it from Intel to AT&T and vice versa. It is better to check once more if there is its implementation in P!++. Also take into account that Linux OS can run on devices with ARM processor. In this case the macro, which was written under AT&T, will not work and you will have to implement it under another type of the processor.
Let's move to IDE. There are lots of them and you have the possibility to choose. Let's examine some of them (taking into account that the project was initially written in Visual Studio, our task is to choose IDE for working in Linux OS):
- Code::Blocks (see http://www.codeblocks.org/)
Pic.1 Code::Blocks. Start Page.
It is often proposed as the substitution of Visual Studio for those who port their project to Linux OS. IDE can open Visual Studio projects under Windows OS and Linux OS. I built a project with its help and I can say that it is very convenient.