Объединение больших программ на C и С++

Я читал о нескольких методах комбинирования кодов C и С++, однако я все еще запутался в том, как действовать в моем случае. Вот моя проблема:

У меня относительно большое количество кода С (состоящее из различных файлов .c и .h), которое используется для моделирования твердых тел в конечных и дискретных элементах. Этот код имеет относительно короткую и простую основную функцию с циклом for, в котором различные другие функции (из других файлов) называются последовательно. Этот код отлично работает при компиляции как в Unix (icc-компилятор), так и в Visual Studio.

У меня есть другой код на С++, который решает взаимодействие молекулярной динамики. Этот код также состоит из различных файлов и отлично работает как в Unix (icpc-компилятор), так и в VS. Оба являются автономными программами со своим собственным набором входных и выходных файлов.

Что мне нужно сделать - это запустить обе программы таким образом, чтобы моя программа C вызывала код С++ в своем основном цикле. Некоторая информация должна передаваться в обоих направлениях между двумя кодами, которые могут быть в виде массивов (или указателей).

Каков самый простой способ сделать это?

В частности, у меня есть несколько вопросов, основанных на рекомендациях, которые я прочитал:

  • Должен ли я обернуть файлы заголовков C с помощью extern "C" {}?
  • Должен ли я использовать extern "C" в моих C-функциях?
  • Или мне следует использовать extern "C" в моих файлах на С++? (заголовки? функции "все из них" или только те, которые мне нужно вызвать из программы C?)
  • В понимании я не могу иметь две функции main. Можно ли просто переименовать мою С++ main функцию?
  • При компиляции в unix я должен использовать компиляторы C (icc) и С++ (icpc) для разных файлов? или просто компилятор С++?
  • Может ли это быть (упростить) преобразование моей функции main из C в С++?
  • Если мне не нужно передавать информацию о классах между этими двумя программами, нужно ли что-нибудь сделать с ними?
  • В каком порядке вы предлагаете решить эту проблему? (например, сначала моя программа на языке C, скомпилированная компилятором С++, во-вторых, скомпилируйте оба кода вместе без ссылок, в-третьих, свяжите коды, в-четвертых, переименуйте main в С++ и попросите его "вызвать" по моему C-коду; передача информации?)
  • Наконец, в каждой программе есть несколько макросов, которые повторяются (одно и то же имя, одна и та же реализация). Есть ли конфликт с этим? Должен ли я хранить только один набор макросов?

Извините за длинный текст и несколько вопросов. Я относительно новичок в C и даже новичок в С++, поэтому даже мой словарь по этим программам ограничен.

Спасибо за помощь. Любые подсказки будут оценены. Если вам нужна дополнительная информация, пожалуйста, дайте мне знать.

Вот главная функция моего кода C:

#include "Yproto.h"
void show_time_info(YDC ydc,CHR Ystage[3]);

