Cross-Platform Development in C: A Comprehensive Guide to Writing Portable Code

Introduction

Writing C code that runs seamlessly across multiple operating systems—Windows, Linux, macOS, and beyond—is a challenging but essential skill for modern developers. Cross-platform development requires careful consideration of compiler differences, operating system APIs, file system conventions, and hardware architectures. This comprehensive guide explores the strategies, techniques, and best practices for writing truly portable C code.


1. Understanding Platform Differences

1.1 Common Platform Variations

// Operating system detection macros
#ifdef _WIN32
#define PLATFORM_WINDOWS 1
#elif defined(__linux__)
#define PLATFORM_LINUX 1
#elif defined(__APPLE__) && defined(__MACH__)
#define PLATFORM_MACOS 1
#elif defined(__FreeBSD__)
#define PLATFORM_FREEBSD 1
#elif defined(__OpenBSD__)
#define PLATFORM_OPENBSD 1
#else
#define PLATFORM_UNKNOWN 1
#endif
// Compiler detection
#ifdef __GNUC__
#define COMPILER_GCC 1
#define COMPILER_VERSION __GNUC__
#elif defined(_MSC_VER)
#define COMPILER_MSVC 1
#define COMPILER_VERSION _MSC_VER
#elif defined(__clang__)
#define COMPILER_CLANG 1
#define COMPILER_VERSION __clang_major__
#endif
// Endianness detection
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define PLATFORM_LITTLE_ENDIAN 1
#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define PLATFORM_BIG_ENDIAN 1
#else
// Fallback runtime detection
#endif

1.2 Data Type Sizes

#include <stdint.h>
#include <limits.h>
// Use fixed-width types for platform-independent sizes
typedef int32_t i32;
typedef uint32_t u32;
typedef int64_t i64;
typedef uint64_t u64;
typedef int16_t i16;
typedef uint16_t u16;
typedef int8_t i8;
typedef uint8_t u8;
// Never assume sizes
void demonstrate_size_variations(void) {
printf("Size variations across platforms:\n");
printf("sizeof(char):      %zu\n", sizeof(char));        // Always 1
printf("sizeof(short):     %zu\n", sizeof(short));       // Usually 2
printf("sizeof(int):       %zu\n", sizeof(int));         // 2, 4, or 8
printf("sizeof(long):      %zu\n", sizeof(long));        // 4, 8, or more
printf("sizeof(void*):     %zu\n", sizeof(void*));       // 4 on 32-bit, 8 on 64-bit
printf("sizeof(size_t):    %zu\n", sizeof(size_t));      // Platform-dependent
// Use limits.h for maximum values
printf("INT_MAX: %d\n", INT_MAX);
printf("LONG_MAX: %ld\n", LONG_MAX);
}

2. Build Systems and Compilation

2.1 Cross-Platform Build with CMake

# CMakeLists.txt - Cross-platform build configuration
cmake_minimum_required(VERSION 3.10)
project(CrossPlatformApp VERSION 1.0.0 LANGUAGES C)
# Set C standard
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
# Platform detection
if(WIN32)
add_definitions(-DPLATFORM_WINDOWS)
set(PLATFORM_SOURCES src/windows/platform_win.c)
elseif(APPLE)
add_definitions(-DPLATFORM_MACOS)
set(PLATFORM_SOURCES src/macos/platform_macos.c)
elseif(UNIX)
add_definitions(-DPLATFORM_LINUX)
set(PLATFORM_SOURCES src/linux/platform_linux.c)
endif()
# Compiler-specific flags
if(MSVC)
add_compile_options(/W4 /WX /utf-8)
add_link_options(/LTCG)
else()
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
add_compile_options(-O2 -D_FORTIFY_SOURCE=2)
endif()
# Add executable
add_executable(${PROJECT_NAME}
src/main.c
src/common.c
${PLATFORM_SOURCES}
)
# Install
install(TARGETS ${PROJECT_NAME} DESTINATION bin)

2.2 Makefile with Platform Detection

