Подтвердить что ты не робот

Вызов С++ из D

Я просмотрел документы, объясняющие, как вызвать С++ из D, описанного здесь: http://dlang.org/cpp_interface.html. Есть несколько вещей, которые мне не совсем понятны.

Взяв пример, представленный на веб-сайте D:

#include <iostream>

using namespace std;

class D {
   public:
   virtual int bar(int i, int j, int k)
   {
       cout << "i = " << i << endl;
       cout << "j = " << j << endl;
       cout << "k = " << k << endl;
       return 8;
   }
};

D *getD() {
   D *d = new D();
   return d;
}

Затем класс С++ можно вызвать из D, как показано ниже:

extern (C++) {
    interface D {
        int bar(int i, int j, int k);
    }

    D getD();
}

void main() {
   D d = getD();
   d.bar(9,10,11);
}

Мне не совсем понятно, как удалить объект С++. Вызывает ли вызов сборщика мусора D на объекте С++ или нам нужно предоставить функцию "deleter", которая удаляет объект и вызывает его вручную из D? Мне кажется, что если я добавлю деструктор в класс С++, он никогда не будет вызван. Также я заметил, что класс С++ должен объявлять функции-члены в том же порядке, что и в интерфейсе D (например, если я добавляю деструктор перед методом bar(), объект С++ не может быть вызван из D, но если деструктор объявлен после метода bar(), все работает нормально).

Также, если интерфейс D определяется как:

extern(C++){
   interface D{
       int bar();
       int foo();
   }
}

И соответствующий С++ класс задается:

class D{
public:
   virtual int bar(){};
   virtual int foo(){};

};

Как вы можете гарантировать, что виртуальные методы С++ vtbl будут созданы в том же порядке, что и методы, объявленные в интерфейсе D. Для меня нет никакой гарантии. Другими словами, как мы можем быть уверены, что D:: bar() будет в первой позиции в vtbl? Разве это не зависит от реализации/компилятора?

4b9b3361

Ответ 1

Конкретный способ, которым это реализовано, - это объект D, который просто имеет совместимую с С++ vtable. Таким образом, работают только виртуальные функции, и поскольку таблица выложена по индексу, они должны отображаться в том же порядке.

D не имеет понятия о конструкторах С++, деструкторах или любом другом специальном методе, но если они являются виртуальными, он может сбросить vtable.

Я написал небольшую небольшую программу под названием dtoh pending review прямо сейчас, которая может помочь автогенерировать заголовки С++ из источника D, чтобы это было просто. Это еще не закончено, но это может быть полезно в любом случае: https://github.com/adamdruppe/tools/blob/7d077b26d991dd5705e834900f66bea737a233b2/dtoh.d

Сначала скомпилируйте его, dmd dtoh.d, затем сделайте JSON из вашего D файла: dmd -X yourfile.d, затем запустите dtoh yourfile.json, и он должен выплюнуть полезный файл yourfile.h. Но, как я уже сказал, он еще не закончен и все еще ждет обзора для общего дизайна, поэтому он может сосать ужасно. Вы всегда можете делать то, что делаете сейчас, и делать это сами.

В любом случае объект, как показано в D, аналогичен классу * в С++. Вы всегда проходите его через указатель, поэтому никогда не строится конструкция или копирование.

D и С++ также не понимают друг друга в системах распределения памяти. Правило, которое я придерживаюсь, - это то, что твоя библиотека создает, твоя библиотека должна уничтожить. Поэтому, если ваша программа на С++ обновила его, убедитесь, что она также удалена на С++. Любой объект, который вы создаете в D для перехода на С++, также должен быть уничтожен D... и вы можете сделать это вручную. Если ваша функция С++ сохраняет ссылку на объект D, но в D нет ни одного, это может привести к сбору мусора! Таким образом, вы либо захотите убедиться, что всегда существует живая ссылка в D для жизни объекта, либо создайте ее самостоятельно, используя функции malloc и бесплатно.

