Как лучше реализовать API для модулей?
> Конечно, C++ может вызвать виртуальные методы у указателей
именно, поэтому вам нужно, как вариант:
1) сделать абстрактные интерфейсы вроде IFooService, библиотека будет отдавать указатели на объекты, их реализующие
2) сделать функции-фабрики и функции-уничтожители:IFooService* CreateFooService(...);
void DestroyFooService(IFooService* service);
Это все нужно в частности для того, чтобы управление памятью не выходило за пределы DLL - иными словами, где порождается, там и убивается. Для большинства библиотек это приемлемое ограничение, которое позволяет сохранять ABI и даже использовать разные рантаймы для DLL и  для EXE, его использующего.
Если вы будете передавать какие-то параметры по указателю, также следуйте этому правилу - не делайте так, что код в EXE выделяет память, а код в DLL - освобождает, и наоборот.
Ну и вообще, это хорошая абстракция интерфейса (ваш IFooService) от реализации (ваш конкретный класс FooService с конкретным конструктором и деструктором. К-р и д-р сложного класса должны оставаться частью _реализации_).
Если в качестве параметров, передающихся по значению вы используете struct-ы, это вполне допустимо, но тогда уже надо компилировать DLL и EXE одним компилятором, как этого требует, например, Qt (игнорирование этого правила рано или поздно приведет к странным и трудноуловим ошибкам, связанным с тем, что разные версии компилятора генерируют разный код конструкторов и деструкторов, и вообще могут по разному разложить struct в памяти).
Сделайте класс импорта с виртуальными функциями. И загружайте в него его реализацию через LoadLibrary.