Я подготовил интерфейс C ++ к устаревшей библиотеке Fortran.
Некоторые подпрограммы в устаревшей библиотеке следуют уродливому, но пригодному для использования соглашению о кодах состояния, чтобы сообщать об ошибках, и я использую такие коды состояния, чтобы генерировать читабельное исключение из моего кода C ++: это прекрасно работает.
С другой стороны, иногда устаревшая библиотека вызывает STOP
(что завершает программу). И он часто делает это, хотя состояние можно исправить.
я бы хотел захватить этот STOP
изнутри C ++, и до сих пор я потерпел неудачу.
Следующий код прост, но точно представляет проблему под рукой:
Библиотека наследия Фортрана fmodule.f90
:
module fmodule
use iso_c_binding
contains
subroutine fsub(x) bind(c, name="fsub")
real(c_double) x
if(x>=5) then
stop 'x >=5 : this kills the program'
else
print*, x
end if
end subroutine fsub
end module fmodule
Интерфейс C ++ main.cpp
:
#include<iostream>
// prototype for the external Fortran subroutine
extern "C" {
void fsub(double& x);
}
int main() {
double x;
while(std::cin >> x) {
fsub(x);
}
return 0;
}
Строки компиляции (GCC 4.8.1 / OS X 10.7.4; $
обозначает командную строку):
$ gfortran -o libfmodule.so fmodule.f90 -shared -fPIC -Wall
$ g++ main.cpp -L. -lfmodule -std=c++11
Бег:
$ ./a.out
1
1.0000000000000000
2
2.0000000000000000
3
3.0000000000000000
4
4.0000000000000000
5
STOP x >=5 : this kills the program
Как я мог захватить STOP
и, скажем, запросить другой номер. Обратите внимание, что я не хочу трогать код Фортрана.
Что я пробовал:
std::atexit
: не могу «вернуться» из него, как только я вошел в негоstd::signal
: STOP
не похоже, чтобы бросить сигнал, который я могу захватитьВы можете решить свою проблему, перехватив вызов exit
функция из среды исполнения Fortran. Увидеть ниже. a.out
создается с вашим кодом и строками компиляции, которые вы даете.
Шаг 1. Выясните, какая функция вызывается. Зажечь gdb
$ gdb ./a.out
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
[...]
(gdb) break fsub
Breakpoint 1 at 0x400888
(gdb) run
Starting program: a.out
5
Breakpoint 1, 0x00007ffff7dfc7e4 in fsub () from ./libfmodule.so
(gdb) step
Single stepping until exit from function fsub,
which has no line number information.
stop_string (string=0x7ffff7dfc8d8 "x >=5 : this kills the programfmodule.f90", len=30) at /usr/local/src/gcc-4.7.2/libgfortran/runtime/stop.c:67
Так stop_string
называется. Нам нужно знать, какому символу соответствует эта функция.
Шаг 2. Найдите точное название stop_string
функция. Это должно быть в одной из общих библиотек.
$ ldd ./a.out
linux-vdso.so.1 => (0x00007fff54095000)
libfmodule.so => ./libfmodule.so (0x00007fa31ab7d000)
libstdc++.so.6 => /usr/local/gcc/4.7.2/lib64/libstdc++.so.6 (0x00007fa31a875000)
libm.so.6 => /lib64/libm.so.6 (0x0000003da4000000)
libgcc_s.so.1 => /usr/local/gcc/4.7.2/lib64/libgcc_s.so.1 (0x00007fa31a643000)
libc.so.6 => /lib64/libc.so.6 (0x0000003da3c00000)
libgfortran.so.3 => /usr/local/gcc/4.7.2/lib64/libgfortran.so.3 (0x00007fa31a32f000)
libquadmath.so.0 => /usr/local/gcc/4.7.2/lib64/libquadmath.so.0 (0x00007fa31a0fa000)
/lib64/ld-linux-x86-64.so.2 (0x0000003da3800000)
Я нашел это (не удивительно) во время выполнения Фортрана.
$ readelf -s /usr/local/gcc/4.7.2/lib64/libgfortran.so.3|grep stop_string
1121: 000000000001b320 63 FUNC GLOBAL DEFAULT 11 _gfortran_stop_string@@GFORTRAN_1.0
2417: 000000000001b320 63 FUNC GLOBAL DEFAULT 11 _gfortran_stop_string
Шаг 3. Напишите функцию, которая заменит эту функцию
Я ищу точную подпись функции в исходном коде (/usr/local/src/gcc-4.7.2/libgfortran/runtime/stop.c
увидеть gdb
сессия)
$ cat my_exit.c
#define _GNU_SOURCE
#include <stdio.h>
void _gfortran_stop_string (const char *string, int len)
{
printf("Let's keep on");
}
Шаг 4. Скомпилируйте общий объект, экспортирующий этот символ.
gcc -Wall -fPIC -c -o my_exit.o my_exit.c
gcc -shared -fPIC -Wl,-soname -Wl,libmy_exit.so -o libmy_exit.so my_exit.o
Шаг 5. Запустите программу с LD_PRELOAD, чтобы наша новая функция имела приоритет над одной из форм времени выполнения
$ LD_PRELOAD=./libmy_exit.so ./a.out
1
1.0000000000000000
2
2.0000000000000000
3
3.0000000000000000
4
4.0000000000000000
5
Let's keep on 5.0000000000000000
6
Let's keep on 6.0000000000000000
7
Let's keep on 7.0000000000000000
Вот и ты.
Так как то, что вы хотите, в любом случае приведет к непереносимому коду, почему бы просто не разрушить механизм выхода, используя неясный механизм длинного перехода:
#include<iostream>
#include<csetjmp>
#include<cstdlib>
// prototype for the external Fortran subroutine
extern "C" {
void fsub(double* x);
}
volatile bool please_dont_exit = false;
std::jmp_buf jenv;
static void my_exit_handler() {
if (please_dont_exit) {
std::cout << "But not yet!\n";
// Re-register ourself
std::atexit(my_exit_handler);
longjmp(jenv, 1);
}
}
void wrapped_fsub(double& x) {
please_dont_stop = true;
if (!setjmp(jenv)) {
fsub(&x);
}
please_dont_stop = false;
}
int main() {
std::atexit(my_exit_handler);
double x;
while(std::cin >> x) {
wrapped_fsub(x);
}
return 0;
}
призвание longjmp
прыгает прямо в середине строки с setjmp
позвонить и setjmp
возвращает значение, переданное в качестве второго аргумента longjmp
, Иначе setjmp
возвращает 0. Пример вывода (OS X 10.7.4, GCC 4.7.1):
$ ./a.out
2
2.0000000000000000
6
STOP x >=5 : this kills the program
But not yet!
7
STOP x >=5 : this kills the program
But not yet!
4
4.0000000000000000
^D
$
Предварительная загрузка библиотеки не требуется (что в любом случае в OS X является более сложным, чем в Linux). Предупреждение: обработчики выхода вызываются в обратном порядке их регистрации. Следует быть осторожным, чтобы никакие другие обработчики выхода не регистрировались после my_exit_handler
,
Объединяя два ответа, которые используют пользовательский _gfortran_stop_string
функция и longjmp
Я думал, что вызов исключения внутри пользовательской функции будет аналогичным, а затем будет реализован в основном коде. Итак, это вышло:
main.cpp:
#include<iostream>
// prototype for the external Fortran subroutine
extern "C" {
void fsub(double& x);
}
int main() {
double x;
while(std::cin >> x) {
try { fsub(x); }
catch (int rc) { std::cout << "Fortran stopped with rc = " << rc <<std::endl; }
}
return 0;
}
catch.cpp:
extern "C" {
void _gfortran_stop_string (const char*, int);
}
void _gfortran_stop_string (const char *string, int len)
{
throw 666;
}
Затем компилируем:
gfortran -c fmodule.f90
g++ -c catch.cpp
g++ main.cpp fmodule.o catch.o -lgfortran
Бег:
./a.out
2
2.0000000000000000
3
3.0000000000000000
5
Fortran stopped with rc = 666
6
Fortran stopped with rc = 666
2
2.0000000000000000
3
3.0000000000000000
^D
Итак, похоже на работу 🙂
Я предлагаю вам разветвить свой процесс перед вызовом кода Фортрана и выйти из 0 (правка: если STOP завершается с нуля, вам понадобится код выхода дозорного, неуклюжий, но делает работу) после выполнения Фортрана. Таким образом, каждый вызов на Фортране завершается так же, как если бы он прекратился. Или, если «STOP» выдает ошибку, сгенерирует исключение, когда код фортрана останавливается, и отправляет какое-то другое сообщение, когда выполнение фортрана «завершается» нормально.
Ниже приведен пример, вдохновляющий ваш код, предполагая, что Fortran «STOP» является ошибкой.
int main() {
double x;
pid_t pid;
int exit_code_normal = //some value that is different from all STOP exit code values
while(std::cin >> x) {
pid = fork();
if(pid < 0) {
// error with the fork handle appropriately
} else if(pid == 0) {
fsub(x);
exit(exit_code_normal);
} else {
wait(&status);
if(status != exit_code_normal)
// throw your error message.
}
}
return 0;
}
Код выхода может быть константой вместо переменной. Я не думаю, что это имеет большое значение.
После комментария оказывается, что результат выполнения будет потерян, если он будет храниться в памяти процесса (а не, скажем, записывать в файл). Если это так, я могу думать о 3 возможностях:
В третьем случае моё решение выше работает как есть. Я предпочитаю его другим предлагаемым решениям, главным образом потому, что: 1) вам не нужно гарантировать, что процесс сборки поддерживается должным образом 2) fortran «STOP» по-прежнему ведет себя как положено, и 3) ему требуется очень мало строк кода и все » Фортран СТОП обходной путь «логика сидит в одном месте. Так что с точки зрения долгосрочного обслуживания, я предпочитаю это.
Во втором случае мой код выше нуждается в небольшой модификации, но все же обладает перечисленными выше преимуществами за счет минимальной дополнительной сложности.
В 1-м случае вам придется возиться с кодом Фортрана, несмотря ни на что.