Я читал о нескольких методах объединения кодов C и C ++, однако я все еще не уверен, как поступить в моем случае. Вот моя проблема:
У меня есть относительно большой объем кода C (состоящий из различных .c
а также .h
файлы), который используется для моделирования твердых тел в конечных и дискретных элементах. Этот код имеет относительно короткую и простую основную функцию с for
цикл, в котором различные другие функции (из других файлов) вызываются последовательно. Этот код отлично работает при компиляции как в Unix (icc compiler), так и в Visual Studio.
У меня есть другой код на C ++, который решает взаимодействия молекулярной динамики. Этот код также состоит из различных файлов и отлично работает как в Unix (компилятор icpc), так и в VS. Оба являются автономными программами с собственным набором входных и выходных файлов.
Что я должен сделать состоит в том, чтобы запустить обе программы таким образом, чтобы моя программа на C «вызывала» код C ++ в своем основном цикле. Некоторая информация должна передаваться в обоих направлениях между двумя кодами, которые могут быть в виде массивов (или указателей).
Какой самый простой способ сделать это?
В частности, у меня есть несколько вопросов на основе рекомендаций, которые я прочитал:
extern "C" {}
?extern "C"
в моих функциях C?extern "C"
в моих файлах C ++? (заголовки? функции? все они? или только те, которые мне нужны для вызова из программы на Си?)main
функции. Могу ли я просто переименовать мой C ++ main
функционировать?main
функция от C до C ++?main
в C ++ и «вызывается» моим C-кодом; в-пятых, осуществлять передачу информации?)Извините за длинный текст и несколько вопросов. Я относительно новичок в 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 в C ++. Для этого переименуйте файл, содержащий main
функция моего кода C с расширением .cpp (изменено с Y.c на Y.cpp) и изменить начало main
функция от:
main(argc, argv)
INT argc; char **argv;
в
int main(int argc,char **argv)
чтобы сделать его C ++ «дружественным». (Примечание: я понимаю, что переименование файла в .cpp не является обязательным, но я думаю, что лучше сделать это для ясности).
Обернуть все мои заголовочные файлы C
#ifdef __cplusplus
extern "C" {
#endif
в начале и
#ifdef __cplusplus
}
#endif
в конце.
Изменить имя моего main
Функция C ++ и (временно) не использует аргументов. Я назвал это int Ynano()
,
Создайте новый заголовочный файл с именем Y_NANO.h (Y_NANO.cpp — это имя файла, содержащего первоначально основную функцию C ++) со строкой:
int Ynano();
Включите новый заголовок в Y.cpp и Y_NANO.cpp:
#include "Y_NANO.h"
Вызвать функцию Ynano()
от main
функция в Y.cpp.
Для компиляции в Visual Studio просто поместите все исходные файлы в одну папку и создайте новый проект. В Unix я следовал инструкциям Вот.
Эти шаги приведут к тому, что программы будут работать вместе без передачи информации между ними. Для передачи информации между программами необходимо включить некоторые параметры в качестве аргументов Ynano()
, но это другая история.
Несколько заключительных комментариев:
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 ++, которые будут включены вместе.
Я думаю, что вы можете просто собрать свой 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, то все в порядке. Если они находятся в заголовочных файлах, то может возникнуть конфликт, если один файл содержит два заголовочных файла, каждый из которых содержит один и тот же макрос. В любом случае я бы порекомендовал вынести все распространенные макросы в общие заголовочные файлы, чтобы был только один экземпляр макроса … гораздо более понятный … используйте мантру «не повторяйся» 🙂
Я не учел все ваши вопросы, но надеюсь, что этого достаточно, чтобы вы начали 🙂
Что ж, в идеале вы можете скомпилировать исходники C как C ++ без изменения семантики, а затем просто пользоваться единой системой. В зависимости от размера вашей кодовой базы и ее формы стоит рассмотреть вариант. На самом деле это может быть меньше работы, чем возиться со всеми этими внешними буквами «С» и последствиями.
Следующий вариант — сотрудничество. C ++ разработан для совместимости с C, другое направление не так теоретически, но на практике — я ожидаю, что компиляторы одного и того же поставщика поддерживают кросс-разговор во всех направлениях.
Важно помнить, что если вы добавляете C ++, то вся ваша система считается C ++, поэтому вы должны учитывать одно правило определения, иметь основную часть из C ++ и т. Д. Вы компилируете C, полученный из компилятора C, и рассматриваете их как guest … Вы должны настроить параметры компилятора для совместимости и установить все заголовки, которые совместно используются системами. Обычно это означает использование этих условных выражений, внешнего «C», структур определения типа для их собственных имен и так далее.
При перекрестных вызовах следите за исключениями. В некоторых системах они не должны пересекать границу C / C ++. В других они могут, но вам нужно настроить параметры, чтобы он работал хорошо.
На первом проходе вам нужно только заставить материал работать так же, как и раньше. Для предложений более поздних рефакторингов есть хорошие вопросы здесь на SO и в других местах.
Я скажу вам правильный ответ, и вам, вероятно, это не понравится.
Не зная всей специфики вашего кода, кажется, что вам нужно провести серьезный рефакторинг.
В идеальном мире, когда вы пишете свое приложение, оно должно быть написано таким образом, чтобы реализация выполнялась как формальный API, а функция main () переводила / анализировала аргументы командной строки в соответствующие вызовы в API. Если все сделано правильно, файл с подпрограммой main () необходим только для сборки исполняемого файла для его задачи.
Более того, реализация будет построена как библиотека и набор заголовков для использования этой библиотеки.
Тогда ваша задача — не смешивать два исполняемых файла, а создать новое приложение, которое вызывает две разные библиотеки.
Причина, по которой вам это не понравится, заключается в том, что это отнимает много времени. Требуется много времени, чтобы сделать правильный дизайн, чтобы создать библиотеку, а не собирать вместе несколько классов / функций для выполнения задачи, но если ваш существующий код не очень хорошо организован, то это поможет вам сэкономить время с проблемами кода, которые в конечном итоге возникнут.
Что бы я сделал на вашем месте, это сначала выяснить, как именно работает каждое приложение. Шаг через них. Посмотрите, куда идет код, и представьте свою теорию операций в свою голову. Это позволит вам упаковать или упаковать его соответствующим образом.