Первый комментатор со своим предложением скриптов в целом транслирует частный случай верной архитектуры, но, видимо в силу еще слабого технического кругозора несколько категорично.
Суть в чем - у вас есть логический модуль отвечающий за ос-зависимый код. У него есть интерфейс. Вы делаете два класса/модуля которые этот самый интерфейс реализуют. Дальнейший вопрос это как давать вашему приложению экземпляр этого интерфейса для использования. Можно создавать в рантайме, полагаясь на выбор ос (это ваш вариант, он же вариант с инверсией контроля, когда обе имплементации интерфейса доступны для приложения в любой момент и создаются во время работы каким-то внутренним механизмом выбирающим их на основе текущей ос), однако чаще это делается в компайл-тайме, проект при этом один, но дробится на проект аппа содержащий вашу абстрактную логику без привязки к системе + столько проектов ос/платформенно зависимых реализаций библиотеки сколько вам нужно. В той же джаве например проекты библиотек оформляются модулями основного проекта с использованием автосборщиков градл или мавен. В случае си и си++ инструменты у вас будут свои. Конечный результат правильно организованной сборки представляет собой набор дистрибутивов для конкретных платформ лишенный "лишнего" - в каждый конкретный дистрибутив идет только то что ему нужно для целевой платформы. Подобный подход так же работает для проектов с разными языками, поскольку множество языков умеет в интероп с нативными библиотеками. В таких случаях абстрактный слой (бизнес-логику) делают общей кодовой базой и пишут ее на чем удобно и что больше подходит для целей приложения. Это и есть частный случай из комментария 1. Если нет желания баловаться с интеропом то и не нужно, выбираете удобный инстурмент автоматизации вашей сборки, аккуратно отделяете свой абстрактный слой от платформенно-зависимого и вперед.