# Makefile with platform detection
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
# Platform detection
ifeq ($(UNAME_S), Linux)
PLATFORM = linux
LIBS = -lpthread -ldl
CFLAGS = -DPLATFORM_LINUX
endif
ifeq ($(UNAME_S), Darwin)
PLATFORM = macos
LIBS = -lpthread
CFLAGS = -DPLATFORM_MACOS
endif
ifeq ($(UNAME_S), MINGW32_NT)
PLATFORM = windows
LIBS = -lws2_32 -lwinmm
CFLAGS = -DPLATFORM_WINDOWS
endif
ifeq ($(UNAME_S), CYGWIN_NT)
PLATFORM = cygwin
LIBS = -lpthread
CFLAGS = -DPLATFORM_CYGWIN
endif
# Architecture detection
ifeq ($(UNAME_M), x86_64)
ARCH = x64
CFLAGS += -m64
endif
ifeq ($(UNAME_M), i686)
ARCH = x86
CFLAGS += -m32
endif
# Common flags
CFLAGS += -std=c11 -Wall -Wextra -Wpedantic -O2
# Source files
SOURCES = src/main.c src/common.c
PLATFORM_SOURCES = src/$(PLATFORM)/platform_$(PLATFORM).c
# Build target
$(PROJECT_NAME): $(SOURCES) $(PLATFORM_SOURCES)
$(CC) $(CFLAGS) $^ -o $@ $(LIBS)
clean:
rm -f $(PROJECT_NAME)

3. File System Abstraction

3.1 Platform-Agnostic File Operations

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef PLATFORM_WINDOWS
#include <windows.h>
#include <direct.h>
#define PATH_SEPARATOR '\\'
#define PATH_SEPARATOR_STR "\\"
#define chdir _chdir
#define getcwd _getcwd
#else
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <libgen.h>
#define PATH_SEPARATOR '/'
#define PATH_SEPARATOR_STR "/"
#endif
// Platform-agnostic path utilities
void normalize_path(char *path) {
if (path == NULL) return;
#ifdef PLATFORM_WINDOWS
// Convert forward slashes to backslashes
for (char *p = path; *p; p++) {
if (*p == '/') *p = '\\';
}
#else
// Convert backslashes to forward slashes
for (char *p = path; *p; p++) {
if (*p == '\\') *p = '/';
}
#endif
}
// Join path components
char* join_path(const char *base, const char *component) {
size_t base_len = strlen(base);
size_t comp_len = strlen(component);
size_t sep_len = 1;
char *result = malloc(base_len + sep_len + comp_len + 1);
if (result == NULL) return NULL;
strcpy(result, base);
// Add separator if needed
if (base_len > 0 && base[base_len - 1] != PATH_SEPARATOR) {
result[base_len] = PATH_SEPARATOR;
strcpy(result + base_len + 1, component);
} else {
strcpy(result + base_len, component);
}
return result;
}
// Get directory listing
typedef struct {
char **entries;
size_t count;
size_t capacity;
} DirList;
#ifdef PLATFORM_WINDOWS
DirList* list_directory(const char *path) {
DirList *list = calloc(1, sizeof(DirList));
if (list == NULL) return NULL;
WIN32_FIND_DATA find_data;
char search_path[MAX_PATH];
snprintf(search_path, sizeof(search_path), "%s\\*", path);
HANDLE find_handle = FindFirstFile(search_path, &find_data);
if (find_handle == INVALID_HANDLE_VALUE) {
free(list);
return NULL;
}
do {
if (strcmp(find_data.cFileName, ".") != 0 &&
strcmp(find_data.cFileName, "..") != 0) {
if (list->count >= list->capacity) {
list->capacity = list->capacity ? list->capacity * 2 : 16;
list->entries = realloc(list->entries, 
list->capacity * sizeof(char*));
if (list->entries == NULL) break;
}
list->entries[list->count] = strdup(find_data.cFileName);
list->count++;
}
} while (FindNextFile(find_handle, &find_data));
FindClose(find_handle);
return list;
}
#else
DirList* list_directory(const char *path) {
DirList *list = calloc(1, sizeof(DirList));
if (list == NULL) return NULL;
DIR *dir = opendir(path);
if (dir == NULL) {
free(list);
return NULL;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") != 0 &&
strcmp(entry->d_name, "..") != 0) {
if (list->count >= list->capacity) {
list->capacity = list->capacity ? list->capacity * 2 : 16;
list->entries = realloc(list->entries,
list->capacity * sizeof(char*));
if (list->entries == NULL) break;
}
list->entries[list->count] = strdup(entry->d_name);
list->count++;
}
}
closedir(dir);
return list;
}
#endif
void free_dirlist(DirList *list) {
if (list == NULL) return;
for (size_t i = 0; i < list->count; i++) {
free(list->entries[i]);
}
free(list->entries);
free(list);
}

