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

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

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

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

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

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

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

  1. Должен ли я обернуть мои файлы заголовка C с extern "C" {}?
  2. Должен ли я использовать extern "C" в моих функциях C?
  3. Или я должен использовать extern "C" в моих файлах C ++? (заголовки? функции? все они? или только те, которые мне нужны для вызова из программы на Си?)
  4. В понимании я не могу иметь два main функции. Могу ли я просто переименовать мой C ++ main функционировать?
  5. При компиляции в Unix, я должен использовать компиляторы C (icc) и C ++ (icpc) для разных файлов? или просто компилятор C ++?
  6. Может ли это быть вариант (чтобы упростить вещи), чтобы преобразовать мой main функция от C до C ++?
  7. Если мне не нужно передавать информацию о классах между двумя программами, нужно ли что-то с ними делать?
  8. В каком порядке вы предлагаете решить эту проблему? (например, сначала скомпилируйте мою C-программу компилятором C ++; во-вторых, скомпилируйте оба кода без ссылок; в-третьих, скомпонуйте коды; в-четвертых, переименуйте main в C ++ и «вызывается» моим C-кодом; в-пятых, осуществлять передачу информации?)
  9. Наконец, в каждой программе есть несколько макросов, которые повторяются (одно и то же имя, одна и та же реализация). Есть ли конфликт с этим? Должен ли я хранить только один набор макросов?

Извините за длинный текст и несколько вопросов. Я относительно новичок в 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. Вот краткое изложение шагов, которые я предпринял:

  1. Конвертировать мой основной файл C в C ++. Для этого переименуйте файл, содержащий main функция моего кода C с расширением .cpp (изменено с Y.c на Y.cpp) и изменить начало main функция от:

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

    в

    int main(int argc,char **argv)
    

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

  2. Обернуть все мои заголовочные файлы C

    #ifdef __cplusplus
    extern "C" {
    #endif
    

    в начале и

    #ifdef __cplusplus
    }
    #endif
    

    в конце.

  3. Изменить имя моего main Функция C ++ и (временно) не использует аргументов. Я назвал это int Ynano(),

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

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

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

  7. Для компиляции в Visual Studio просто поместите все исходные файлы в одну папку и создайте новый проект. В Unix я следовал инструкциям Вот.

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

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

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

11

Решение

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

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

Только если вы планируете #include заголовки C из некоторых исходных файлов C ++, то есть, если вы хотите вызвать одну из функций C из вашего кода 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" извне заголовка при включении его в исходный файл C ++:

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

extern "C" {

#include "my_c_header.h"
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

7

Другие решения

  1. да, но завернутый как объяснено здесь: Сочетание C ++ и C — как работает #ifdef __cplusplus?
  2. не требуется, если они находятся в заголовочном файле. Если это не так, то вам нужно предварительное объявление extern в ваших файлах C ++, если это необходимо, и да с extern «C»
  3. Это не всегда возможно, потому что классы и некоторые типичные для C ++ вещи просто не работают в C. Но если код C ++ действительно просто C, он тоже будет работать. Использование C в C ++ намного проще, чем наоборот.
  4. Какой смысл переименовывать вторую основную функцию? Он не будет вызываться, вы можете иметь только одну основную функцию
  5. Вы можете переименовать ваши c файлы в C ++ и начать компилировать все с помощью C ++. Это решило бы ваши проблемы со связью с внешним «C» и это то, что я бы сделал в первую очередь. В противном случае C компилируется компилятором C, а C ++ — компилятором C ++. Эти компиляторы ведут себя по-разному, конечно.
  6. да, конечно, ваш код на C может нуждаться в некоторой переработке. Это то, что я бы сделал
  7. не думай так Опять же, кажется, нет никаких зависимостей? Как это может быть ?
  8. не имеет значения, начните компиляцию, затем исправьте проблемы компоновщика
  9. могут возникнуть конфликты, если вы включите 2 заголовочных файла, содержащих один и тот же макрос. Компилятор будет пожаловаться с переопределением.
2

Резюме:

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

РЕДАКТИРОВАТЬ: Выше отредактировано благодаря обратной связи от Микаэля (см. Комментарии). Глядя на это, я думаю, что с C ++ до C, как правило, все еще проще, если код C ++ использует преимущества специфических функций C ++, таких как перегрузка объектов и т. Д., Поскольку в этом случае могут потребоваться средства обработки API C (см. Ссылку выше). В этом случае, как указывает Микаэль, это не совсем так, так что в любом случае так легко / сложно …

Примечание: объедините ваши main()в одной функции CPP.

Деталь:

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

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

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

Должен ли я обернуть мои файлы заголовков C с внешним «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 ++?

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

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

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

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

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

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

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

1

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

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

Важно помнить, что если вы добавляете C ++, то вся ваша система считается C ++, поэтому вы должны учитывать одно правило определения, иметь основную часть из C ++ и т. Д. Вы компилируете C, полученный из компилятора C, и рассматриваете их как guest … Вы должны настроить параметры компилятора для совместимости и установить все заголовки, которые совместно используются системами. Обычно это означает использование этих условных выражений, внешнего «C», структур определения типа для их собственных имен и так далее.

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

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

0

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

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

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

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

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

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

-1
По вопросам рекламы [email protected]