从C ++拦截Fortran STOP

我为传统的Fortran库准备了一个C ++接口。

遗留库中的一些子例程遵循一个丑陋但可用的状态代码约定来报告错误,我使用这样的状态代码从我的C ++代码中抛出一个可读的exception:它工作得很好。

另一方面,有时遗留库会调用STOP (终止程序)。 即使条件可以恢复,它也经常这样做。

我想从C ++中捕获这个STOP ,到目前为止我一直没有成功。

以下代码很简单,但完全代表了手头的问题:

Fortran遗留库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 // 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 ,比如请求另一个号码。 请注意,我不想触及Fortran代码

我试过的:

  • std::atexit :输入后不能“回来”
  • std::signalSTOP似乎没有抛出我能捕获的信号

您可以通过拦截Fortran运行时对exit函数的调用来解决您的问题。 见下文。 使用您的代码和您提供的编译行创建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) 

我发现它(毫不奇怪)fortran运行时。

 $ 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 session)

 $ cat my_exit.c #define _GNU_SOURCE #include  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 #include #include // 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; } 

使用setjmp调用调用longjmp在行的中间跳转, 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的两个答案,我认为在自定义函数中引发exception将类似,然后在主代码中捕获。 所以这出来了:

main.cpp:

 #include // 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 < 

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 

所以,似乎工作:)

我建议你在调用fortran代码之前分叉你的进程并退出0(编辑:如果STOP退出为零,你将需要一个sentinel退出代码,clanky但是完成工作)在fortran执行之后。 这样每一次fortran调用都会以同样的方式结束:就像它已经停止一样。 或者,如果“STOP”确保错误,则在fortran代码停止时抛出exception,并在fortran执行“完成”正常时发送一些其他消息。

下面是一个例子激发您的代码假设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种可能性:

  • fortran代码在调用期间会占用大量内存,并且让执行继续超出STOP可能不是一个好主意。
  • fortran代码只返回一些值(通过它的参数,如果我的fortran不太生锈),这可以通过共享内存空间轻松地中继回父。
  • fortran子例程的执行作用于外部系统(例如:写入文件),并且不需要返回值。

在第三种情况下,我的解决方案按原样工作。 我更喜欢它比其他一些建议的解决方案主要是因为:1)你不必确保构建过程得到适当维护2)fortran“STOP”仍然按预期运行3)它需要很少的代码行和所有“ fortran STOP解决方案“逻辑坐落在一个地方。 所以在长期维护方面,我更喜欢这样。

在第二种情况下,我的上面的代码需要很小的修改,但仍然保持上面列举的优点,代价是最小的复杂性。

在第一种情况下,无论如何都要使用fortran代码。