Last active
January 6, 2021 16:03
-
-
Save jlschrag/01d49e90c52647825e65554fb8dfc2fa to your computer and use it in GitHub Desktop.
Designing a C interface for complex types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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