Как реализовать кроссплатформенное ПО с библиотеками для Windows/Linux?
Я с 2011 года занимаюсь совершенствованием игрового фреймворка. Весь его код написан на C++. Целевых платформ раньше было 5 (Win, MacOs, iOs, Android, Bada), теперь 4 (Bada закрылась же) с приглядом за Tizen, WinMo и, когда-нибудь, консолями.
В общем, уровень требований к кроссплатформенности должен быть понятен. И вот как я этого добился.
Большая часть кода написана на платформонезависимом C++. Весь платформозависимый код расщеплен на три слоя:
- Нижний слой, общий интерфейс для всех платформ, общие поля всех платформ.
- Средний слой, platform-specific решения и поля. Наследуется от нижнего.
- Верхний слой, ввод platform-spcific кода во фреймворк. Наследуется от среднего слоя.
Условная компиляция применяется только для включения заголовка платформозависимого кода. Никаких макросов, никакой условной компиляции больше не допускается. В платформозависимом коде все пишется открытым кодом так, как будто пишется под одну платформу.
На уровне файлов этот подход работает так.
Есть в заголовках проекта папка "platform", где собраны нижние уровни расщепления, мастер-заголовок с условным подключением среднего уровня расщепления и все общие типы для платформ.
Так же в проекте есть папки "platform.windows", "platform.macos", "platform.###", в которых реализован средний уровень расщепления и мастер-заголовки для условного подключения.
Верхний уровень или реализуется в своей папке, если он представляет собой целую подсистему, или описывается во все той же папке "platform".
Исходный код сгруппирован так же, но включает в себя только мастер-заголовок .
Сценарии сборки на каждую из платформ включают в себя платформозависимый код только своей платформы.
Все собирается в статические библиотеки и линкуется в один исполняемый файл. Хотя есть возможность вытеснения библиотек в динамические модули (сделано на случай передачи фреймворка аутсорсерам).
Это все дает полную прозрачность исполнения кода для любой платформы. Так же этот подход делает очень легкой экспансию всего фреймворка на новую платформу.UPD:
Пример с файлом очень хорошо подходит благодаря своей простоте, его я даже по памяти могу выписать из своего фреймворка, но я кое-что все таки упрощу, чтобы никого не смущать и не пугать.spoiler// PlatformSpecificFile.Windows.h
class PlatformSpecificFile
{
// Platform-specific interface.
public:
	inline ::HANDLE GetHandle() const	{ return m_handle; };
	
// Platform-independent interface, but platform-dependent implementation.
public:
	// RAII.
	PlatformSpecificFile() = delete;
	PlatformSpecificFile(
		const std::string& path,
		const OpeningMode desired_mode,
		const AccessOptions& desired_access,
		const SharingOptions& desired_sharing
	);
	
	virtual ~PlatformSpecificFile();
	
	void Close();
	void Flush();
	
	const size64_t GetSize() const;
	const bool Resize( const size64_t new_size );
	
	const size32_t Read( NotNull<uint8_t> buffer, const size32_t buffer_size ) const;
	const size32_t Write( NotNull<const uint8_t> buffer, const size32_t buffer_size );
	
	const size64_t Seek( const size64_t offset, const SeekOrientation orientation );
	
	inline const bool IsValid() const	{ return IsHandleValid( m_handle ); };
	
private:
	::HANDLE	m_handle = INVALID_HANDLE_VALUE;
};

// PlatformSpecificFile.Android.h
class PlatformSpecificFile
{
// Platform-specific interface.
public:
	inline int GetHandle() const	{ return m_handle; };
	
// Platform-independent interface, but platform-dependent implementation.
public:
	// RAII.
	PlatformSpecificFile() = delete;
	PlatformSpecificFile(
		const std::string& path,
		const OpeningMode desired_mode,
		const AccessOptions& desired_access,
		const SharingOptions& desired_sharing
	);
	
	virtual ~PlatformSpecificFile();
	
	void Close();
	void Flush();
	
	const size64_t GetSize() const;
	const bool Resize( const size64_t new_size );
	
	const size32_t Read( NotNull<uint8_t> buffer, const size32_t buffer_size ) const;
	const size32_t Write( NotNull<const uint8_t> buffer, const size32_t buffer_size );
	
	const size64_t Seek( const size64_t offset, const SeekOrientation orientation );
	
	inline const bool IsValid() const	{ return m_handle >= 0; };
	
private:
	int		m_handle = -1;
};

// File.h
class File final : public PlatformSpecificFile
{
public:
	using PlatformSpecificFile::PlatformSpecificFile;
	
	const size64_t GetPosition() const; // Seek( 0, SeekOrientation::FromPosition );
	
	const bool SetPosition( const size64_t position ); // Seek( position, SeekOrientation::FromBeginning );
	
	const bool IsFileEnded() const; // GetPosition() == getSize();
};
Мастер-заголовок платформенного кода "platform.h" в зависимости от сценария сборки включает в себя один из мастер-заголовков платформозависимого кода "platform.###.h". Платформозависимый код уже включает в себя соответствующий заголовок файла "PlatformSpecificFile.###.h"
Как это правильно реализовать?
Если коротко - то написать разные реализации одинаковых хедеров. Можно линковать статически, можно динамически (разумеется, тут всё-таки придётся пошаманить с макросами, чтобы учесть специфичные для платформы вещи).