Как взаимодействовать между иностранным языком и библиотекой C ++, которая возвращает общие указатели - PullRequest
2 голосов
/ 05 июня 2019

Я пишу библиотеку, которая взаимодействует между kdb + (хотя этот вопрос применим к интерфейсам на иностранных языках в целом) и библиотекой C ++, где большинство вызовов API возвращают std::shared_ptr. При взаимодействии с большинством библиотек с помощью kdb + типично создавать объекты с использованием API библиотеки, а затем возвращать их необработанный указатель как long long, чтобы программист kdb + мог отправить объект обратно в библиотеку по своему выбору.

Общие указатели затрудняют это. Минимальный пример:

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 

Я хотел бы знать, есть ли способ продлить время жизни std::shared_ptr до бесконечности или иным образом предотвратить уничтожение данных, на которые он указывает, до тех пор, пока программист вручную не освободит их из kdb + с помощью другого вызова в эту интерфейсную библиотеку. Я хорошо знаю, что то, о чем я прошу, побеждает цель использования умных указателей; Я хотел бы знать хороший способ справиться с этим, если такая вещь существует и является практичной.

Ответы [ 3 ]

3 голосов
/ 05 июня 2019

Единственный способ продлить срок службы такого общего объекта - сохранить shared_ptr в памяти до тех пор, пока общий объект больше не понадобится.

Например, вы можете new отдельный std::shared_ptr и вернуть его на иностранный язык, а затем delete, когда вы закончите, используя его:

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);
}

Кроме того, вы можете сохранить shared_ptr в принадлежащем вам глобальном контейнере, а затем передать значения, относящиеся к его элементам:

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);
}
2 голосов
/ 05 июня 2019

Я понимаю вашу точку зрения, что пройти через это беспорядок.

Позвольте мне предположить, что у вас есть не просто новый метод, у вас также есть явный метод удаления. В этом случае вы можете выделить shared_ptr.

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>(new std::shared_ptr<library::Foo>(ptr)));
}

extern "C" void k_delete_foo(K k) {
     delete reinterpret_cast<std::shared_ptr<library::Foo> *>(k.foo));
}

extern "C" void k_thread_foo(K k) {
     auto &ptr = *reinterpret_cast<std::shared_ptr<library::Foo> *>(k.foo));
     std::thread{[ptr]{ /* Do something */ }}.detach();
}

Конечно, это не очень хороший способ кодирования. Тем не менее, он позволяет вам сохранить shared_ptr для всего вашего кода C ++, при этом возможны утечки памяти / ub при неправильном использовании из другого языка.

Если вы хотите защитить от неправильного использования. Вместо этого вы можете собрать все указатели в какой-то глобальной переменной. Таким образом, вы можете сначала проверить, является ли этот указатель общим для другого языка. Вместо передачи нового shared_ptr, вы можете поделиться с ним необработанным указателем, и поведение может стать более определенным, так как при выходе будут вызваны все деструкторы. Недостаток включает в себя больше бухгалтерии, что будет заметно как снижение производительности.

Если вы просто хотите прикрепить владение к необработанному указателю, который вы передаете, без такого метода удаления, я могу порекомендовать вам какую-то утечку памяти, собирая все в глобальный вектор.

1 голос
/ 05 июня 2019

Что вы можете сделать, это сохранить контейнер shared_ptr<Foo> в вашем интерфейсе. Ваша k_new_foo функция сохранит копию shared_ptr в этом контейнере. Затем функция k_delete_foo удалит shared_ptr из контейнера.

Это не будет работать, если слишком много разных типов объектов для отслеживания.

...