5

我为旧版 Fortran 库准备了一个 C++ 接口。

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

另一方面,有时遗留库调用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<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,比如说,请求另一个号码。请注意,我不想接触 Fortran 代码

我试过的:

  • std::atexit:一旦我进入它就不能“回来”
  • std::signalSTOP似乎没有发出我可以捕捉到的信号
4

4 回答 4

11

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)

我在(毫不奇怪)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.cgdb会话)

$ 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   

你去吧。

于 2013-10-25T20:20:52.600 回答
5

既然你想要的无论如何都会导致不可移植的代码,为什么不使用晦涩的长跳转机制来颠覆退出机制:

#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所在行的中间跳转,setjmpsetjmp返回作为 的第二个参数传递的值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

于 2013-10-26T14:47:12.370 回答
1

结合使用自定义_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

所以,似乎工作:)

于 2013-10-28T13:27:15.987 回答
0

我建议你在调用 fortran 代码和退出 0 之前分叉你的进程(编辑:如果 STOP 以零退出,你将需要一个哨兵退出代码,叮当但可以完成工作)在 fortran 执行之后。这样,每个 fortran 调用都将以相同的方式完成:就像它已经停止一样。或者,如果“停止”确保错误,则在 fortran 代码停止时抛出异常,并在 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 代码。

于 2013-10-25T18:17:23.727 回答