Logo
blank Skip to main content

Windows2Linux Porting

C++

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.

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).  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.

Please look at the code before reading the article (BackupMBR_win.zip attached to this article). This is the project of VisualStudio2008.

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.

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 (ยซGNU C Compilerยป) 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 ยซGNU Compiler Collectionยป. G++ is a part of GCC and is used to compile *.cpp files. GCC is used, in its turn, to compile *.ั 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.

  1. 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.
C
#ifndef SOME_HEADER_H
  #define SOME_HEADER_H
  // code.
  
  #endif // SOME_HEADER_H 
  1. 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.
C
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
  1. 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).
Intel Syntax

AT&T Syntax

mov   al,bl
mov   ax,bx
mov   eax,ebx
mov   eax, dword ptr [ebx]

movb %bl,%al
movw %bx,%ax
movl  %ebx,%eax
movl  (%ebx),%eax

P.S. For conversion, you can use the Intel2gas utility. 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.

  1. 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:
ShellScript
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.

You can learn more about how to hook DLL function in Windows from another our article.

  1. Macros. Macros in GCC differ from the analogous ones in Visual C++. During the code porting to Gะกะก, 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 ะก++ and port it to GCC. The following table provides examples of frequently used macros.
ShellScript
 __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 ะก++. 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/)

code_blocks

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.

