Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C and C++ interface for OO based C++ library

Tags:

c++

c

I am currently developing a library in C++ (Mainly because dependent libraries have C++ interfaces). I created a proof-of-concept implementation with C++ interfaces for quick development. The library has to mandatorily provide C interface for it. The C++ interface is good to have but can be modified/removed if it gets in the way of the C interface.

The C++ API right now looks something like this:

typedef struct {
    // only basic C data types like int,float.
    int a;
    float b;
} Object;

typedef struct {
std::vector<Object> objects;
} GroupOfObjects;

typedef struct {
    std::vector<GroupOfObjects> groups;
} Result;

typedef struct {
   // some basic C data types
   int init1;
   int init2;
   float init3;
   // some C++ types which I can possibly replace with something compatible with C
   std::string init4;
   std::vector<std::string> init5;   
} MyClassInitParams;

struct IMyClass {
public:
    virtual bool initialize(MyClassInitParams &params) = 0;
    virtual bool getResult(Result &result) = 0;
    //Other public methods, constructor, virtual destructor
}

// Actual implementation of the above interface
class MyClass : IMyClass {
}

IMyClass *createMyClassInstance();

I have come up with this C interface till now:

extern "C" {

typedef struct MyClass *MyClassHandle;

// A C version of the above MyClassInitParams
typedef struct{

} MyClassInitParams_C;

typedef struct {
    Object * objects;
    int numObjects;
} GroupOfObjects_C;

// A C version of the above Result structure
typedef struct{
    GroupOfObjects_C *groups;
    int numGroups;
}Result_C;

MyClassHandle MyClass_Create();

MyClass_Destroy(MyClassHandle handle);

int MyClass_Initialize(MyClassHandle handle, MyClassInitParams_C *params);

int MyClass_GetResult(MyClassHandle handle , Result_C *result);

void MyClass_FreeResult(Result_C *result);

} // end of extern "C" 

Implementation of the C interface:

MyClassHandle MyClass_Create()
{
    return createMyClassInstance();
}

MyClass_Destroy(MyClassHandle handle)
{
    delete handle;
}

int MyClass_Initialize(MyClassHandle handle, MyClassInitParams_C *params)
{
    MyClassInitParam params_cpp;
    // fill params_cpp using the params structure

    return handle->initialize (params_cpp); 
}

int MyClass_GetResult(MyClassHandle handle , Result_C *result)
{
    Result result_cpp;
    bool ret = handle->getResult(result_cpp);

    if (!ret)
        return 0;

    // Fill the C structure using the cpp structure
    result->numGroups = result_cpp.groups.size();
    result->groups = new GroupOfObjects_C[result->numGroups];
    for (int i = 0; i < result->numGroups; i++) {
        result->groups[i].numObjects = result_cpp.groups[i].objects.size();
        result->groups[i].objects = new Object[result->groups[i].numObjects];
        for (int j = 0; j < result->groups[i].numObjects; j++) {
            result->groups[i].objects[j] = result_cpp.groups[i].objects[j];
        }
    }

    return 1;
}

void MyClass_FreeResult(Result_C *result) {
    // free all the arrays allocated in the above function
}

I have a few questions regarding this:

  1. The GetResult method has a overhead of copying the objects from C++ vectors to the C arrays. Is there a more elegant and efficient way to handle this?
  2. I will have to maintain the structures for both C and C++. Should I just use the C versions of the MyClassInitParams and Result structures in C++ interface? This will also help with (1).
  3. If I use the solution in (2), does it make sense to even have the C++ interface? Or are there any advantages of keeping both the C and C++ interfaces in this case?
like image 569
user10880529 Avatar asked Nov 07 '22 22:11

user10880529


1 Answers

  1. I'd suggest to return Result* from C MyClass_GetResult method, like int MyClass_GetResult(MyClassHandle handle, Result_C **result) or Result_C* MyClass_GetResult(MyClassHandle handle). Then, add accessors for groups and objects.

  2. It's up to you to decide, but I'd rather to use one or the other, but not both.

  3. First of all, I'd suggest to decide what language and its features (C or C++) you're going to use in order to implement your business logic. Next, another language becomes nothing more than a wrapper over logic implemented in another language. Again, if you use functions for accessing actual underlying data you'll get rid of copying this data, as you did in MyClass_GetResult method.

Here's an example

struct Object {
    int a;
    float b;
};

struct GroupOfObjects;
struct Result;
struct MyClass;

#ifdef __cplusplus

#include <vector>
struct GroupOfObjects {
    std::vector<Object> objects;
};

struct Result {
    std::vector<GroupOfObjects> groups;
};

struct MyClass {
private:
public:
    Result getResult() { /*...*/ }
    MyClass(int init1, int init2, float init3, const std::string& init4, const std::vector<std::string>& init5);
}

#endif

#ifdef __cplusplus
extern "C" {
#endif __cplusplus

struct Object* GroupOfObjects_GetObject(struct GroupOfObjects* g, size_t i)
/* { return &g.objects[i]; } */  // commented sections must be in cpp file, not in this header
size_t GroupOfObjects_GetCount(struct GroupOfObjects* g)
/* { return g.objects.size(); } */

struct GroupOfObjects* Result_GetGroup(struct Result* r, size_t i)
/* { return &r.groups[i]; } */
size_t Result_GetGroupCount(struct Result* r)
/* { return g.groups.size(); } */

MyClass *CreateMyClassInstance(int init1, int init2, float init3, const char* init4, const char** init5)
/* {
    try {
        std::vector<std::string> init5_v;
        while (init5 != nullptr)
            init5_v.push_back(std::string(*init5++));
        return new MyClass(init1, init2, init3, std::string(init4), init5_v);
    }
    catch (...) {
        return nullptr;
    }
} */

void FreeMyClassInstance(struct MyClass* mc) 
/* { delete mc; } */

Result* MyClass_GetResult(struct MyClass* mc) 
/* {
    Result *result = nullptr;
    try {
        result = new Result;
        *result = mc->GetResult();
        return result;
    }
    catch (...) {
        delete result;
        return nullptr;
    }
} */

void FreeResult(struct Result* r)
/* { delete r; } */

#ifdef __cplusplus
} // end of extern "C" 
#endif
like image 185
Vyacheslav Napadovsky Avatar answered Nov 15 '22 05:11

Vyacheslav Napadovsky