C++ Cross-Platform Development
Overview
Cross-platform development allows C++ programs to run on different operating systems. This chapter introduces cross-platform development strategies, tools, and best practices.
🌍 Platform Difference Handling
Conditional Compilation
cpp
#include <iostream>
// Platform detection macros
#ifdef _WIN32
#include <windows.h>
#define PLATFORM_WINDOWS
#elif __linux__
#include <unistd.h>
#include <sys/utsname.h>
#define PLATFORM_LINUX
#elif __APPLE__
#include <sys/utsname.h>
#define PLATFORM_MACOS
#endif
class PlatformUtils {
public:
static std::string getPlatformName() {
#ifdef PLATFORM_WINDOWS
return "Windows";
#elif PLATFORM_LINUX
return "Linux";
#elif PLATFORM_MACOS
return "macOS";
#else
return "Unknown";
#endif
}
static void sleep(int milliseconds) {
#ifdef PLATFORM_WINDOWS
Sleep(milliseconds);
#else
usleep(milliseconds * 1000);
#endif
}
static std::string getSystemInfo() {
#ifdef PLATFORM_WINDOWS
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return "Windows system, processor count: " + std::to_string(sysInfo.dwNumberOfProcessors);
#else
struct utsname unameData;
uname(&unameData);
return std::string(unameData.sysname) + " " + unameData.release;
#endif
}
};
// File path handling
class PathUtils {
public:
static const char PATH_SEPARATOR =
#ifdef PLATFORM_WINDOWS
'\\';
#else
'/';
#endif
static std::string joinPath(const std::string& dir, const std::string& file) {
return dir + PATH_SEPARATOR + file;
}
static std::string normalizePath(const std::string& path) {
std::string result = path;
#ifdef PLATFORM_WINDOWS
// Replace '/' with '\'
for (char& c : result) {
if (c == '/') c = '\\';
}
#endif
return result;
}
};🛠️ Build Systems
CMake Example
cmake
# CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(CrossPlatformApp)
# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Platform-specific settings
if(WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
elseif(UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
endif()
# Source files
set(SOURCES
src/main.cpp
src/platform_utils.cpp
)
# Platform-specific source files
if(WIN32)
list(APPEND SOURCES src/windows_specific.cpp)
elseif(UNIX)
list(APPEND SOURCES src/unix_specific.cpp)
endif()
# Executable
add_executable(${PROJECT_NAME} ${SOURCES})
# Link libraries
if(WIN32)
target_link_libraries(${PROJECT_NAME} ws2_32)
elseif(UNIX)
target_link_libraries(${PROJECT_NAME} pthread)
endif()
# Installation rules
install(TARGETS ${PROJECT_NAME} DESTINATION bin)Cross-Platform Library Integration
cpp
// Use Boost library for cross-platform functionality
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
class CrossPlatformApp {
public:
static void demonstrateFilesystem() {
namespace fs = boost::filesystem;
fs::path currentPath = fs::current_path();
std::cout << "Current directory: " << currentPath.string() << std::endl;
// Cross-platform path operations
fs::path configFile = currentPath / "config" / "app.conf";
std::cout << "Config file path: " << configFile.string() << std::endl;
// Directory traversal
if (fs::exists(currentPath) && fs::is_directory(currentPath)) {
for (const auto& entry : fs::directory_iterator(currentPath)) {
std::cout << entry.path().filename().string() << std::endl;
}
}
}
static void demonstrateThreading() {
boost::thread_group threads;
for (int i = 0; i < 4; ++i) {
threads.create_thread([i]() {
std::cout << "Thread " << i << " running on: "
<< PlatformUtils::getPlatformName() << std::endl;
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
});
}
threads.join_all();
}
};📱 Platform Abstraction Layer
Unified Interface Design
cpp
// Abstract interface
class IFileSystem {
public:
virtual ~IFileSystem() = default;
virtual bool fileExists(const std::string& path) = 0;
virtual std::string readFile(const std::string& path) = 0;
virtual bool writeFile(const std::string& path, const std::string& content) = 0;
virtual std::vector<std::string> listDirectory(const std::string& path) = 0;
};
// Windows implementation
#ifdef PLATFORM_WINDOWS
class WindowsFileSystem : public IFileSystem {
public:
bool fileExists(const std::string& path) override {
return GetFileAttributesA(path.c_str()) != INVALID_FILE_ATTRIBUTES;
}
std::string readFile(const std::string& path) override {
std::ifstream file(path);
return std::string((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
bool writeFile(const std::string& path, const std::string& content) override {
std::ofstream file(path);
if (file.is_open()) {
file << content;
return true;
}
return false;
}
std::vector<std::string> listDirectory(const std::string& path) override {
std::vector<std::string> files;
WIN32_FIND_DATAA findData;
HANDLE hFind = FindFirstFileA((path + "\\*").c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (strcmp(findData.cFileName, ".") != 0 &&
strcmp(findData.cFileName, "..") != 0) {
files.push_back(findData.cFileName);
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
}
return files;
}
};
#endif
// Unix implementation
#ifdef PLATFORM_LINUX
class UnixFileSystem : public IFileSystem {
public:
bool fileExists(const std::string& path) override {
return access(path.c_str(), F_OK) == 0;
}
std::string readFile(const std::string& path) override {
std::ifstream file(path);
return std::string((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
bool writeFile(const std::string& path, const std::string& content) override {
std::ofstream file(path);
if (file.is_open()) {
file << content;
return true;
}
return false;
}
std::vector<std::string> listDirectory(const std::string& path) override {
std::vector<std::string> files;
DIR* dir = opendir(path.c_str());
if (dir) {
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (strcmp(entry->d_name, ".") != 0 &&
strcmp(entry->d_name, "..") != 0) {
files.push_back(entry->d_name);
}
}
closedir(dir);
}
return files;
}
};
#endif
// Factory function
std::unique_ptr<IFileSystem> createFileSystem() {
#ifdef PLATFORM_WINDOWS
return std::make_unique<WindowsFileSystem>();
#elif PLATFORM_LINUX
return std::make_unique<UnixFileSystem>();
#else
return nullptr;
#endif
}🔧 Compilation Configuration
Compilation Script Examples
bash
#!/bin/bash
# build.sh - Cross-platform compilation script
# Detect platform
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
PLATFORM="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
PLATFORM="macos"
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
PLATFORM="windows"
else
echo "Unsupported platform: $OSTYPE"
exit 1
fi
echo "Building for platform: $PLATFORM"
# Create build directory
mkdir -p build
cd build
# Run CMake
cmake .. -DCMAKE_BUILD_TYPE=Release
# Compile
if [[ "$PLATFORM" == "windows" ]]; then
cmake --build . --config Release
else
make -j$(nproc)
fi
echo "Build complete"bat
@echo off
REM build.bat - Windows compilation script
echo Building for platform: Windows
if not exist build mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release
echo Build complete
pause📋 Configuration Management
cpp
#include <iostream>
#include <map>
#include <fstream>
class ConfigManager {
private:
std::map<std::string, std::string> config_;
public:
bool loadConfig() {
std::string configFile = getConfigPath();
std::ifstream file(configFile);
if (!file.is_open()) {
createDefaultConfig();
return false;
}
std::string line;
while (std::getline(file, line)) {
size_t pos = line.find('=');
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
config_[key] = value;
}
}
return true;
}
std::string getValue(const std::string& key, const std::string& defaultValue = "") {
auto it = config_.find(key);
return (it != config_.end()) ? it->second : defaultValue;
}
private:
std::string getConfigPath() {
#ifdef PLATFORM_WINDOWS
return "C:\\ProgramData\\MyApp\\config.ini";
#elif PLATFORM_MACOS
return "/Users/" + std::string(getenv("USER")) + "/Library/Application Support/MyApp/config.ini";
#else
return "/home/" + std::string(getenv("USER")) + "/.myapp/config.ini";
#endif
}
void createDefaultConfig() {
config_["app_name"] = "MyApp";
config_["version"] = "1.0.0";
config_["log_level"] = "INFO";
#ifdef PLATFORM_WINDOWS
config_["temp_dir"] = "C:\\Temp";
#else
config_["temp_dir"] = "/tmp";
#endif
}
};
int main() {
std::cout << "=== Cross-Platform Development Demo ===" << std::endl;
std::cout << "Platform: " << PlatformUtils::getPlatformName() << std::endl;
std::cout << "System info: " << PlatformUtils::getSystemInfo() << std::endl;
// Filesystem demo
auto fs = createFileSystem();
if (fs) {
std::cout << "Current directory files:" << std::endl;
auto files = fs->listDirectory(".");
for (const auto& file : files) {
std::cout << " " << file << std::endl;
}
}
// Configuration management
ConfigManager config;
config.loadConfig();
std::cout << "Application name: " << config.getValue("app_name") << std::endl;
return 0;
}Summary
Cross-Platform Strategies
- Conditional Compilation: Use preprocessor macros to handle platform differences
- Abstraction Layer: Design unified interfaces to hide platform details
- Standard Library First: Prioritize C++ standard library
- Third-Party Libraries: Use mature cross-platform libraries
Tool Selection
| Tool | Purpose | Advantage |
|---|---|---|
| CMake | Build system | Widely supported |
| Boost | Cross-platform library | Feature-rich |
| Qt | GUI framework | Complete ecosystem |
Best Practices
- Consider cross-platform requirements early
- Isolate platform-related code
- Unify encoding and line endings
- Continuous integration testing on all platforms
- Document platform-specific behaviors
Cross-platform development requires careful planning and design but can significantly expand the applicability of your programs.