Skip to content

Instantly share code, notes, and snippets.

@jlschrag
Last active January 6, 2021 16:03
Show Gist options
  • Save jlschrag/01d49e90c52647825e65554fb8dfc2fa to your computer and use it in GitHub Desktop.
Save jlschrag/01d49e90c52647825e65554fb8dfc2fa to your computer and use it in GitHub Desktop.
Designing a C interface for complex types
auto resort = ExternalInterface_GetSkiResort("Tahoe");
auto blackRuns = resort.GetRunsByDifficulty(resort.Resort, SkiRunDifficultyRating::Black);
//Process the runs list
resort.Destroy(resort.Resort);
blackRuns.Destroy(blackRuns.Collection);
IExternalInterface_SkiResort ExternalInterface_GetSkiResort(char* name)
{
auto resort = SkiResortLookup::GetResortByName(name);
IExternalInterface_SkiResort result;
result.Resort = reinterpret_cast<ExternalInterface_SkiResort*>(&resort);
result.GetName = IExternalInterface_SkiResort_GetName;
result.GetRunsByDifficulty = ExternalInterface_SkiResort_GetRunsByDifficulty;
return result;
}
const char* IExternalInterface_SkiResort_GetName(ExternalInterface_SkiResort* resort)
{
return reinterpret_cast<SkiResort>(resort).GetName().c_str();
}
IExternalInterface_SkiRunCollection ExternalInterface_SkiResort_GetRunsByDifficulty(
SkiRunDifficultyRating difficulty)
{
auto runs = reinterpret_cast<SkiResort>(resort).GetRunsByDifficulty(difficulty);
//We could have the class return a heap-allocated collection,
//but that would tightly couple it to the interface.
//Also, we would have to return a raw pointer from SkiResort.GetRunsByDifficulty(),
//but that is poor C++ practice.
auto heapAllocatedRuns = new std::vector<SkiRun>(runs);
IExternalInterface_SkiRunCollection result;
result.Collection = reinterpret_cast<ExternalInterface_SkiRunCollection*>(heapAllocatedRuns);
result.GetRunByIndex = IExternalInterface_SkiRunCollection_GetRunByIndex;
result.GetRunCount = IExternalInterface_SkiRunCollection_GetRunCount;
result.Destroy = IExternalInterface_SkiRunCollection_Destroy;
return result;
}
ExternalInterface_SkiRun* IExternalInterface_SkiRunCollection_GetRunByIndex(
ExternalInterface_SkiRunCollection* collection,
uint8_t index)
{
return (*reinterpret_cast<std::vector<SkiRun>*>(collection))[index];
}
const uint8_t IExternalInterface_SkiRunCollection_GetRunCount(
ExternalInterface_SkiRunCollection* collection)
{
return reinterpret_cast<std::vector<SkiRun>*>(collection)->size();
}
void IExternalInterface_SkiRunCollection_Destroy(
ExternalInterface_SkiRunCollection* collection)
{
delete reinterpret_cast<std::vector<SkiRun>*>(collection);
}
extern "C"
{
typedef void ExternalInterface_SkiRunCollection;
typedef struct IExternalInterface_SkiRunCollection
{
ExternalInterface_SkiRunCollection* Collection;
ExternalInterface_SkiRun* (*GetRunByIndex)(
ExternalInterface_SkiRunCollection* collection,
uint8_t index);
const uint8_t (*GetRunCount)(ExternalInterface_SkiRunCollection* collection);
//Having the destroy method as part of the return struct makes it obvious
//who is responsible for memory management. This can be reinforced with a comment.
void (*Destroy)(ExternalInterface_SkiRunCollection* collection);
} IExternalInterface_SkiRunCollection;
typedef void ExternalInterface_SkiResort;
typedef struct IExternalInterface_SkiResort
{
ExternalInterface_SkiResort* Resort;
const char* (*GetName)(ExternalInterface_SkiResort* resort);
IExternalInterface_SkiRunCollection (*GetRunsByDifficulty)(
ExternalInterface_SkiResort* resort,
SkiRunDifficultyRating difficulty);
} IExternalInterface_SkiResort;
typedef void ExternalInterface_SkiRun;
typedef struct IExternalInterface_SkiRun
{
ExternalInterface_SkiRun* Run;
const char* (*GetName)(ExternalInterface_SkiRun* run);
SkiRunDifficultyRating (*GetDifficulty)(ExternalInterface_SkiRun* run);
} IExternalInterface_SkiRun;
ExternalInterface_SkiResort ExternalInterface_GetSkiResort(const char* name);
}
const char* IExternalInterface_SkiResort_GetName(ExternalInterface_SkiResort* resort);
IExternalInterface_SkiRunCollection ExternalInterface_SkiResort_GetRunsByDifficulty(
SkiRunDifficultyRating difficulty);
ExternalInterface_SkiRun* IExternalInterface_SkiRunCollection_GetRunByIndex(
ExternalInterface_SkiRunCollection* collection,
uint8_t index);
const uint8_t IExternalInterface_SkiRunCollection_GetRunCount(
ExternalInterface_SkiRunCollection* collection);
void IExternalInterface_SkiRunCollection_Destroy(
ExternalInterface_SkiRunCollection* collection);
ExternalInterface_SkiResort* output = nullptr;
ExternalInterface_GetSkiResort("Tahoe", output);
ExternalInterface_SkiRun* runs;
uint8_t runCount;
ExternalInterface_SkiResort_GetRunsByDifficulty(
SkiRunDifficultyRating.Black,
runs,
&runCount);
//Process the runs list
for (size_t i = 0; i < runCount; ++i)
{
delete runs[i];
}
delete output;
ExternalInterface_GetSkiResort(const char* name, ExternalInterface_SkiResort* output)
{
//Yes, I know GetResortByName() isn't static,
//but let's ignore the lifecycle of SkiResortLookup for brevity
SkiResort resort = SkiResortLookup::GetResortByName(name);
output = malloc(sizeof(ExternalInterface_SkiResort));
output.Name = resort.GetName().c_str();
}
ExternalInterface_SkiResort_GetRunsByDifficulty(SkiRunDifficultyRating difficulty,
ExternalInterface_SkiRun* runsOutput,
uint8_t* runCountOutput)
{
auto runs = resort.GetRunsByDifficulty(difficulty);
output.RunCount = runs.count();
output.Runs = malloc(sizeof(ExternalInterface_SkiRun) * output.RunCount);
for(size_t i = 0; i < runs.count(); ++i)
{
strcpy(output.Runs[i]->Name, runs[i].GetName().c_str());
output.Runs[i]->Difficulty = runs[i].GetDifficulty();
}
}
extern "C"
{
//First, we need a structure to pass across the interface.
//Perhaps the simplest option is this:
typedef struct ExternalInterface_SkiResort
{
const char* Name;
}
typedef struct ExternalInterface_SkiRun
{
const char* Name;
//There are problems with passing this enum, but for simplicity...
SkiRunDifficultyRating Difficulty;
}
//I'm no C programmer, but I think this is the traditional approach:
ExternalInterface_GetSkiResort(const char* name, ExternalInterface_SkiResort* output);
ExternalInterface_SkiResort_GetRunsByDifficulty(SkiRunDifficultyRating difficulty,
ExternalInterface_SkiRun* runsOutput,
uint8_t runCountOutput);
}
class SkiResortLookup
{
private:
std::unordered_map<SkiResort> Resorts;
public:
//Realistically, we would probably return std::optional<SkiResort> here,
//but for simplicity...
SkiResort& GetResortByName(std::string name) const;
}
class SkiResort
{
private:
std::string Name;
std::vector<SkiRun> Runs;
public:
const std::string& GetName() const;
std::vector<SkiRun> GetRunsByDifficulty(SkiRunDifficultyRating difficulty) const;
}
class SkiRun
{
private:
std::string Name;
SkiRunDifficultyRating Difficulty;
public:
std::string GetName() const;
SkiRunDifficultyRating GetDifficulty() const;
}
enum SkiRunDifficultyRating
{
Green,
Blue,
Black,
DoubleBlack,
Orange
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment