Writing C Interfaces for C++ Code

I wanted to use a C++ library in my C code and I did the following.

This is the original API in C++. The original code is Copyright (C) 2011-2018 Filipe Coelho <falktx at falktx.com> and is licensed under the GNU GPL, version 2 or later. Some parts are abbreviated.

/* CarlaPlugin.hpp */
namespace CarlaBackend
{
  typedef enum {
      PLUGIN_CATEGORY_NONE = 0,
      PLUGIN_CATEGORY_SYNTH = 1,
      PLUGIN_CATEGORY_DELAY = 2,
      PLUGIN_CATEGORY_EQ = 3,
      PLUGIN_CATEGORY_FILTER = 4,
      PLUGIN_CATEGORY_DISTORTION = 5,
      PLUGIN_CATEGORY_DYNAMICS = 6,
      PLUGIN_CATEGORY_MODULATOR = 7,
      PLUGIN_CATEGORY_UTILITY = 8,
      PLUGIN_CATEGORY_OTHER = 9
  } PluginCategory;

  class CARLA_API CarlaPlugin
  {
    virtual PluginCategory getCategory() const noexcept;
    uint32_t getAudioInCount() const noexcept;
    virtual void getMaker(char* const strBuf) const noexcept;
    virtual void showCustomUI(const bool yesNo);
    virtual void getRealName(char* const strBuf) const noexcept;
    virtual void uiIdle();
    bool saveStateToFile(const char* const filename);
    bool loadStateFromFile(const char* const filename);
    virtual void process(const float** const audioIn, float** const audioOut,
                         const float** const cvIn, float** const cvOut, const uint32_t frames) = 0;
  };
}

This is the header file for the interface. It works for both C and C++.

/* carla_plugin.h */

#ifndef __PLUGINS_CARLA_PLUGIN_INTERFACE_H__
#define __PLUGINS_CARLA_PLUGIN_INTERFACE_H__

#include <CarlaBackend.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef void * CarlaPluginHandle;

void
carla_plugin_show_custom_ui (
  CarlaPluginHandle handle,
  const int         show);

void
carla_plugin_ui_idle (
  CarlaPluginHandle handle);

void
carla_plugin_process (
  CarlaPluginHandle handle,
  const float ** const audio_in,
  float ** const audio_out,
  const float ** const cv_in,
  float ** const cv_out,
  const uint32_t frames);

void
carla_plugin_get_real_name (
  CarlaPluginHandle handle,
  char * const      name);

void
carla_plugin_get_maker (
  CarlaPluginHandle handle,
  char * const      name);

int
carla_plugin_get_category (
  CarlaPluginHandle handle);

uint32_t
carla_plugin_get_audio_in_count (
  CarlaPluginHandle handle);
int
carla_plugin_save_state_to_file (
  CarlaPluginHandle handle,
  const char * const filename);

int
carla_plugin_load_state_from_file (
  CarlaPluginHandle handle,
  const char * const filename);

#ifdef __cplusplus
}
#endif

#endif // header guard

This is the interface "glue code" in C++. It should be compiled as a C++ file and linked together with the rest of the C code.

#include "carla_plugin.h"
#include <CarlaPlugin.hpp>

extern "C"
{

#define GET_PLUGIN \
  CarlaBackend::CarlaPlugin * plugin = \
    (CarlaBackend::CarlaPlugin *) handle

void
carla_plugin_show_custom_ui (
  CarlaPluginHandle handle,
  const int         show)
{
  GET_PLUGIN;
  plugin->showCustomUI (show);
}

void
carla_plugin_ui_idle (
  CarlaPluginHandle handle)
{
  GET_PLUGIN;
  plugin->uiIdle ();
}

void
carla_plugin_process (
  CarlaPluginHandle handle,
  const float ** const audio_in,
  float ** const audio_out,
  const float ** const cv_in,
  float ** const cv_out,
  const uint32_t frames)
{
  GET_PLUGIN;
  plugin->process (
    audio_in, audio_out, cv_in, cv_out, frames);
}

void
carla_plugin_get_real_name (
  CarlaPluginHandle handle,
  char * const      name)
{
  GET_PLUGIN;
  plugin->getRealName (name);
}

void
carla_plugin_get_maker (
  CarlaPluginHandle handle,
  char * const      name)
{
  GET_PLUGIN;
  plugin->getMaker (name);
}


int
carla_plugin_get_category (
  CarlaPluginHandle handle)
{
  GET_PLUGIN;
  return plugin->getCategory ();
}

uint32_t
carla_plugin_get_audio_in_count (
  CarlaPluginHandle handle)
{
  GET_PLUGIN;
  return plugin->getAudioInCount ();
}

int
carla_plugin_save_state_to_file (
  CarlaPluginHandle handle,
  const char * const filename)
{
  GET_PLUGIN;
  return plugin->saveStateToFile (filename);
}

int
carla_plugin_load_state_from_file (
  CarlaPluginHandle handle,
  const char * const filename)
{
  GET_PLUGIN;
  return plugin->loadStateFromFile (filename);
}

}

This is an example usage:

/* some_code.c */

/* get a plugin handle through another similar interface
 * (carla_engine) */
CarlaEngineHandle engine =
  carla_engine_get_from_native_plugin (
    self->descriptor, self->handle);
CarlaPluginHandle plugin =
  carla_engine_get_plugin (
    engine, self->carla_plugin_id);

/* use it */
carla_plugin_show_custom_ui (
  plugin, 1);

The general idea is to hide the C++ class instance under a void * so it can be used in C, and instead of calling class functions on the class instance, call C functions normally by passing that pointer around.

The interface implementation converts that pointer back to the class instance and calls the instance methods.

By Alexandros Theodotou in
Tags : #programming, #c, #cpp,