4. Threading Abstraction

4.1 Unified Thread API

#ifdef PLATFORM_WINDOWS
#include <windows.h>
typedef HANDLE ThreadHandle;
typedef DWORD (WINAPI *ThreadFunc)(void*);
int thread_create(ThreadHandle *thread, ThreadFunc func, void *arg) {
*thread = CreateThread(NULL, 0, func, arg, 0, NULL);
return (*thread != NULL) ? 0 : -1;
}
int thread_join(ThreadHandle thread) {
return (WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0) ? 0 : -1;
}
void thread_detach(ThreadHandle thread) {
CloseHandle(thread);
}
#else
#include <pthread.h>
typedef pthread_t ThreadHandle;
typedef void* (*ThreadFunc)(void*);
int thread_create(ThreadHandle *thread, ThreadFunc func, void *arg) {
return pthread_create(thread, NULL, func, arg);
}
int thread_join(ThreadHandle thread) {
return pthread_join(thread, NULL);
}
void thread_detach(ThreadHandle thread) {
pthread_detach(thread);
}
#endif
// Mutex abstraction
#ifdef PLATFORM_WINDOWS
typedef CRITICAL_SECTION Mutex;
void mutex_init(Mutex *mutex) {
InitializeCriticalSection(mutex);
}
void mutex_lock(Mutex *mutex) {
EnterCriticalSection(mutex);
}
void mutex_unlock(Mutex *mutex) {
LeaveCriticalSection(mutex);
}
void mutex_destroy(Mutex *mutex) {
DeleteCriticalSection(mutex);
}
#else
typedef pthread_mutex_t Mutex;
void mutex_init(Mutex *mutex) {
pthread_mutex_init(mutex, NULL);
}
void mutex_lock(Mutex *mutex) {
pthread_mutex_lock(mutex);
}
void mutex_unlock(Mutex *mutex) {
pthread_mutex_unlock(mutex);
}
void mutex_destroy(Mutex *mutex) {
pthread_mutex_destroy(mutex);
}
#endif

5. Socket Programming Abstraction

5.1 Cross-Platform Networking

#ifdef PLATFORM_WINDOWS
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef SOCKET Socket;
#define INVALID_SOCKET_VAL INVALID_SOCKET
#define SOCKET_ERROR_VAL SOCKET_ERROR
int network_init(void) {
WSADATA wsa_data;
return WSAStartup(MAKEWORD(2, 2), &wsa_data);
}
void network_cleanup(void) {
WSACleanup();
}
int socket_close(Socket sock) {
return closesocket(sock);
}
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
typedef int Socket;
#define INVALID_SOCKET_VAL (-1)
#define SOCKET_ERROR_VAL (-1)
int network_init(void) {
return 0;  // No initialization needed on Unix
}
void network_cleanup(void) {
// Nothing to clean up
}
int socket_close(Socket sock) {
return close(sock);
}
#endif
// Cross-platform socket creation
Socket create_tcp_socket(void) {
Socket sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET_VAL) {
return INVALID_SOCKET_VAL;
}
// Set socket options
int opt = 1;
#ifdef PLATFORM_WINDOWS
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 
(const char*)&opt, sizeof(opt));
#else
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 
&opt, sizeof(opt));
// Set non-blocking mode
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK);
#endif
return sock;
}
// Cross-platform socket connect
int socket_connect(Socket sock, const char *host, int port) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
// Convert host to IP
if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
return -1;
}
return connect(sock, (struct sockaddr*)&addr, sizeof(addr));
}

6. Dynamic Library Loading

6.1 Cross-Platform Plugin System