main(argc, argv)
  INT argc; char **argv;
{ CHR c1name[300];         /* name of the problem i.e. input file */
  struct YD_struct yd;     /* Y database                          */
  YDC ydc=&(yd.ydc);       /* Y control database                  */
  YDE yde=&(yd.yde);       /* Y element database                  */
  YDI ydi=&(yd.ydi);       /* Y interaction database              */
  YDN ydn=&(yd.ydn);       /* Y node database                     */
  YDB ydb=&(yd.ydb);       /* Y borehole database                 */
  YDS yds=&(yd.yds);       /* Y source (inter. fluid) database    */
  YDO ydo=&(yd.ydo);       /* Y output database                   */
  YDPE ydpe=&(yd.ydpe);    /* Y property database  for elements   */
  YDPN ydpn=&(yd.ydpn);    /* Y property database  for nodes (BC) */
  YDPJ ydpj=&(yd.ydpj);    /* Y property database  for joints     */
  YDPM ydpm=&(yd.ydpm);    /* Y property database  for meshing    */
  INT Tctrlc, itimes=0;
  CHR *p=NULL;

  /* get name of the problem */
  if(argv[1]!=NULL)
  { CHRcpy(c1name,argv[1]);
  }
  else
  { CHRwcr(stdout);
    CHRw(stdout,"  please define input file names: "); CHRwcr(stdout);
    CHRw(stdout," >");
    fgets(c1name,sizeof(c1name),stdin);
    if((p=strrchr(c1name,'\n'))!=NULL) *p = '\0';
  }
  strcpy(ydc->cfiname, c1name);   ydc->cfiname[255]='\0';
  ydc->finp=FILENULL; ydc->fcheck=FILENULL;

  /* Process while any input */
  while(Yrd(c1name,&yd)>0)
  { itimes=itimes+1;
    CHRw(stdout,"NEW INPUT: "); CHRw(stdout, c1name); CHRwcr(stdout);
    if(Ycheck(&yd)<0) break; date_and_time(ydc->cruntime); timestamp();
    CHRw(stdout, "Start calculating ...\n");
    omp_set_num_threads(8);
    for(ydc->ncstep=ydc->ncstep;ydc->ncstep<ydc->mcstep;ydc->ncstep++)
    { show_time_info(ydc,"Ymd");                      /* show time information    */
      Ymd(ydc,yde,ydi,ydn,ydpe,ydpn,ydpm);            /* mesh elements            */

      /********** HERE IS WHERE I WOULD LIKE TO CALL MY C++ PROGRAM ***************/

      Yfd(ydc,yde,ydn,ydi,ydo,ydpe,ydpn,ydpj);        /* nodal forces             */
      Ybor(ydc,yde,ydn,ydb,yds,ydpe,ydpj,ydpn);       /* borholes, inter. fluid   */
      Ycd(ydc,yde,ydi,ydn,ydpe,ydpn);                 /* contact detection        */
      Yid(ydc,yde,ydi,ydn,ydo,ydpe,ydpn, ydpj,ydpm);  /* interaction              */
      Yod(c1name,&yd);                                /* output results           */
      Ysd(ydc,yde,ydn,ydo,ydpe,ydpn );                /* solve equations          */
      Yfrd(ydc,yde,ydi,ydn,ydpe,ydpn,ydpj,ydpm);      /* fracture                 */
      ydc->dctime=ydc->dctime+ydc->dcstec;            /* update time              */
      /* CTRL-C Interruption */
      Tctrlc = enablc(ydc->dctime, ydc->ncstep, ydc->mcstep);
      if(Tctrlc!=1) break;
    }
  }

  /* Termination */
  CHRw(stderr,"   ***** Y HAS ORDERLY FINISHED *****");  CHRwcr(stderr);
  CHRw(stderr,"Press a key to continue");  CHRwcr(stderr);
  getchar();
}

ОБНОВЛЕНИЕ 24 ЧАСА ПОСЛЕ ОТВЕТОВ

Я следил за рекомендациями в соответствии с предоставленными ответами, и решение моей проблемы оказалось намного проще, чем первоначально предполагалось (хотя мне пришлось изучить несколько вариантов, прежде чем заставить его работать). Самое приятное то, что он работает как в Unix, так и в Visual Studio. Вот краткое изложение шагов, которые я сделал:

  • Преобразуйте мой основной файл C в С++. Для этого переименуйте файл, содержащий функцию main моего C-кода с расширением .cpp(измененным с Y.c на Y.cpp) и измените начало функции main на:

    main(argc, argv)
      INT argc; char **argv;
    

    к

    int main(int argc,char **argv)
    

    чтобы сделать его "дружественным". (Примечание: я понимаю, что переименование файла на .cpp не является существенным, но я думаю, что лучше сделать это для ясности).

  • Оберните все мои файлы заголовков C с помощью

    #ifdef __cplusplus
    extern "C" {
    #endif
    

    в начале и

    #ifdef __cplusplus
    }
    #endif
    

    в конце.

  • Измените имя моей функции main С++ и (временно) не используйте аргументы. Я назвал его int Ynano().

  • Создайте новый заголовочный файл с именем Y_NANO.h(Y_NANO.cpp - это имя файла, содержащего исходную основную функцию С++) с помощью строки:

    int Ynano();
    
  • Включите новый заголовок как в Y.cpp, так и в Y_NANO.cpp:

    #include "Y_NANO.h"
    
  • Вызвать функцию Ynano() из функции main в Y.cpp.

  • Чтобы скомпилировать в Visual Studio, просто поместите все исходные файлы в одну и ту же папку и создайте новый проект. В Unix я выполнил шаги, указанные здесь.

