( примечание , если вы не хотите читать все здесь, возможно, вы захотите взглянуть на этот вопрос здесь .)
Прежде всего, я попытаюсь дать этот ответ с исторической точки зрения, поскольку вы спрашиваете о необходимости, которая приходит со временем, но оригинальный дизайн ни C ++, ни C # не заключался в том, чтобы сделать их совместимыми языками. Я буду привлекать C к середине, так как требования к дизайну C ++, чтобы быть совместимым с C, приняли много проектных решений для решения проблемы (и это даст много света в этой теме). Таким образом, это единственный языковой дизайн, для которого исходная (а не двоичная) совместимость была первоначальным требованием. Есть две причины следовать этому подходу:
- C # был разработан позже, чем C ++, поэтому, если какое-либо из них имеет некоторые свойства совместимости в своем дизайне, вероятно, что C # имеет их, так как C ++ не имел бинарных решений по проектированию совместимости, принятых для C # (его не было на время определения C ++)
- Поскольку C ++ был разработан с учетом требований "совместимости" , чтобы иметь возможность использовать обширный набор библиотечных подпрограмм, доступных в C, это язык, который имеет некоторый способ express это заявления о совместимости каким-то образом, так что возможно иметь некоторую точку отсчета, которая позволяет вам искать в документации.
Компиляция C-программы с помощью C-компилятора имеет некоторые недостатки, когда речь идет о взаимодействии C и C ++-процедур между собой. Когда C ++ разрабатывался, он имел в виду C-совместимость. Но обратное неверно, никто не проектировал C с «как C ++-совместимый» . Единственный способ получить доступ к функциям C ++ из C - это скомпилировать их с помощью компилятора C ++ и выполнить модификации compatibility , чтобы устранить внутренние несовместимости обоих языков (или использовать компилятор C, а затем связать двоичные файлы вместе с помощью компоновщика C ++) Последний момент очень важен связать их с компоновщиком C ++ (или связать в режиме C ++)
Почему так важно использовать компоновщик C ++ для связывания объектов C и C ++? Главным образом потому, что компоновщик C не готов включить потребности языка C ++ (создание экземпляров глобальных объектов, вызывая их конструкторы, не поддерживается компоновщиком C) В случае Java или C ++ экземпляры объектов всегда (по крайней мере, в Java, не может гарантировать, что происходит в C #), управляемый по ссылке. C # был разработан как язык для спецификации виртуальной машины .Net (как Sun сделал и с Java), и, поскольку у вас есть компиляторы C ++ для этой платформы, C ++ был спроектирован без учета виртуальной целевой платформы и, вероятно, возможности такого организация использования C ++ из C # будет как минимум трудной.
Давайте поговорим о случае C / C ++:
Во-первых, для того, чтобы функции C ++ были доступны из кода C, они должны иметь "C"
интерфейс вызова (возможно, что какое-то расширение языка необходимо для того, чтобы сделать вызовы методов C ++ и функции совместимыми с C #). Это означает, что функции C ++, которые вы вызываете из C, должны быть объявлены как
extern "C" void my_cplusplus_function(type1 arg1, type2 arg2);
или положить все в
extern "C" {
...
}
блок. Причина этого заключается в том, что компиляторы C ++ искажают внешние идентификаторы (включая типы параметров и другие атрибуты функций, например атрибут const
в методах ...), чтобы разрешить разные версии для одного и того же имени функции с разными интерфейсами и это не совместимо с вызывающим интерфейсом C. Конечно, это также верно для C # (возможно, в компиляторе Visual C ++ есть расширение extern "C#"
, позволяющее работать с именами в определениях языка C #). Если вы не придерживаетесь этого подхода, первое, что у вас есть, это проверить, какое из возможных определений с этим именем является тем, которое вам нужно, а затем проверьте, как компилятор искажает полное определение прототипа, чтобы узнать окончательное имя, которое получает компоновщик, и которое используется для доступа к функции и, наконец, для уважения всей среды стековых фреймов, записей активации функций и т. д., которые приведут к зависанию программы, если за ней не следовать. Если у вас есть интерес к способу Java / C ++ (полностью задокументирован на www.java.com), вы увидите ограничения, налагаемые вызовами функций Java / C ++ (это документ под эпиграфом JNI , возможно, если вы ищете C точный собственный интерфейс , вы получите что-то) Это связано с разной организацией памяти исполняемых файлов, даже для одной и той же целевой платформы.
Пример "режима совместимости с C" (извините, у меня нет эквивалента для C # или Java под рукой) проиллюстрируем это:
main.c
#include <stdio.h> /* for printf */
#include <stdlib.h> /* for EXIT_SUCCESS */
#include <math.h> /* for normal sqrt() */
#include "cpp_sqrt.h" /* for the prototype of cpp_mysqrt() */
int main()
{
/* you are forced to use stdio here, as you are in plain C */
printf("cpp_sqrt(3.0) => %lg\n"
"sqrt(3.0) => %lg\n",
cpp_sqrt(3.0), /* <== this is a c++ function */
sqrt(3.0)); /* <== this is the math C function */
return EXIT_SUCCESS;
}
cpp_mysqrt.cc
#include "cpp_sqrt.h"
#include <iostream>
double cpp_sqrt(const double x)
{
/* you can use all C++ stuff here, as this is C++ */
std::cout << "in cpp_sqrt()" << std::endl;
double g = 1.0, /* geometric mean */
a = x; /* arithmetic mean */
const double epsilon = 1.0E-6;
while ((a - g) > epsilon) {
a = (a + g) / 2.0;
g = x / a;
}
return g;
}
cpp_mysqrt.h
/* this construction is required to be able to include this
* file in C and C++ source code. C++ compilers always define
* the __cplusplus macro, so they will include the
* extern "C" block, while C compilers don't. This makes it
* possible to use the same prototype header file in both C and
* C++ source files without problems.
*/
#ifdef __cplusplus
extern "C" {
#endif
double cpp_sqrt(double X);
#ifdef __cplusplus
} /* extern "C" */
#endif
Если вы удалите части extern "C" {
и }
файла заголовка (или просто переименуете __cplusplus
в __cplusplus1
, чтобы компилятор C ++ не использовал его), компилятор C ++ будет искажать имя для cpp_sqrt(double)
функция, делающая ее недоступной из кода C, как вы увидите, если попытаетесь ее скомпилировать:
$ ed cpp_sqrt.h <<'EOF'
1,$s/__cplusplus/__cplusplus1/g
w
EOF
$ make
cc -O -pipe -c main.c -o main.o
c++ -O -pipe -c cpp_sqrt.cc -o cpp_sqrt.o
c++ -o cppfromc main.o cpp_sqrt.o -lm
main.o: In function `main':
main.c:(.text+0x10): undefined reference to `cpp_sqrt'
c++: error: linker command failed with exit code 1 (use -v to see invocation)
*** Error code 1
Stop.
make: stopped in /home/user/cfromcpp
Итак, наконец, какова проблема, которую вы решили в наше время? Разработчик, сталкивающийся с такой проблемой, обычно создает «интерфейсный набор функций» , который соответствует ограничениям интерфейса для обоих миров и действует как клейкий код (конечно, вы слышали, что последний термин), чтобы сделать интерфейс возможным. Это позволяет иметь Java доступ к необработанным сокетам или доступ к функциональности операционной системы низкого уровня.
Это также позволяет, если в будущем интерфейс вашей библиотеки изменится, вам нужно всего лишь изменить модуль склеивания , чтобы он снова работал с новой версией.
Примечание
Далее Makefile
используется для построения примера кода, просто выполните make
для сборки программы:
Makefile
RM ?= rm -f
targets = cppfromc
toclean=$(targets)
cppfromc_objs = main.o cpp_sqrt.o
toclean += $(cppfromc_objs)
cppfromc_libs = -lm
all: $(targets)
clean:
$(RM) $(toclean)
# NOTE: you must use the C++ linker because C++ programs have a different memory map,
# to allow for constructors of global object instances to be constructed before main()
# is executed.
cppfromc: main.o cpp_sqrt.o
$(CXX) $(LDFLAGS) -o $@ $($@_objs) $($@_libs)
main.o cpp_sqrt.o: cpp_sqrt.h