#ifdef PLATFORM_WINDOWS
#include <windows.h>
typedef HMODULE LibHandle;
LibHandle load_library(const char *path) {
return LoadLibrary(path);
}
void* get_symbol(LibHandle lib, const char *name) {
return GetProcAddress(lib, name);
}
void unload_library(LibHandle lib) {
FreeLibrary(lib);
}
const char* library_extension(void) {
return ".dll";
}
#else
#include <dlfcn.h>
typedef void* LibHandle;
LibHandle load_library(const char *path) {
return dlopen(path, RTLD_LAZY);
}
void* get_symbol(LibHandle lib, const char *name) {
return dlsym(lib, name);
}
void unload_library(LibHandle lib) {
dlclose(lib);
}
const char* library_extension(void) {
#ifdef __APPLE__
return ".dylib";
#else
return ".so";
#endif
}
#endif
// Example plugin structure
typedef struct {
const char *name;
void (*init)(void);
void (*shutdown)(void);
int (*process)(const void *data, size_t size);
} Plugin;
Plugin* load_plugin(const char *name) {
char path[512];
snprintf(path, sizeof(path), "plugins/%s%s", name, library_extension());
LibHandle lib = load_library(path);
if (lib == NULL) {
return NULL;
}
Plugin *plugin = malloc(sizeof(Plugin));
if (plugin == NULL) {
unload_library(lib);
return NULL;
}
// Get plugin functions
plugin->name = name;
plugin->init = get_symbol(lib, "plugin_init");
plugin->shutdown = get_symbol(lib, "plugin_shutdown");
plugin->process = get_symbol(lib, "plugin_process");
if (plugin->init == NULL || plugin->shutdown == NULL || 
plugin->process == NULL) {
free(plugin);
unload_library(lib);
return NULL;
}
// Store library handle for cleanup
*(LibHandle*)(plugin + 1) = lib;
return plugin;
}
void unload_plugin(Plugin *plugin) {
if (plugin == NULL) return;
LibHandle lib = *(LibHandle*)(plugin + 1);
plugin->shutdown();
unload_library(lib);
free(plugin);
}

7. Shared Memory and IPC

7.1 Cross-Platform Shared Memory

#ifdef PLATFORM_WINDOWS
#include <windows.h>
typedef HANDLE SharedMemoryHandle;
SharedMemoryHandle create_shared_memory(const char *name, size_t size) {
return CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, (DWORD)size, name);
}
void* map_shared_memory(SharedMemoryHandle handle, size_t size) {
return MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
}
void unmap_shared_memory(void *ptr) {
UnmapViewOfFile(ptr);
}
void close_shared_memory(SharedMemoryHandle handle) {
CloseHandle(handle);
}
#else
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
typedef int SharedMemoryHandle;
SharedMemoryHandle create_shared_memory(const char *name, size_t size) {
return shm_open(name, O_CREAT | O_RDWR, 0600);
}
void* map_shared_memory(SharedMemoryHandle handle, size_t size) {
if (ftruncate(handle, size) == -1) {
return MAP_FAILED;
}
return mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, handle, 0);
}
void unmap_shared_memory(void *ptr) {
munmap(ptr, 0);  // Size must be known in real implementation
}
void close_shared_memory(SharedMemoryHandle handle) {
close(handle);
}
#endif

8. Unicode and Character Encoding

8.1 Cross-Platform Unicode Handling