Эти шаги позволят программам работать вместе, без передачи информации между ними. Для передачи информации между программами необходимо включить некоторые параметры в качестве аргументов Ynano(), но это другая история.

Некоторые заключительные комментарии:

  • Проблема с повторением макросов в разных заголовочных файлах, похоже, не является реальной проблемой, поскольку ни один файл не включает оба заголовка (мне не нужно было ничего делать).
  • Спасибо всем, кто дал ответы. Они были действительно полезны. Выбранный ответ был выбран на основе полноты, но другие были так же хороши. Я надеюсь, что нить помогает другим выполнять свою работу, так как многие другие темы помогли мне сделать то же самое.

Ответ 1

1) Должен ли я обернуть файлы заголовков C с помощью extern "C" {}?

2) Должен ли я использовать extern "C" в моих C-функциях?

Только если вы планируете #include заголовки C из некоторых исходных файлов С++, то есть, если вы хотите вызвать одну из функций C из вашего кода на С++. Типичный способ создания файла заголовка C, который можно использовать в С++, выглядит следующим образом:

#ifndef MY_C_HEADER_H
#define MY_C_HEADER_H

#ifdef __cplusplus
extern "C" {
#endif

/* All the original content of the C header */

#ifdef __cplusplus
}
#endif

#endif

Если вы не хотите изменять заголовок, также можно просто применить extern "C" к внешней стороне заголовка, включив его в исходный файл С++:

// in my_source.cpp (or some C++ header file):

extern "C" {

#include "my_c_header.h"

}

ПРИМЕЧАНИЕ. Выполнение этого решения совсем не рекомендуется и не является долговременным/поддерживаемым решением, это просто быстрое и грязное решение "просто сделайте это", которое часто терпит неудачу, но иногда работает, в зависимости от как выглядят заголовки C (заголовки C не должны включать многие другие заголовки, и не должны вообще, но некоторые авторы не имеют здравого смысла для этого).

Причиной для extern "C" является отключение С++ name-mangling, т.е. сообщить компилятору, что функции должны быть скомпилированы, чтобы соответствовать неуправляемым символам и/или должны быть просмотрены в таблице символов без искажений ( при соединении с ними). Таким образом, правило прост, любые функции С++, которые вы хотите скомпилировать в библиотеку, которая может быть вызвана из C-кода (или любого другого языка в этом отношении), должны быть объявлены как extern "C". И любые объявления функций, которые вы вызываете в коде на С++, но ссылаетесь на библиотеку, скомпилированную из C (или любого другого языка), также должны быть extern "C".

3) Или мне нужно использовать extern "C" в моих файлах на С++? (заголовки? функции "все из них" или только те, которые мне нужно вызвать из программы C?)

Если вы хотите вызвать некоторые С++-функции из вашего C-кода, тогда эти конкретные функции должны быть объявлены как extern "C" при компиляции этого кода на С++. В заголовочном файле C, который объявляет эти функции (для их вызова из кода C), нет необходимости в extern "C" (он всегда подразумевается в C).

4) В понимании я не могу иметь две "основные" функции. Можно ли просто переименовать мою основную функцию С++?

