Вызов C ++ из D

17

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

Взяв пример, представленный на веб-сайте 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;
}

Затем класс C ++ можно вызвать из 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);
}

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

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

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

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

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

};

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

    
задан BigONotation 02.01.2014 в 23:55
источник

4 ответа

3

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

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

Я написал небольшую небольшую программу под названием dtoh pending review прямо сейчас, которая может помочь автогенерировать заголовки C ++ из источника D, чтобы это было просто. Это еще не закончено, но это может быть полезно в любом случае: Ссылка

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

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

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

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

    
ответ дан Adam D. Ruppe 03.01.2014 в 03:11
  • Так значит ли это, что я могу добавить деструктор в класс C ++ до тех пор, пока он не является виртуальным, чтобы он не испортил методы, соответствующие интерфейсу D? Еще один момент, который я не совсем понимаю, - это то, как порядок методов будет соответствовать. Например, если интерфейс D определяется следующим образом: interface D {void foo (); } –  BigONotation 03.01.2014 в 21:00
  • Да, это должно сработать, добавив dtor. НО, D ничего не узнает об этом и никогда не назовет его. Порядок этих методов не гарантирует соответствия, это ваша ответственность за право, убедившись, что функции отображаются в том же порядке как в D, так и в C ++. –  Adam D. Ruppe 03.01.2014 в 21:17
  • Я думал, что для C ++ порядок виртуальных методов в vtbl зависит от компилятора. Другими словами, нет никакой гарантии, что методы vtbl будут в том же порядке, что и их объявление в определении класса. Я что-то упускаю? –  BigONotation 03.01.2014 в 21:54
  • Я не уверен в том, что говорит стандарт, но я пробовал это несколько раз, и это работает на практике. –  Adam D. Ruppe 03.01.2014 в 22:50
  • Это не часть стандарта: stackoverflow.com/questions/3674929/... Вот почему я немного волнуюсь, что мой код будет тихо сломаться, если я переключу компиляторы / версии ... –  BigONotation 04.01.2014 в 13:59
7

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

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

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

    
ответ дан Aaron 03.01.2014 в 00:30
2

Вам нужно добавить метод в свой класс D, который вызывает оператор c ++ 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.

    
ответ дан Noishe 03.01.2014 в 00:50
  • Вам необязательно нужен extern C, поскольку D имеет (частично, но достаточно хорошо для ths) extern (C ++) поддержку, чтобы понять манипуляцию. –  Adam D. Ruppe 03.01.2014 в 17:59
  • Таким образом, ваш код будет работать до тех пор, пока вы не будете использовать новый компилятор c ++, который D никогда ранее не видел. Или ваш компилятор c ++ изменяет свою схему переключения при обновлении. –  Noishe 04.01.2014 в 02:53
  • да, это могут быть проблемы (хотя они также нарушали код C ++ в дикой природе, но, похоже, это произошло раньше). Я согласен с тем, что максимальная совместимость будет заключаться в использовании C-интерфейсов. –  Adam D. Ruppe 04.01.2014 в 03:20
1

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

  

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

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

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

  

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

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

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

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

    
ответ дан DejanLekic 03.01.2014 в 17:42
  • , я не согласен с полномасштабной компилятором C ++, по крайней мере, не для большинства вещей. Это было бы верно для чтения заголовочного файла C ++, но вызов функции не нуждается в этом. Это в основном просто вызов конвенции (получил его), искалечение (есть большая часть), а макеты данных (сложно с заголовком, но может быть сделано - действительно ничем не отличается от использования C-структур). Даже шаблоны C ++ в основном сводятся к искажению, так как загрузка c ++ shared lib фактически не создает экземпляр шаблона, он просто выглядит как любая другая функция. Думаю, это не имеет значения для вопроса здесь. –  Adam D. Ruppe 03.01.2014 в 17:57
  • Я бы не упрощал «поддержку на C ++» для вызова функции C ++ ... :) Idk о вас, но я полностью согласен с Уолтером в этом. (На 100% совместимость с C ++ означает более или менее добавление полностью функционального C ++-компилятора к концу D. Анекдотические данные говорят о том, что написание таких файлов - это минимум 10 человеко-летних проектов, в результате чего компилятор D с такой возможностью не реализуется). –  DejanLekic 03.01.2014 в 18:26
  • eh, я бы тоже не упрощал это, но D может приблизиться, объединив это с его собственной способностью делать деструкторы. Конечно, не 100% поддержка C ++, но достаточно, чтобы по-настоящему использовать общие библиотеки C ++. –  Adam D. Ruppe 03.01.2014 в 21:19
  • Вот почему я считаю, что нынешний подход очень хорош - прагматичный, как сам язык D. Я не могу дождаться поддержки пространства имен C ++. Это единственное, что мне действительно нужно в данный момент. –  DejanLekic 03.01.2014 в 21:46