1

What are the best practices for returning read-only std::vector<uint8_t> by value from C++ API for consumption by C?

// cpplib.hpp
struct A {
  std::vector<uint8_t> getHash() const;
};


// c-api.h
typedef struct A A_t;

??? A_get_hash();

// c-api.cpp
extern "C" {
  ??? A_get_hash() {
    // return std::vector<uint8_t> to C here
    A a = doSomethingToGetA();
    return a.getHash(); ??? 
  }
}

One option that I considered was to define new struct in C representing this vector:

struct vector {
  const uint8_t* data;
  size_t size;
}

But, as a C++ programmer it hurts to allocate with new then return.

  • consumers will forget to deallocate memory for this array
  • given many functions, consumers will not know that some returned pointers are read-only (no deallocation needed), and some require deallocation.
warchantua
  • 1,154
  • 1
  • 10
  • 24
  • 1
    As far as I know (not an expert in C though) there is no such a thing as "read only" there. You pass a pointer to you vector structure to C function, it fills it out and usually returns an error code. There is no way to prevent any modificstions of you structure after the function call. And yeah, you're always responsible for deallocation of your structure (usually by calling free_* function). That's how C language works, I suppose (again not an expert, can be compleatly wrong). – Dmitry Feb 27 '21 at 13:49

1 Answers1

1

Where does your A instance live? What you can do is to return the struct you defined by value, pointing to the data held by a vector - no dynamic allocation needed. You need however to keep that C++ vector somewhere and there is no way around that - vector is by definition dynamically allocated.

In your example code it's always returning the same value, so you could have it stored statically:

extern "C" {
struct vector A_get_hash() {
    static A a;
    static std::vector<uint8_t> vec = a.getHash();
    return {vec.data(), vec.size());
}
}

If it's not the case, A_get_hash would need to reference the vector you get from somewhere else.

On the other hand, if the data is of known size, you can define the struct that owns the whole memory in a single block (so the vector is no longer dynamic):

struct hash {
    uint8_t data[32];
}

And then, again - return it by value. Without a need to keep reference for original A.

twasilczyk
  • 186
  • 4
  • There are many A instances, I wrote just simple example. It can't be static. – warchantua Feb 27 '21 at 16:10
  • And what if the size is not known? Are there best practices I can use to let consumers know that func get_hash actually allocated memory? – warchantua Feb 27 '21 at 16:12
  • 1
    It's the size bound by some reasonable value? You can define buffer with that max size plus length. If it's unbounded and can be very large (is it a hash?) then you won't get away with dynamic allocation. The usual way is to define hash_free(struct hash*) and mention it in a comment of a function that allocates it. – twasilczyk Feb 27 '21 at 17:47