Какова была бы цель двух основных функций? Это недопустимо и не полезно. У вас все еще может быть только одна "программа" с одним стартом и одним концом, т.е. С одной основной функцией. Вам нужно будет выбрать одну из основных функций и добавить к ней любые дополнительные шаги (вызывать другую библиотеку). Другими словами, вы должны "объединить" основные функции.

5) При компиляции в unix я должен использовать компиляторы C (icc) и С++ (icpc) для разных файлов? или просто компилятор С++?

Вы используете компилятор C для компиляции кода C и компилятора С++ для компиляции кода на С++. Большинство систем сборки (cmake, make и т.д.) Будут автоматически делать это в любом случае. Технически вы можете попытаться скомпилировать C-код с помощью компилятора С++, но не ожидайте, что он будет работать сразу или даже будет легко заставить его работать, не стоит усилий IMHO.

6) Может ли быть вариантом (упростить) для преобразования моей основной функции из C в С++?

Это вариант. Исходный файл, содержащий основную функцию C, кажется относительно простым, он включает один заголовочный файл C и имеет довольно простую основную функцию. Если это так, это не сложно будет скомпилировать на компиляторе С++ (если только заголовок C, в который он входит, содержит множество других заголовков C, что является плохой практикой, но вероятно). Вам нужно будет включить включение файла заголовка C с помощью extern "C" { }, как показано выше. Затем вы можете просто попытаться скомпилировать его (только исходный файл, содержащий основную функцию) в компиляторе С++, а остальную часть кода C с компилятором C, а затем связать все вместе. Если это сработает сразу, то отлично, вы можете начать слияние этой основной функции C с основной функцией С++ из другой библиотеки, и вам будет хорошо идти.

В противном случае обычный вариант - выяснить, что вам нужно для кода на С++. Затем создайте C-friendly функцию (без классов и т.д.) В С++, которая делает эти вещи, используя библиотеку С++. Затем создайте заголовочный файл, который объявляет эту функцию, с помощью спецификатора extern "C" (при компиляции только под С++ (__cplusplus)) и убедитесь, что этот заголовок не содержит никаких других заголовков С++ (а не стандартных заголовков, а не другие заголовки из библиотеки С++). И, наконец, в исходном коде C, где у вас есть основная функция, включите этот заголовок и вызовите эту функцию из того места, где она вам нужна, в основной функции. Свяжите все это вместе, и оно должно работать.

7) Если мне не нужно передавать информацию о классах между этими двумя программами, нужно ли что-нибудь сделать с ними?

Нет. Пока вы не включаете заголовок С++ из кода C (который компилятор не принимает в любом случае), код C не знает, что классы даже существуют. Таким образом, здесь нет никакой опасности.

8) В каком порядке вы предлагаете решить эту проблему? (например, сначала моя программа на языке C, скомпилированная компилятором С++, во-вторых, скомпилируйте оба кода вместе без ссылок, в-третьих, свяжите коды, в-четвертых, переименуйте main на С++ и "вызовите" мой C-код, в-пятых, выполните передачу информации?)

Первым шагом, конечно же, является то, что вы можете скомпилировать как по отдельности. Второй шаг - посмотреть, можно ли скомпилировать основную функцию программы C (только основную функцию) с помощью компилятора С++ (как описано выше). В случае успеха начните включать элементы из основной функции С++ в эту новую "объединенную" основную функцию. Если это не удается, выполните действия, которые я только что упомянул.

9) Наконец, в каждой программе есть несколько макросов, которые повторяются (одно и то же имя, одна и та же реализация). Есть ли конфликт с этим? Должен ли я хранить только один набор макросов?

