Search code examples
c++c++17shared-ptrsmart-pointerskdb

How to interface between a foreign language and a C++ library which returns shared pointers


I'm writing a library which interfaces between kdb+ (although this question is applicable to foreign language interfaces in general), and a C++ library where most of the API calls return a std::shared_ptr. When interfacing with most libraries with kdb+, it is typical to create objects using the library's API, then return their raw pointer as a long long so that the kdb+ programmer can send the object back into the library however they choose.

Shared pointers make this difficult. Minimal example:

extern "C" K k_new_foo() {
    // not using auto for the sake of clarity in the example
    std::shared_ptr<library::Foo> ptr = library::Foo::Create();
    // return the raw pointer as a long long int in a kdb object
    return kj(reinterpret_cast<long long>(ptr.get()));
}
// ptr goes out of scope -> the library::Foo is freed prematurely 

What I would like to know is if there is some way to extend the lifetime of the std::shared_ptr indefinitely, or to otherwise prevent the destruction of the data it points to until the programmer manually frees it from within kdb+ using another call to this interface library. I'm well aware that what I'm asking for defeats the purpose of using smart pointers; I'd love to know a good way of handling this if such a thing exists, and is practical.


Solution

  • The only way to extend the lifetime of such a shared object is to store the shared_ptr in memory until the shared object is no longer needed.

    For instance, you could new a separate std::shared_ptr and return that to the foreign language, and then delete it when you are done using it:

    using Foo_sharedptr = std::shared_ptr<library::Foo>;
    
    extern "C" K k_new_foo() {
        Foo_sharedptr ptr = library::Foo::Create();
        Foo_sharedptr *ptr2 = new Foo_sharedptr(ptr);
        return kj(reinterpret_cast<J>(ptr2));
    }
    
    extern "C" void k_free_foo(K foo) {
        delete reinterpret_cast<Foo_sharedptr*>(foo->j);
        r0(foo);
    }
    

    Alternatively, you could store the shared_ptr in a global container that you own, and then pass around values that refer to its elements:

    using Foo_ptr = library::Foo*;
    using Foo_sharedptr = std::shared_ptr<library::Foo>;
    using FooMap = std::map<Foo_ptr, Foo_sharedptr>;
    
    static FooMap g_foos;
    // wrapped with a std::mutex if you need multithread safety...
    
    extern "C" K k_new_foo() {
        Foo_sharedptr foo = library::Foo::Create();
        Foo_ptr ptr = foo.get();
        g_foos[ptr] = foo;
        return kj(reinterpret_cast<J>(ptr));
    }
    
    extern "C" void k_free_foo(K foo) {
        Foo_ptr ptr = reinterpret_cast<Foo_ptr>(foo->j);
        FooMap::iterator iter = g_foos.find(ptr);
        if (iter != g_foos.end()) g_foos.erase(iter);
        r0(foo);
    }