How do I wrap a C++ function that returns a smart pointer in C?
Asked Answered
K

5

13

I need to wrap some APIs in a C++ library in C. I have done this in the past, using opaque pointers to class objects, extern "C", etc. as described here. However, this new library I'm dealing with makes extensive use of reference-counting smart pointers. I'm not sure how to do the wrapping in the presence of smart pointers. For example, let's say the C++ library has the following function:

SmartPointer<MyClass> foo() {
    SmartPointer<MyClass> ret(new MyClass); // Create smart pointer ret
    ret->DoSomething(); // Do something with ret
    return ret;
}

How can I wrap foo() in C? Clearly, I need an opaque pointer (like void* or an empty struct pointer) that I can use in a C function to refer to the MyClass object. The first option I thought of was to extract the MyClass object from ret, and cast it into a void*. However, this void* will become dangling once ret goes out of scope due to the automatic deletion done by the smart pointer (correct me if I'm wrong).

The other option is to allocate a pointer to the smart pointer (say retPtr), do *retPtr=ret, and then create an opaque pointer to retPtr. I think this option might work, but is this the best way?

Any help is appreciated.

Kerriekerrigan answered 13/10, 2016 at 21:46 Comment(7)
I agree with your last suggestion.Kindless
What kind of smart pointer, I know no class called SmartPointer in the stdlib. This depends enormously on the semantics of the smart pointer in question...Bahaism
does the C library actually need to access members of ObjectType? or does it just hold the pointer for a while and release it later? Also what is the relationship between ObjectType and MyClass ?Sororate
@Vitaly SmartPointer is a templated class defined in the C++ library that I'm using. It's a reference-counting smart pointer. I'm still going through the implementation of SmartPointer to ensure that I understand exactly how it works.Kerriekerrigan
@Sororate No, the C library does not need to know the type - it just needs to hold the pointer and pass it to the C++ library APIs as needed. ObjectType was a typo, sorry - it should be MyClass. Corrected.Kerriekerrigan
The C++library APIs in question - do they take MyClass *, or SmartPointer<MyClass> ?Sororate
They take SmartPointer<MyClass>Kerriekerrigan
S
4

The comments indicate that the C code needs to hold the smart pointer but does not need to do anything with it other than pass it verbatim to another C++ function. The code for that could be:

// Shared  header
#ifdef __cplusplus
extern "C" {
#endif

void * foo_acquire(void);
int  foo_bar(void *);
void foo_release(void *);

#ifdef __cplusplus
}
#endif

// C++ implementation
extern "C" void *foo_acquire()
{
    return new SmartPointer<MyClass>( foo() );
}

extern "C" int foo_bar(void *s)
{
    auto& sp = *static_cast< SmartPointer<MyClass> * >(s);
    return bar(sp);   // bar represents some function expecting the smart pointer
}  

extern "C" void foo_release(void *s)
{
    delete static_cast<SmartPointer<MyClass> *>(s);
}

This uses the move-constructor of SmartPointer (or copy-constructor if it didn't have a move-constructor), an operation that should be supported by smart pointers.

If you want to prevent implicit conversions in the C code, you could use an opaque handle instead of void *. (E.g. a struct with a void * as member).

Sororate answered 14/10, 2016 at 1:10 Comment(0)
D
5

You pretty much have to return an opaque pointer to the C code. So you'll need to use new to allocate a new smart pointer that you can return a pointer to.

About your only alternative is to use a collection of shared pointers to keep the object alive. When the C code indicates it's finished with the object, you remove the shared pointer from the collection. This allows you to return any kind of identifier you want to the C code -- it's just used as a handle to find the object in the collection.

Disorderly answered 13/10, 2016 at 21:49 Comment(1)
The idea of a handle is a good one - after all that's how FILE works in C.Ganger
S
4

The comments indicate that the C code needs to hold the smart pointer but does not need to do anything with it other than pass it verbatim to another C++ function. The code for that could be:

// Shared  header
#ifdef __cplusplus
extern "C" {
#endif

void * foo_acquire(void);
int  foo_bar(void *);
void foo_release(void *);

#ifdef __cplusplus
}
#endif

// C++ implementation
extern "C" void *foo_acquire()
{
    return new SmartPointer<MyClass>( foo() );
}

extern "C" int foo_bar(void *s)
{
    auto& sp = *static_cast< SmartPointer<MyClass> * >(s);
    return bar(sp);   // bar represents some function expecting the smart pointer
}  

extern "C" void foo_release(void *s)
{
    delete static_cast<SmartPointer<MyClass> *>(s);
}

This uses the move-constructor of SmartPointer (or copy-constructor if it didn't have a move-constructor), an operation that should be supported by smart pointers.

If you want to prevent implicit conversions in the C code, you could use an opaque handle instead of void *. (E.g. a struct with a void * as member).

Sororate answered 14/10, 2016 at 1:10 Comment(0)
L
2

In addition to David Schwartz's answer, you may consider an approach similar to COM's IUnknown. You may define a struct containing function pointers (simulating a C++ interface in pure C), and expose a couple of methods like AddRef and Release, to increase and release the ref count.

The caller gets a pointer to that structure, so he/she can control the proper lifetime of the returned object using AddRef and Release.

In addition, other methods (function pointers) can be added in the struct to expose other functionalities of the returned object.

This article on COM in plain C explains things in detail.

Labaw answered 13/10, 2016 at 22:33 Comment(0)
P
0

Expose a struct to the C code rather than just the pointer. In the said struct, have the pointer and some indicator of its state. Add behavior to the smart pointer so that it is aware of the struct. The struct would contain both a pointer and the state of its allocated object. So the smart pointer's extra behavior would have to update the state of the allocated object in the struct (for example, setting it to some value when the smart pointer does the deallocation).

Popinjay answered 14/10, 2016 at 1:14 Comment(0)
T
0

The comments indicate that the C code needs to hold something and pass the SmartPointer to C++ API but does not need to do anything with it other than pass it verbatim to another C++ function.

I think you need to create some think like std::enable_shared_from_this, let's say EnableSharedFromThis:

Make your MyClass inherite from EnableSharedFromThis:

struct MyClass : public EnableSharedFromThis, public AnotherBaseClass {
//...
};

Shared header:

#ifdef __cplusplus
extern "C" {
#endif

struct MyClass;
MyClass * foo_acquire(void);
int  foo_bar(MyClass *);
void foo_release(MyClass *);

#ifdef __cplusplus
}
#endif

C++ implementation:

List<SmartPointer<MyClass> > listToEnsureLifeTime;
extern "C" MyClass * foo_acquire()
{
    SmartPointer<MyClass> ptr = foo();
    listToEnsureLifeTime.Add(ptr);
    return ptr.get();
}

extern "C" int foo_bar(MyClass *s)
{
    // bar represents some function expecting the smart pointer
    return bar(s->SharedFromThis());
}  

extern "C" void foo_release(MyClass *s)
{
    // I suppose this list can take difference SmartPointer with same
    // inner have the same hash or something like that
    listToEnsureLifeTime.erase(s->SharedFromThis());
}
Thiourea answered 14/10, 2016 at 1:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.