МАКРО... это трудно сказать. Если вы следуете процедуре создания функции С++, которая может быть вызвана из основной функции C, то у вас по существу есть полная изоляция обеих библиотек, т.е. Они компилируются отдельно и связаны друг с другом после. В этом случае не будет проблем с конфликтующими MACRO (но могут быть с функциями с тем же именем, если некоторые из них являются extern "C" в библиотеке С++). Если вы попытаетесь объединить основные функции в одну основную функцию С++, у вас могут возникнуть проблемы с конфликтующими MACRO между заголовками C и заголовками С++, которые будут включены вместе.

Ответ 2

  • да, но обернуто, как описано здесь: Комбинирование С++ и C - как работает #ifdef __cplusplus?
  • не нужны, если они находятся в файле заголовка. Если это не так, вам нужно форвардное выражение extern в ваших файлах С++, если это необходимо, и да с extern "C"
  • Это не всегда возможно, потому что классы и некоторые типичные вещи С++ просто не работают на C. Но если код С++ действительно просто C, он тоже будет работать. Использование C в С++ намного проще, чем наоборот.
  • Какой смысл переименовать второй основной элемент? Он не будет вызываться, у вас может быть только одна основная функция.
  • вы можете переименовать файлы c на С++ и начать компилировать все с помощью С++. Это позволит решить ваши проблемы с привязкой к материалам extern "C", и это то, что я сделал бы в первую очередь. В противном случае C скомпилирован с компилятором C и С++ с компилятором С++. Эти компиляторы ведут себя по-разному, конечно.
  • да, конечно, ваш C-код может понадобиться для некоторой переделки. Это то, что я сделал бы
  • Не думайте так. Опять же, похоже, нет никаких зависимостей? Как это может быть?
  • не имеет значения, начните компиляцию, а затем устраните проблемы с компоновщиками.
  • могут иметь конфликты, если вы включаете 2 заголовочных файла, содержащих один и тот же макрос. Компилятор будет жаловаться на переопределение.

Ответ 3

Резюме:

Я думаю, вы можете просто скомпилировать свой C main() в отдельном компиляторе CPP, а затем "extern C" - все ваши определения функций C. Вызов с CPP на C легко. Другой способ немного более громоздкий, так как вам нужно будет создать "... API-интерфейс C для раскрытия функциональности вашего кода на С++..." - см. Как вызвать С++ функция от C?

EDIT: выше отредактировано благодаря отзывам Mikael (см. комментарии). Глядя на это, я думаю, что С++ для C, как правило, проще, если код С++ использует специальные функции С++, такие как перегрузка объектов и т.д., Так как тогда ему могут понадобиться C API-wappers (см. Ссылку выше). В этом случае, как указывает Микаэль, на самом деле это не так, так что любой способ такой же легкий/жесткий...

Примечание. Объедините оба параметра main() в одну функцию CPP.

Деталь:

запускать обе программы таким образом, чтобы моя программа C вызывала код С++

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

Я бы сказал, что было бы лучше запустить основную часть из модуля С++ и вызвать оттуда ваши C-функции... таким образом вы столкнетесь с проблемой изменения имени.

Должен ли я обернуть файлы заголовков C с помощью extern "C" {}

Да, важно в файлах заголовков C обернуть все определения функций этим. Обычно вы увидите что-то вроде

#ifndef HEADER_FILE_NAME
#define HEADER_FILE_NAME
#ifdef __cplusplus
   extern "C" {
#endif
/// FILE CONTENTS
#ifdef _cplusplus
   }
#endif
#endif // HEADER_FILE_NAME

Что это значит, говорит компилятор CPP, что эти имена функций не должны искажаться. Таким образом, правильные имена символов будут использоваться при связывании с функциями C.

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

Или мне следует использовать extern "C" в моих файлах на С++?

Экстерн "C" просто сообщает компилятору, что функция имеет C-языковую связь, поэтому сгенерированный символ не будет искажен. Поэтому я думаю, что если это функция, которая не была перегружена, тогда выполнение этого (в H файле для определения функции) будет препятствовать тому, чтобы имя функции искажалось, поэтому вы сможете вызвать ее из C. Однако, если вы extern "C", например, который все равно будет иметь ссылку на С++, то же самое для функций члена класса и т.д.... зависит, используете ли вы эти или нет... не похоже на это из вашего образца кода.