#include <wchar.h>
#include <locale.h>
#ifdef PLATFORM_WINDOWS
// Windows uses UTF-16 internally
#define CHAR_TYPE wchar_t
#define TEXT_LITERAL(s) L##s
#define STDIO_WFUNC wprintf
#else
// Unix systems typically use UTF-8
#define CHAR_TYPE char
#define TEXT_LITERAL(s) s
#define STDIO_WFUNC printf
#endif
// Convert between UTF-8 and UTF-16
#ifdef PLATFORM_WINDOWS
char* utf16_to_utf8(const wchar_t *wstr) {
if (wstr == NULL) return NULL;
int size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
if (size <= 0) return NULL;
char *result = malloc(size);
if (result == NULL) return NULL;
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result, size, NULL, NULL);
return result;
}
wchar_t* utf8_to_utf16(const char *str) {
if (str == NULL) return NULL;
int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
if (size <= 0) return NULL;
wchar_t *result = malloc(size * sizeof(wchar_t));
if (result == NULL) return NULL;
MultiByteToWideChar(CP_UTF8, 0, str, -1, result, size);
return result;
}
#else
// On Unix, convert using iconv or assume UTF-8
char* utf16_to_utf8(const wchar_t *wstr) {
// Simplified - use iconv in production
size_t len = wcslen(wstr);
char *result = malloc(len * 4 + 1);
if (result == NULL) return NULL;
// Simple conversion for ASCII only
for (size_t i = 0; i < len; i++) {
result[i] = (char)(wstr[i] & 0xFF);
}
result[len] = '\0';
return result;
}
wchar_t* utf8_to_utf16(const char *str) {
size_t len = strlen(str);
wchar_t *result = malloc((len + 1) * sizeof(wchar_t));
if (result == NULL) return NULL;
for (size_t i = 0; i < len; i++) {
result[i] = (wchar_t)str[i];
}
result[len] = L'\0';
return result;
}
#endif
// Cross-platform file open with Unicode support
FILE* open_file_unicode(const char *filename, const char *mode) {
#ifdef PLATFORM_WINDOWS
wchar_t *wfilename = utf8_to_utf16(filename);
wchar_t *wmode = utf8_to_utf16(mode);
FILE *f = _wfopen(wfilename, wmode);
free(wfilename);
free(wmode);
return f;
#else
return fopen(filename, mode);
#endif
}

9. Time and Date Handling

9.1 Cross-Platform Time Utilities

#include <time.h>
#ifdef PLATFORM_WINDOWS
#include <sys/timeb.h>
#include <windows.h>
// Windows high-resolution timer
typedef struct {
LARGE_INTEGER frequency;
LARGE_INTEGER start;
} HighResTimer;
void timer_start(HighResTimer *timer) {
QueryPerformanceFrequency(&timer->frequency);
QueryPerformanceCounter(&timer->start);
}
double timer_elapsed_ms(HighResTimer *timer) {
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
return (double)(now.QuadPart - timer->start.QuadPart) * 1000.0 /
(double)timer->frequency.QuadPart;
}
#else
#include <sys/time.h>
// POSIX high-resolution timer
typedef struct {
struct timeval start;
} HighResTimer;
void timer_start(HighResTimer *timer) {
gettimeofday(&timer->start, NULL);
}
double timer_elapsed_ms(HighResTimer *timer) {
struct timeval now;
gettimeofday(&now, NULL);
return (now.tv_sec - timer->start.tv_sec) * 1000.0 +
(now.tv_usec - timer->start.tv_usec) / 1000.0;
}
#endif
// Get current time as ISO 8601 string
char* get_iso_timestamp(void) {
time_t now = time(NULL);
struct tm *tm_info;
char *buffer = malloc(32);
if (buffer == NULL) return NULL;
#ifdef PLATFORM_WINDOWS
tm_info = localtime(&now);
strftime(buffer, 32, "%Y-%m-%dT%H:%M:%S%z", tm_info);
#else
tm_info = localtime(&now);
strftime(buffer, 32, "%Y-%m-%dT%H:%M:%S%z", tm_info);
// Insert colon in timezone offset
char *tz = strchr(buffer, '+');
if (tz == NULL) tz = strchr(buffer, '-');
if (tz && strlen(tz) == 5) {
memmove(tz + 3, tz + 2, 4);
tz[2] = ':';
}
#endif
return buffer;
}

10. Complete Cross-Platform Example

