Home > Net >  Interfacing C iterators from C
Interfacing C iterators from C

Time:09-30

I'm writing a C backend for a C library and I want the C code to be able to iterate over the individual items of a forward iterator. In C the code which iterates over the items looks like this:

auto rng = wks.range(XLCellReference("A1"), XLCellReference("Q1"));
for (auto& cell : rng) {
   // do something with "cell" 
}

Now I want to export this code so that it is accessible from C. I'd like to be able to iterate over the items from C using a set of functions like this:

void *startiteration(void *cpp_obj);
void *getnextitem(void *cpp_obj, void *iter);
void finishiteration(void *cpp_obj, void *iter);

I'd imagine startiteration() to return an iterator pointer to the C code which would then be passed along with the C object pointer for all successive calls to getnextitem() and finishiteration().

But the problem here is that I can easily pass pointers of objects created in C using new between C and C code but I don't see how I could do the same with iterators since the iterators are returned by a class method and I don't think there's any way to turn the iterator into a pointer that I could pass to C code which would then pass it back to C during the iteration. Also, I don't know how I would "free" such an iterator.

Does anybody have some tips how I can iterate over a C forward iterator from C code? How should this be implemented?

EDIT

Implementation based on Silvio's suggestions:

struct myiter
{
    XLCellIterator begin;
    XLCellIterator end;
};
    
void *xlsx_startiteration(void *handle, int idx)
{
    XLDocument *doc = (XLDocument *) handle;
    XLWorkbook wb = doc->workbook();
    auto wks = wb.worksheet(wb.sheetNames()[idx-1]);    
    XLCellRange cr = wks.range(XLCellReference("A1"), XLCellReference("Q1"));   
    myiter *it = new myiter();
                
    it->begin = std::begin(static_cast<XLCellRange*>(cr));
    it->end = std::end(static_cast<XLCellRange*>(cr));
 
    return it;
}

CodePudding user response:

auto rng = wks.range(XLCellReference("A1"), XLCellReference("Q1"));
for (auto& cell : rng) {
   // do something with "cell" 
}

I like doing 1:1 relationship between C and C. The following code outputs 3 lines var=1 var=2 var=3.

By wrapping the objects inside structures, the C side only forward declarations of structures and pointers. The C side sees all the rest. Additionally, C side will get a warning when passing invalid pointer to the wrong function.

#include <cstdio>
#include <vector>
typedef int do_not_know_what_is_the_type;
typedef std::vector<do_not_know_what_is_the_type> XLCellRange;
typedef XLCellRange::iterator XLCellIterator;


// header file

// C side
#ifdef __cplusplus
extern "C" {
#endif

struct wks_range_it_s;
void wks_range_it_inc(struct wks_range_it_s *);
bool wks_range_it_ne(struct wks_range_it_s *, struct wks_range_it_s *);
do_not_know_what_is_the_type wks_range_it_deref(struct wks_range_it_s *);
void wks_range_it_destruct(struct wks_range_it_s *);

struct wks_range_s;
struct wks_range_s *wks_range_construct();
void wks_range_destruct(struct wks_range_s *);
struct wks_range_it_s *wks_range_begin(struct wks_range_s *);
struct wks_range_it_s *wks_range_end(struct wks_range_s *);

#ifdef __cplusplus
}
#endif

#ifdef __cplusplus

// C   side
struct wks_range_s {
    XLCellRange rng;
};

struct wks_range_it_s {
    XLCellIterator it;
};

extern "C"
void wks_range_it_inc(struct wks_range_it_s *t) {
      t->it;
}

extern "C"
bool wks_range_it_ne(struct wks_range_it_s *a, struct wks_range_it_s *b) {
    return a->it != b->it;
}

extern "C"
do_not_know_what_is_the_type wks_range_it_deref(struct wks_range_it_s *t) {
    return *t->it;
}

extern "C"
void wks_range_it_destruct(struct wks_range_it_s *t) {
    delete t;
}

extern "C"
struct wks_range_s *wks_range_construct() {
     // return new struct wks_range_s(wks.range(XLCellReference("A1"), XLCellReference("Q1")));
    return new wks_range_s{{1,2,3}};
}

extern "C"
void wks_range_destruct(struct wks_range_s *t) {
     delete t;
}

extern "C"
struct wks_range_it_s *wks_range_begin(struct wks_range_s *t) {
    return new wks_range_it_s{t->rng.begin()};
}

extern "C"
struct wks_range_it_s *wks_range_end(struct wks_range_s *t) {
    return new wks_range_it_s{t->rng.end()};
}

#endif

int main() {
   // C code example
   struct wks_range_s *rng = wks_range_construct();
   // Almost 1:1 relationship with C   range loop.
   for(struct wks_range_it_s *begin = wks_range_begin(rng),
                *end = wks_range_end(rng);
                wks_range_it_ne(begin, end) ? 1 : (
                    // Yes, I'm sneaky.
                    wks_range_it_destruct(begin),
                    wks_range_it_destruct(end),
                    0); 
                wks_range_it_inc(begin)
            ) {
        do_not_know_what_is_the_type var = wks_range_it_deref(begin);
        // do something with var here?
        printf("var=%d\n", var);
    }  
    wks_range_destruct(rng);
}
    

CodePudding user response:

Forward iterators in C are regular, which means they can be copied. In fact, in our case, we would only need to move them. Specifically, assuming the type of the iterator in question is I, then startiteration could do something like this.

struct Range {
  I begin;
  I end;
};

void* startiteration(void* cpp_obj) {
  Range* my_range = new Range();
  my_range->begin = std::begin(*static_cast<MyCppType*>(cpp_object));
  my_range->end = std::end(*static_cast<MyCppType*>(cpp_object));
  return my_range;
}

Just because the forward iterator is originally returned by value doesn't prohibit us from putting it into a pointer, either directly or indirectly. In our case, we grab both the beginning and the end and store them in a (heap-allocated) structure. Then finishiteration, of course, would be responsible for deleteing the constructed range object.

  •  Tags:  
  • c c
  • Related