– Eclipse/CDT (see http://www.eclipse.org/cdt/downloads.php).

eclipse

Pic.2 Eclipse/CDT.

It is another free IDE. If you write in C++, download it together with the CDT plug-in. After installation IDE supports only Java by default and if you install CDT plug-in after, the auto-add-on probably will not work.

– Qt Creator (see http://qt.nokia.com/products/developer-tools).

qt_cretor

Pic.3 QT Creator. Start Page

When I first worked with Linux I used this IDE. Its interface is simple (developers must have followed the example of XCode interface during the development of Qt Creator). And you do not have to use Qt while working in Qt Creator. Just configure the project using the *.pro file (in it, libraries that are statically linked, *.h and *.cpp files, the type of the application are defined).

– Emacs/Vim (see http://www.gnu.org/software/emacs/)

emacs

Pic.4 Emacs. Start Page

This IDE is for experienced developers under Linux OS. The problem of this IDE is that it can take long to learn how to work with Emacs. And the skills are quickly lost, as people say. You will be able to perform the majority of commands by using hot keys or the Emacs command line. Even while opening the file you will be asked to define the file path in the command line. But on the other hand, this tool is very powerful and flexible.

I introduced four IDEs that are the most popular among the developers under Linux OS. For example, I work with QtCreator but there must be no problems while working with other IDEs (do not forget about problems with Emacs). So, it is your time to choose.

I want to add a few words about the debuggers. Sometimes I did not manage to debug the code using the native IDE debugger. That is why I had to search for the alternative. I used two debuggers: Insight and kdbg. I also tried to use ddd debugger but I did not like it. So use the one you like more. With time you will find the one that will meet the maximum of your demands.

Porting of the application

Letโ€™s return to the code we examined in the second part. As we can see in the API code, functions are used directly without the wrappers above the API level. You may say that it is not bad: errors are handled, the code works, so why not to leave it as it is? There is a reason not to leave as it is. When you port the application to another platform, there will be one more problem of searching API functions in the code and their replacement. For example, the CreateFileW function (disk opening and creation of the file from the MBR disk) is represented twice in the examined code. And the program is simple in itself. If the project is big and you do not use wrappers above the API level, the code will repeat itself a lot. And it means that you will have to change a lot during the porting to another platform.

That is why the first step is to write the library that contains all used APIs and that implements the methods of working with them. It must also contain all functions specific for the definite platform. Such library will have the necessary interface, and during the porting, you will have to make some corrections only in it.

Letโ€™s examine the schemes that illustrate the thoughts mentioned above. The majority of developers extract the core of their application into the separate libraries (Business Logic) that implement the definite interface. Such library also implements the separate GUI that will use this very library. This is correct, convenient and also it works. But there is one nuance that we just mentioned.

platforms_compability_layer

There must be the Platform Compatibility Layer for the cross-platform applications. This is the component that will stand between the API level and the core of your application. It is necessary to include the following components in it:

  • working with files;
  • working with sockets;
  • working with ports;
  • ASCII/Unicode
  • Threading API

Letโ€™s start from the very beginning. Letโ€™s try to port our application. We must define the code that will be included in the “Platform Compatibility Layer”. The following table displays the comparison between Windows and Linux API.

Table 1. Comparing Linux and Windows API.

Windows API

Linux API

    HANDLE WINAPI CreateFileA(
__in LPCTSTR lpFileName,
__in DWORD dwDesiredAccess,
__in DWORD dwShareMode,
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
__in DWORD dwCreationDisposition,
__in DWORD dwFlagsAndAttributes,
__in_opt HANDLE hTemplateFile)
    int  open(
const char* name,
int flags)

int open(
const char* name,
int flags,
mode_t mode)
    BOOL WINAPI ReadFile( 
__in HANDLE hFile,
__out LPVOID lpBuffer,
__in DWORD nNumberOfBytesToRead,
__out_opt LPDWORD lpNumberOfBytesRead,
__inout_opt LPOVERLAPPED lpOverlapped)

    ssize_t read(
int fd,
void buf*,
size_t len)

    BOOL WINAPI WriteFile( 
__in HANDLE hFile,
__in LPCVOID lpBuffer,
__in DWORD nNumberOfBytesToWrite,
__out_opt LPDWORD lpNumberOfBytesWritten,
__inout_opt LPOVERLAPPED lpOverlapped)
    ssize_t write(
int fd,
void buf*,
size_t count)
    DWORD SetFilePointer(
__in HANDLE hFile,
__in LONG lDistanceToMove,
__inout_opt PLONG lpDistanceToMoveHigh,
__in DWORD dwMoveMethod)
    off_t lseek(
int fd,
off_t pos,
int origin)
    BOOL CloseHandle(
__in HANDLE hObject)
int  close(
int fd)

The first function that we use is the CreateFileA function. We need it to open the device (in this case, it is PhysycalDrive0). We call it with the following parameters:

C
/BackupMBR.cpp
// MBRBackup_win
HANDLE handle_r = ::CreateFileA("\.PhysicalDrive0"
                        , GENERIC_READ 
                        , FILE_SHARE_READ | FILE_SHARE_WRITE
                        , 0
                        , OPEN_EXISTING
                        , FILE_ATTRIBUTE_NORMAL
                    , 0);

With the help of the first parameter we define the path of the device we are opening \.PhysicalDrive0. In Linux OS, all devices have the format of simple files and are located in the /dev directory. There you can find the required device (it is /dev/sda device in my system). You can define the path to the device on your computer by entering the mount command in Linux terminal. This command is used for device mounting. But such command without any parameters will display all mounted devices in the system. The following example is the example of mount command for my system:

ShellScript
root@ubuntu:/usr/share/man$ mount
    /dev/sda1 on / type ext3 (rw)
    tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)
    proc on /proc type proc (rw,noexec,nosuid,nodev)
    ..........................................................................
    securityfs on /sys/kernel/security type securityfs (rw)

Letโ€™s examine the first line, where:

/dev/sda1       – the path to the device file
/                       – the path of the mounted filesystem
type ext3        – the filesystem type
rw                    – read and write permissions.

Hard drives are usually indicated as /dev/sd* and /dev/hd* in Linux OS. If the computer has three hard drives, they will be indicated as /dev/sda, /dev/sdb, /dev/sdc. The number that follows after the name of the hard drive defines the partition number on this drive. To open the disk, use the open() function.

According to the manual, this function juxtaposes the full file path with the descriptor of the file, which it returns. Descriptor is the analog of HANDLE in Windows OS. It can be used for read/write/lseek functions. If you did not manage to open/create a file, the function returns -1. Permission of file access is defined in the flags parameter. For /dev/sda, set the O_RDONLY flag (it is an analog of GENERIC_READ for CreateFileA). For more information about flags see the manual. So, for Linux OS, the open() function will look like the following:

C++
//main.cpp in BackupMBR_linux.rar
//
//   int handle_r = ::open("/dev/sda", O_RDONLY);

It is more compact than in Windows OS. Now it is necessary to write the function that will be common for Windows and Linux OS. First, letโ€™s define how this function will look like. CreateFileA can be assumed as a basis. There are three parameters out of 7 that do not change in the program:

ShellScript
- DWORD dwShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
- LPSECURITY_ATTRIBUTES  lpSecurityAttributes = 0
- HANDLE hTemplateFile = 0

As these parameters have the same value, we exclude them from the target function. You may say that we limit the functionality defining one and the same value to some parameters. But we did not change them in the program, those values, which are defined, are enough for us. It means that the function performs the program minimum and can cover the functionality that we need. It is worth defining here how we use one or another API function. The minimum functionality is usually enough. But on the other hand, if we want to set the FILE_SHARE_READ value to the dwSharedMode parameter, we will hit a problem. It is not enough that we must add changes to the function, but also we will have to change the number of parameters in all its calls. That is why it is necessary to define what exactly you want, for example, from CreateFileA and how you will use it. Then it will be clear how the wrapper over this function will look like.

So, we came to conclusion that the function will have 4 parameters. Now we have to confirm them on both platforms. I will port the minimum of them: GENERIC_READ (to open the file for reading) and GENERIC_WRITE (to open the file for writing). These parameters are similar to O_RDONLY and O_WRONLY parameters for the open() function. dwCreationDisposition defines the way of opening the file. I used two values: OPEN_EXISTING (the file opens; if there is no file, the error is returned) and CREATE_ALWAYS (a new file is created; if such file already exists, it is rewritten). There are no such parameters for the open() function. The closest will be the O_CREAT parameter. This flag is used for file creation and corresponds to CREATE_ALWAYS parameter. You donโ€™t need to use the OPEN_EXISTING value for the open() function. You may say that the FILE_ATTRIBUTE_NORMAL value of the dwFlagsAndAttributes also did not change and that we have to exclude it from the parameters. But as it was mentioned earlier, the open() function has one more mode parameter. It defines the file permission, as you remember. Letโ€™s define the FILE_ATTRIBUTE_NORMAL = 0644 parameter (the owner of the file has read and write permissions, others only read permission). It is not right as we cannot define other file permissions but it is enough for porting our application. It is a certain support point. But it is easy to correct: we just find the value correspondence for the necessary parameters for both platforms.

We compared all parameters that are interesting for us and now can start writing the function itself. What is the process of writing? We use the #ifndef/#define/#elif/#endif preprocessor directives. We use the following construction:

C
#ifdef WIN32
    // do something for Windows
#elif __linux__
    //do something for Linux
#else 
    #error: unknown OS
#endif

By using this construction, we can single out parts of the code that are specific for a definite platform. With the help of this method you can single out not only the parts of functions but also the whole functions.

int do_something()
{
#ifdef WIN32
// specific Windows parts
#elif __linux__
//specific Linux parts
#else
#error: unknown OS
#endif
}
#ifdef WIN32
int do_something()
{
// specific Windows do_something
}
#elif __linux__
int do_something()
{
//specific Linux do_something
}
#else
#error: unknown OS
#endif

As we can see, both methods prove its value. The first one is less readable; the second one contains more code but it is easier to make changes in it. I would advise you to use the second one but it is your right to choose. As an alternative, you can separate off all to the  header files.

Letโ€™s look how the function will look like after making all changes in it:

C
// file_io.cpp
// MBRBackup_port_win
#include "file_io.h"
#ifdef WIN32
    // nothing to do
#elif __linux__
 #include <sys/types.h>
    #include <sys/stat.h>
    #include "unistd.h"
    #include <fcntl.h>
    #define GENERIC_READ                O_RDONLY    //read only mode
    #define GENERIC_WRITE               O_WRONLY    //write only mode
    #define CREATE_ALWAYS               O_CREAT        //create new file
    #define OPEN_EXISTING               0           //fake parameter's value
 #define FILE_ATTRIBUTE_NORMAL     0644          // file attributes
#else
    #error: unknown OS
#endif
#ifdef WIN32 // create_io_file for windows
HANDLE create_io_file(const char* file_name,
           DWORD desired_access,
           DWORD creation_despositions,
           DWORD flags_attributes)
{
        return ::CreateFileA(file_name,
                             desired_access,
                             FILE_SHARE_READ | FILE_SHARE_WRITE,
                             0,
                                creation_despositions,
                                flags_attributes,
                                0);
}
#elif __linux__ // create_io_file for Linux
HANDLE create_io_file(const char* file_name,
                      DWORD desired_access,
                      DWORD creation_despositions,
                      DWORD flags_attributes)
{
    return ::open(file_name,
                  desired_access | creation_despositions,
                  flags_attributes);
}
#else
    #error: unknown OS
#endif

As it can be seen from the code, we took into account everything mentioned above. The received create_io_file wrapper does not implement all the features of CreateFileA and open but it is an example of how you can implement the cross-platform wrapper above the API level. You can port the rest of functions in the same way (see file_io.h/file_io.cpp file).

Besides API, we used DeviceIOControl to receive the disk geometry. The get_drive_info() function is represented below:

C++
// utils.cpp 
// MBRBackup_win
bool get_drive_info(HANDLE _handle, drive_info โˆ“drive_info_)
{
    DWORD size_ = 0;
    BOOL is_accessible_ =
        ::DeviceIoControl(_handle
        , IOCTL_STORAGE_CHECK_VERIFY
        , 0
        , 0
        , 0
        , 0
        , &size_
        , 0);
    if (!is_accessible_)
    {
        return false;
    }
    DISK_GEOMETRY disk_geometry_;
    ::memset(&disk_geometry_, 0, sizeof disk_geometry_);
    BOOL is_geometry_ =
        ::DeviceIoControl(_handle
        , IOCTL_DISK_GET_DRIVE_GEOMETRY
        , 0
        , 0
        , &disk_geometry_
        , sizeof disk_geometry_
        , &size_
        , 0);
    if (!is_geometry_)
    {
        return false;
    }
    drive_info_._cylinder = disk_geometry_.Cylinders.QuadPart;
    drive_info_._type = fixed;
    drive_info_._tracks_per_cylinder = disk_geometry_.TracksPerCylinder;
    drive_info_._sectors_per_track = disk_geometry_.SectorsPerTrack;
    drive_info_._bytes_per_sector = disk_geometry_.BytesPerSector;
        drive_info_._lba_size = drive_info_._cylinder
           * drive_info_._bytes_per_sector
           * drive_info_._sectors_per_track
           * drive_info_._tracks_per_cylinder;
       return true;
}

There is an analog of DeviceIOControl in Linux. It is the ioctl function, which receives the device descriptor and can return the requested information about it. By calling DeviceIOControl with the IOCTL_DISK_GET_DRIVE_GEOMETRY parameter we receive the DISK_GEOMETRY structure as a result. It looks like this:

C++
typedef struct _DISK_GEOMETRY {
    LARGE_INTEGER Cylinders;
    MEDIA_TYPE MediaType;
    DWORD TracksPerCylinder;
    DWORD SectorsPerTrack;
    DWORD BytesPerSector;
} DISK_GEOMETRY, *PDISK_GEOMETRY;

To receive the similar structure with the disk geometry in Linux, call the ioctl function with the HDIO_GETGEO parameter and reference the following structure:

C++
struct hd_geometry {
      unsigned char heads;
      unsigned char sectors;
      unsigned short cylinders;
      unsigned long start;
};

Having compared the structures, we notice the absence of one of the fields. The hd_geometry structure does not contain the BytesPerSector value. This can be corrected by calling the ioctl function with the BLKSSZGET parameter, which according to Linux sources returns the block device sector size. These are the specifics of porting this function. The final variant looks like the following:

C++
// utils.cpp
// MBRBackup_port_win
#ifdef WIN32
bool get_drive_info(HANDLE _handle, drive_info &drive_info_)
{
    DWORD size_ = 0;
    BOOL is_accessible_ =
        ::DeviceIoControl(_handle
        , IOCTL_STORAGE_CHECK_VERIFY
        , 0
        , 0
        , 0
        , 0
        , &size_
        , 0);
    if (!is_accessible_)
    {
        return false;
    }
    DISK_GEOMETRY disk_geometry_;
    ::memset(&disk_geometry_, 0, sizeof disk_geometry_);
    BOOL is_geometry_ =
        ::DeviceIoControl(_handle
        , IOCTL_DISK_GET_DRIVE_GEOMETRY
        , 0
        , 0
        , &disk_geometry_
        , sizeof disk_geometry_
        , &size_
        , 0);
    if (!is_geometry_)
    {
        return false;
    }
    drive_info_._cylinder = disk_geometry_.Cylinders.QuadPart;
    drive_info_._type = fixed;
    drive_info_._tracks_per_cylinder = disk_geometry_.TracksPerCylinder;
    drive_info_._sectors_per_track = disk_geometry_.SectorsPerTrack;
    drive_info_._bytes_per_sector = disk_geometry_.BytesPerSector;
    drive_info_._lba_size = drive_info_._cylinder
        * drive_info_._bytes_per_sector
        * drive_info_._sectors_per_track
        * drive_info_._tracks_per_cylinder;
    return true;
}
#elif __linux__
bool get_drive_info(HANDLE _handle, drive_info &drive_info_)
{
    struct hd_geometry geometry;
    ::memset(&geometry, 0, sizeof geometry);
    int result = ioctl(_handle, HDIO_GETGEO, &geometry);
    if (result < 0)
    {
        std::cout<<"ioctl error";
        return false;
    }
    drive_info_._cylinder = geometry.cylinders;
    drive_info_._type = fixed;
    drive_info_._tracks_per_cylinder = geometry.heads;
    drive_info_._sectors_per_track = geometry.sectors;
    long sector_size = 0;
    result = ioctl(_handle, BLKSSZGET, &sector_size); 
    if (result < 0)
    {
        std::cout<<"ioctl error";
        return false;
    }
    drive_info_._bytes_per_sector = sector_size;
    drive_info_._lba_size = drive_info_._cylinder
        * drive_info_._bytes_per_sector
        * drive_info_._sectors_per_track
        * drive_info_._tracks_per_cylinder;
    return true;
}
#else
    #error: unknown OS
#endif

There is one more function left. It is the store_drive_geometry function but it does not require the change of the code. It can be easily ported. Such effect can be achieved by using STL (see http://cplusplus.com), boost (see http://boost.org) or Qt libraries. All these libraries are cross-platform and allow writing the easily ported code. For example, in STL, the wrapper above the API level is already implemented for the work with files and the library is already cross-platform. So pay attention to using such libraries. It can make your work easier.

Windows->Linux. A few words

We examined only one of many components from the list that change during the porting. I would like to say a few words about the rest of them.

ASCII/Unicode. Here we can meet problems because the size of the wchar_t type in Windows is twice smaller than in Linux (2 bytes in Windows and 4 bytes in Linux). The reason is that Linux uses UTF-32 character encoding and Windows – UCS-2. The difference in character encodings can be solved by setting the GCC flag during the compilation:

ShellScript
-fshort-wchar

Attention should be paid to all char <-> wchar_t conversions and to the use of Unicode during the transfer between systems.

Threading API. It is better to implement it using boost or Qt libraries. It means that the work with threads has many pitfalls and it will be difficult to implement everything by yourself and it will also take you too long. That is why, as I have already said, it is better to use ready solutions.

The End

I tried to represent in brief some key moments that you can face during the porting of the application from Windows OS to Linux OS. I rather managed to show how to implement cross-platform wrappers for API by giving an example. You can come to one more conclusion from the information mentioned above. During the porting of applications it is worth paying attention to the ready implementations of cross-platform libraries (such as STL, boost and QT).

Additional materials

Look through all files attached to the article.

BackupMBR_win.zip โ€“ a project (Visual Studio 2008). An application that is to be ported to Linux OS.
BackupMBR_port_win.zip โ€“ a project (Visual Studio 2008). The cross-platform alternative of BackupMBR.
BackupMBR_linux.zip โ€“ a project (Qt Creator). The cross-platform alternative of BackupMBR that was built into the project for Linux OS.

Have a question?

Ask our expert!

Tell us about
your project

...And our team will:

  • Process your request within 1-2 business days.
  • Get back to you with an offer based on your project's scope and requirements.
  • Set a call to discuss your future project in detail and finalize the offer.
  • Sign a contract with you to start working on your project.

Do not have any specific task for us in mind but our skills seem interesting? Get a quick Apriorit intro to better understand our team capabilities.