В понимании я не могу иметь две основные функции. Можно ли просто переименовать основную функцию С++?   Может ли быть вариантом (упростить) для преобразования моей основной функции из C в С++?

Да, я думаю, что это лучший вариант. Если только функция main() должна вызывать оба типа кода, тогда вы в порядке. Функция main(), записанная в блоке компиляции CPP.

Если, однако, есть модуль C, который должен вызывать модуль С++, тогда вам нужно подумать о компиляции этого как файла CPP или о том, что функция CPP extern "C" и не перегружена.

Наконец, в каждой программе есть несколько макросов, которые повторяются (одно и то же имя, одна и та же реализация). Есть ли конфликт с этим? Должен ли я хранить только один набор макросов?

Если макросы определены в файлах C/CPP, тогда вы в порядке. Если они находятся в заголовочных файлах, тогда может возникнуть конфликт, если один файл содержит два файла заголовков, которые содержат один и тот же макрос. В любом случае я бы рекомендовал вывести все распространенные макросы в общие файлы заголовков, чтобы был только один экземпляр макроса... гораздо удобнее обслуживать... идите с мантрой "не повторяй себя":)

Я не затронул все ваши вопросы, но надеюсь, что этого достаточно, чтобы вы начали:)

Ответ 4

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

Следующий вариант - сотрудничество. С++ предназначен для воспроизведения совместимости с C, другое направление не так теоретически, но на практике - я ожидаю, что компиляторы того же поставщика поддерживают перекрестный разговор во всех направлениях.

Самое главное знать, что если вы добавите С++, то вся ваша система будет считаться С++, поэтому вы должны прислушаться к одному правилу определения, иметь основную информацию на С++ и т.д. Вы компилируете C-источник с компилятором C и рассматривайте их как гость... Вы должны настроить параметры компилятора для совместимости и установить все заголовки, которые совместно используются системами. Это обычно означает использование этих условных выражений, extern "C", typedefing structs для их собственных имен и так далее.

С перекрестными вызовами смотрим за исключениями. В некоторых системах они не должны пересекать границу C/С++. В других они могут, но вам нужно настроить параметры, чтобы они работали хорошо.

В первый проход вам нужно только сделать материал, как и раньше. Для предложений более поздних рефакторингов здесь есть хорошие вопросы о SO и в других местах.

Ответ 5

Я скажу вам правильный ответ, и вам, вероятно, это не понравится. Не зная всей специфики вашего кода, похоже, что вам нужно сделать тяжелый рефакторинг.

В идеальном мире, когда вы пишете свое приложение, оно должно быть написано таким образом, что реализация выполняется как формальный API, а main() переводит/анализирует аргументы командной строки на соответствующие вызовы в API, Если все сделано правильно, файл с функцией main() необходим только для создания исполняемого файла для его задачи.

Еще лучше, реализация будет построена как библиотека и набор заголовков для использования этой библиотеки.

Тогда ваша задача не будет blendering двух исполняемых файлов, а вместо этого создаст новое приложение, которое вызывает две разные библиотеки.

Причина, по которой вам не понравится, заключается в том, что это отнимает много времени. Требуется много времени, чтобы сделать правильный дизайн, чтобы создать библиотеку, а не объединять несколько классов/функций для выполнения задачи jour, но если ваш существующий код не очень хорошо организован, это означает, что вы хотите сохранить свое время с проблемами кода, которые в конечном итоге появятся.

Что бы я сделал, если бы был вами, сначала выясните, как работает каждое приложение. Пройдите через них. Посмотрите, куда идет код, и получите свою теорию операций в вашу голову. Это позволит вам упаковать или переупаковать его соответствующим образом.