Here's a complete example that demonstrates many cross-platform techniques:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
/* Platform detection */
#ifdef _WIN32
#define PLATFORM_WINDOWS 1
#include <windows.h>
#include <direct.h>
#define PATH_SEP '\\'
#define PATH_SEP_STR "\\"
#define getcwd _getcwd
#else
#define PLATFORM_WINDOWS 0
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#define PATH_SEP '/'
#define PATH_SEP_STR "/"
#endif
/* Fixed-width types */
typedef int32_t i32;
typedef uint32_t u32;
typedef int64_t i64;
typedef uint64_t u64;
/* Platform-agnostic path utilities */
char* join_path(const char *a, const char *b) {
size_t a_len = strlen(a);
size_t b_len = strlen(b);
char *result = malloc(a_len + b_len + 2);
if (result == NULL) return NULL;
strcpy(result, a);
if (a_len > 0 && a[a_len - 1] != PATH_SEP) {
result[a_len] = PATH_SEP;
strcpy(result + a_len + 1, b);
} else {
strcpy(result + a_len, b);
}
return result;
}
/* Directory operations */
int create_directory(const char *path) {
#if PLATFORM_WINDOWS
return _mkdir(path);
#else
return mkdir(path, 0755);
#endif
}
/* File operations */
typedef struct {
FILE *file;
char *path;
} FileHandle;
FileHandle* open_file(const char *path, const char *mode) {
FileHandle *fh = malloc(sizeof(FileHandle));
if (fh == NULL) return NULL;
fh->file = fopen(path, mode);
if (fh->file == NULL) {
free(fh);
return NULL;
}
fh->path = strdup(path);
return fh;
}
void close_file(FileHandle *fh) {
if (fh == NULL) return;
if (fh->file) fclose(fh->file);
free(fh->path);
free(fh);
}
/* Configuration structure */
typedef struct {
char *app_name;
char *config_dir;
char *data_dir;
int max_threads;
int verbose;
} Config;
/* Platform-specific defaults */
Config* load_config(void) {
Config *cfg = malloc(sizeof(Config));
if (cfg == NULL) return NULL;
memset(cfg, 0, sizeof(Config));
cfg->max_threads = 4;
cfg->app_name = strdup("MyApp");
#if PLATFORM_WINDOWS
char *appdata = getenv("APPDATA");
if (appdata) {
cfg->config_dir = join_path(appdata, cfg->app_name);
cfg->data_dir = join_path(appdata, cfg->app_name);
} else {
cfg->config_dir = strdup(".myapp");
cfg->data_dir = strdup(".myapp");
}
#else
char *home = getenv("HOME");
if (home) {
cfg->config_dir = join_path(home, ".config");
cfg->config_dir = join_path(cfg->config_dir, cfg->app_name);
cfg->data_dir = join_path(home, ".local");
cfg->data_dir = join_path(cfg->data_dir, "share");
cfg->data_dir = join_path(cfg->data_dir, cfg->app_name);
} else {
cfg->config_dir = strdup(".myapp");
cfg->data_dir = strdup(".myapp");
}
#endif
return cfg;
}
void free_config(Config *cfg) {
if (cfg == NULL) return;
free(cfg->app_name);
free(cfg->config_dir);
free(cfg->data_dir);
free(cfg);
}
/* Platform information display */
void print_platform_info(void) {
printf("Platform Information:\n");
#if PLATFORM_WINDOWS
printf("  OS: Windows\n");
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
GetVersionEx((OSVERSIONINFO*)&osvi);
printf("  Version: %d.%d\n", osvi.dwMajorVersion, osvi.dwMinorVersion);
printf("  Build: %d\n", osvi.dwBuildNumber);
#elif defined(__APPLE__)
printf("  OS: macOS\n");
#ifdef __arm64__
printf("  Architecture: ARM64\n");
#else
printf("  Architecture: x86_64\n");
#endif
#elif defined(__linux__)
printf("  OS: Linux\n");
#ifdef __x86_64__
printf("  Architecture: x86_64\n");
#elif defined(__aarch64__)
printf("  Architecture: ARM64\n");
#else
printf("  Architecture: Unknown\n");
#endif
#else
printf("  OS: Unknown\n");
#endif
printf("  Pointer size: %zu bytes\n", sizeof(void*));
printf("  Endianness: ");
uint32_t test = 1;
if (*(uint8_t*)&test == 1) {
printf("Little Endian\n");
} else {
printf("Big Endian\n");
}
}
/* Main application */
int main(int argc, char *argv[]) {
printf("=== Cross-Platform Application ===\n\n");
// Load configuration
Config *cfg = load_config();
if (cfg == NULL) {
fprintf(stderr, "Failed to load configuration\n");
return 1;
}
// Display platform info
print_platform_info();
// Display configuration
printf("\nConfiguration:\n");
printf("  App name: %s\n", cfg->app_name);
printf("  Config dir: %s\n", cfg->config_dir);
printf("  Data dir: %s\n", cfg->data_dir);
printf("  Max threads: %d\n", cfg->max_threads);
// Create directories if needed
printf("\nCreating directories...\n");
if (create_directory(cfg->config_dir) == 0) {
printf("  Created config directory\n");
} else {
printf("  Config directory already exists or error\n");
}
// Write a config file
char *config_file = join_path(cfg->config_dir, "settings.txt");
if (config_file) {
FileHandle *fh = open_file(config_file, "w");
if (fh) {
fprintf(fh->file, "# Application Settings\n");
fprintf(fh->file, "verbose=%d\n", cfg->verbose);
fprintf(fh->file, "max_threads=%d\n", cfg->max_threads);
fprintf(fh->file, "timestamp=%s\n", get_iso_timestamp());
close_file(fh);
printf("  Wrote config to: %s\n", config_file);
}
free(config_file);
}
// Clean up
free_config(cfg);
printf("\nApplication completed successfully.\n");
return 0;
}