Мне не нравится, что у вызывающего есть даже общий бесплатный(), так как версия не обязательно будет соответствовать. Я говорю, всегда предлагаю метод из вашей библиотеки на бесплатные вещи. Даже если его реализация просто бесплатна (ptr);, предоставив свою собственную функцию, она будет ясно, что она должна использоваться и дать вам защиту от таких несоответствий.

Ответ 2

Я бы не ожидал, что сборщик мусора D узнает, как освободить объект С++. Это означало бы (по крайней мере), что время выполнения D:

  • сделать предположения о времени выполнения С++, то есть как удалить объект С++
  • что объект больше не нужен другому С++-коду

Я уверен, что вам придется предоставить еще одну функцию С++, которая вызывает переданный ей объект. Фактически, многие библиотеки С++ (даже если они также используются с С++) имеют такой же шаблон в тех случаях, когда конструктор вызывается из библиотеки. Даже в прямом C обычно бывает плохой идеей распределять память в одной dll/exe и освобождать ее в другой. Это может сильно испортиться, если два двоичных файла не используют одну и ту же библиотеку времени выполнения.

Ответ 3

Вам нужно добавить метод в свой класс D, который вызывает оператор С++ delete. Или вы можете использовать глобальный метод уничтожения.

Кроме того, не забывайте, что любые интерфейсы на другом языке должны быть объявлены как extern "C", чтобы избежать изменения имени функции компилятора.

#include <iostream>

using namespace std;

class D {
   public:
   virtual int bar(int i, int j, int k)
   {
       cout << "i = " << i << endl;
       cout << "j = " << j << endl;
       cout << "k = " << k << endl;
       return 8;
   }

   // option 1
   virtual void destroy()
   {
       delete this;
   }
};

extern "C"
D *getD() {
   D *d = new D();
   return d;
}

// option 2
extern "C"
void killD(void* d) {
   delete d;
   return;
}

Затем в вашем коде d вам нужно создать предложение scope, которое вызывает метод destroy.

Ответ 4

Поскольку у вашего вопроса есть заголовок "Вызов С++ из D", я предполагаю, что вы пытаетесь интерфейс к С++.

Вызывается ли вызов сборщика мусора D на объекте С++ или нам нужно предоставить функцию "deleter", которая удаляет объект и вызывает его вручную из D?

Под "объектом С++" я предполагаю, что вы имеете в виду объект, выделенный с помощью оператора new. D не имеет понятия о объектах С++, созданных с помощью нового оператора С++. Поэтому, когда вам нужно удалить объект, выделенный С++, вы должны предоставить свой собственный код, чтобы освободить память.

Поддержка С++ в D очень ограничена по уважительной причине. - Полная поддержка С++ означает, что в D-компилятор должен быть включен полномасштабный компилятор С++ (с препроцессором С++). Это значительно затруднило бы реализацию компилятора D.

Также я заметил, что класс С++ должен объявлять функции-члены в том же порядке, что и в интерфейсе D (например, если я добавляю деструктор перед методом bar(), объект С++ не может быть вызван из D, но если деструктор объявлен после метода bar(), все работает нормально).

В этом конкретном случае, я считаю, вы сначала пишете класс С++, имея в виду, что он будет использоваться в проекте D, а затем вы напишите интерфейс D. Интерфейс D должен тесно соответствовать методам в классе С++, потому что компилятор D будет создавать виртуальную таблицу совместимую с С++.

Поддержка С++ улучшится, но D вряд ли получит полную поддержку на С++. Проделана работа по поддержке пространств имен С++ (улучшение, запрашиваемое сообществом D).

Так как D полностью поддерживает C, лучшая идея состоит в том, чтобы "сгладить" сложный код С++ на C так же, как это делается в статье " Mixed C и С++". Давным-давно я использовал аналогичный подход для вызова методов С++ из Delphi.