11. Cross-Platform Testing Strategies

11.1 Testing Matrix

// test_platform.c
#include <stdio.h>
#include <stdlib.h>
// Test each platform-specific code path
void test_platform_features(void) {
// Test path handling
char *path = join_path("foo", "bar");
printf("join_path(\"foo\", \"bar\") = %s\n", path);
free(path);
// Test directory creation
const char *test_dir = "test_dir";
if (create_directory(test_dir) == 0) {
printf("Created directory: %s\n", test_dir);
} else {
printf("Directory already exists: %s\n", test_dir);
}
// Test file operations
FileHandle *fh = open_file("test.txt", "w");
if (fh) {
fprintf(fh->file, "Test content\n");
close_file(fh);
printf("Wrote test file\n");
}
// Test timer
HighResTimer timer;
timer_start(&timer);
sleep_ms(100);
double elapsed = timer_elapsed_ms(&timer);
printf("Timer elapsed: %.2f ms\n", elapsed);
}

11.2 Continuous Integration

# .github/workflows/cross-platform.yml
name: Cross-Platform Tests
on: [push, pull_request]
jobs:
test-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: |
mkdir build && cd build
cmake .. && make
- name: Test
run: ./build/test_app
test-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: |
mkdir build && cd build
cmake .. && make
- name: Test
run: ./build/test_app
test-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: |
mkdir build
cd build
cmake ..
cmake --build . --config Release
- name: Test
run: .\build\Release\test_app.exe

12. Common Cross-Platform Libraries

LibraryPurposeDescription
libuvI/OCross-platform asynchronous I/O
GLibCoreGNOME's utility library with cross-platform abstractions
SDLGraphicsCross-platform graphics and input handling
OpenSSLCryptoCryptography across all platforms
zlibCompressionCross-platform compression
libcurlNetworkingCross-platform HTTP/FTP client
SQLiteDatabaseEmbedded database that works everywhere
ICUUnicodeInternationalization and Unicode support

13. Best Practices Summary

  1. Use standard C where possible: Stick to ISO C standards
  2. Abstract platform differences: Create wrappers for platform-specific APIs
  3. Use fixed-width types: stdint.h types for predictable sizes
  4. Handle paths correctly: Use platform-appropriate separators
  5. Test on all target platforms: Set up CI for each platform
  6. Use cross-platform build systems: CMake, Meson, or similar
  7. Document platform assumptions: Clearly state supported platforms
  8. Handle endianness: Use byte-swapping where necessary
  9. Consider character encodings: Use UTF-8 internally, convert at boundaries
  10. Provide fallbacks: Have default implementations for unsupported features

Conclusion

Cross-platform development in C is challenging but achievable with careful design and abstraction. By isolating platform-specific code behind well-defined interfaces, using standard C features where possible, and leveraging cross-platform libraries, you can write code that compiles and runs correctly on Windows, Linux, macOS, and beyond.

The key is to think about portability from the start, not as an afterthought. With the techniques and patterns outlined in this guide, you can build C applications that truly work everywhere, reaching the widest possible audience with a single codebase.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper