Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Call Fortran from C

lxh37
Beginner
2,469 Views

Hi,

I am trying to learn how to call Fortran from C, I am able to create .so file from C and call it from Ruby On Rails,

the C code is:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

long add(long maxn, double delta, double conf, char *errMsg)
{
        long ERR_MSG_LENGTH, answer;
        unsigned i;
        char * iarr;

       iarr = "Hello!";

        answer = (long) (maxn+delta+conf);

        for (i=0; ((char) iarr) != '\0'; ++i) errMsg = (char) iarr;
        errMsg =  '\0';
        return answer;
}

but when I tried to call Fortran add function from C, I have trouble:

add.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern addsize(long maxn, double delta, double conf, long * sum);

long add(long maxn, double delta, double conf, char *errMsg)
{
        long ERR_MSG_LENGTH, answer;
        unsigned i;
        char * iarr;

        iarr = "Hello!";

        addsize(maxn, delta, conf, &answer);

        for (i=0; ((char) iarr) != '\0'; ++i) errMsg = (char) iarr;
        errMsg =  '\0';
        return answer;
}

addsize.f90:

subroutine addsize( maxn, delta, conf, sum ) bind(c, name='addsize')
use, intrinsic :: iso_c_binding

!use program_constants
implicit none

real(C_LONG), intent(in) :: maxn
real (C_DOUBLE), intent(in) :: delta, conf
real(C_LONG), intent(out) :: sum
 
sum = maxn + delta + conf;

end subroutine addsize

I compile addsize.f90 as below:

/opt/intel/bin/ifort -c addsize.f90

then compile add.c

gcc -c -shared -fPIC add.c

then create add.so

gcc -shared -fPIC *.o -o add.so

Somehow I couldn't get it to work, is anything wrong in the code? Thanks!

Liz

 

 

0 Kudos
1 Solution
FortranFan
Honored Contributor II
2,473 Views

lxh37 wrote:

Well, when I comment out call to Fortran, c code works fine and c code is so simple...

Must something wrong in the interface between c and Fortran. 

Liz

@Liz,

As Steve said, it would appear the code you have posted should work, however it is spread across several messages and it's unclear if there are any differences between what you show here and in your actual system which could be causing the problem.

So why don't we try something different?  Listed below is a somewhat simplified version of code for what you are trying to do that tries to use standard Fortran and C.  It works on my system and gives the output shown below.  Can you take this as-is and build execute on your system without any code changes and see what output you get?  Post here if your output is different.  This might help you figure out what is going on.

module m

   use, intrinsic :: iso_c_binding, only : c_char, c_null_char, c_long, c_double

   implicit none

   private

   public :: addsize

contains

   function addsize( maxn, delta, conf, msgLen, Msg ) result( answer ) bind(c, name='addsize')

      !.. argument list
      integer(c_long), value, intent(in)          :: maxn
      real(c_double), value, intent(in)           :: delta
      real(c_double), value, intent(in)           :: conf
      integer(c_long), value, intent(in)          :: msgLen
      character(kind=c_char,len=1), intent(inout) :: Msg(msgLen)
      !.. function result
      integer(c_long) :: answer

      !.. local variables
      integer :: i
      integer(c_long) :: LenMsg
      character(kind=c_char,len=:), allocatable :: errmsg

      !.. Message
      errmsg = "hello"
      LenMsg = min( msgLen-1, len_trim(errmsg) )
      forall (i=1:LenMsg)
         Msg(i) = errmsg(i:i)
      end forall
      !.. Append C null char
      Msg(LenMsg+1) = c_null_char

      answer = int( maxn + delta + conf ) ! the sample size

      return

   end function addsize

end module m
#include <stdio.h>
#include <string.h>

// function prototypes
extern long addsize(long maxn, double delta, double conf, long msgLen, char *Msg);

#define ERR_MSG_LENGTH 70

int main()
{

   long result, maxn;
   double delta, conf;
   char errMsg[ERR_MSG_LENGTH];

   maxn = 1000;
   delta = 3.2;
   conf = 6.8;

   strncpy(errMsg, "There!", (size_t)ERR_MSG_LENGTH);
   printf("errMsg before calling Fortran function addsize: %s\n", errMsg);

   result = addsize(maxn, delta, conf, (long)ERR_MSG_LENGTH, errMsg);

   printf("result=%ld\n", result);
   printf("errMsg after function call: %s\n", errMsg);

   return 0;

}
errMsg before calling Fortran function addsize: There!
result=1010
errMsg after function call: hello
Press any key to continue . . .

By the way, you have mixed-mode arithmetic in your Fortran code computing answer that adds an integer with two floating point variables and then casts back to an integer - is this really what you intended?  This can cause unexpected behavior unless one is very careful.

P.S.>  Are you at Penn State?  Go Nittany Lions! 

View solution in original post

0 Kudos
30 Replies
Steven_L_Intel1
Employee
1,818 Views

You need to add the VALUE attribute to the Fortran declaration of maxn, delta and conf. Like this:

real(C_LONG), value, intent(in) :: maxn
real (C_DOUBLE), value, intent(in) :: delta, conf

Your C prototype has these passed by value, so you need to say that in Fortran.

0 Kudos
lxh37
Beginner
1,818 Views

 

Thanks for the suggestions, I add the VALUE attribute to the Fortran as you suggested, but still have trouble. 

Liying

0 Kudos
Steven_L_Intel1
Employee
1,818 Views

The declarations of maxn and sum in the Fortran code needs to be "integer(C_LONG)", not "real(C_LONG)".

0 Kudos
lxh37
Beginner
1,818 Views

 

 

Great, it works now! I guess that probably Mac version is more forgiven when I used  DLLEXPORT, and didn't use  iso_c_binding. I will try to rewrite fortran code using iso_c_binding. Only the function called from C needs to use iso_c_binding, the rest functions don't, right?

Thanks very much for your help!

Liz

0 Kudos
lxh37
Beginner
1,818 Views

 

I have anotehr question, if my fortran function is:

integer(our_int) function samplesize( maxn, delta, conf, msgLen, iMsg ) result( answer )

how to use iso_c_binding? 

Can I do:

integer(our_int) function samplesize( maxn, delta, conf, msgLen, iMsg )  bind(c, name='samplesize')

or I have to use subroutine without returning a value? (I only find document for subroutine when call Fortran 

from C).

Thanks!

Liz

0 Kudos
Steven_L_Intel1
Employee
1,818 Views

I would suggest:

function samplesize( maxn, delta, conf, msgLen, iMsg )  result(answer) bind(c, name='samplesize')
integer(our_int) :: answer

I assume you have made "our_int" available in code not shown.

ISO_C_BINDING provides declarations of various "kinds" that correspond to C types, plus some useful functions and types. You don't HAVE to use it, but it's more portable if you do. Also, you don't need ", name='samplesize'" in the BIND clause because BIND(C) automatically downcases the name.

 

0 Kudos
lxh37
Beginner
1,818 Views

 

Thanks! I will rewrite the simple Fortran code I had using 

function add( maxn, delta, conf, msgLen, iMsg )  result(answer) bind(c, name='samplesize')
integer(our_int) :: answer

to see if I can make it work.

If I don't use ISO_C_BINDING, I don't need this line: 

use, intrinsic :: iso_c_binding

but I still need to use "value" if I pass by value? I will see if i Can get it working.

Thanks!

Liz

0 Kudos
Steven_L_Intel1
Employee
1,818 Views

The combination of BIND(C) and VALUE is what you need. ISO_C_BINDING isn't relevant to pass-by-value.

0 Kudos
lxh37
Beginner
1,818 Views

 

Hi,

I can get it working without int *  iMsg . I must did something wrong when I

add it.  Please take a look? out_int, our_double are all defined in program_constants,

they are equivalent to C_LONG, C_DOUBLE.

In add.c, I declare 

extern long addsize(long maxn, double delta, double conf, long msgLen, int *iMsg);

Fortran code below:

 

integer(our_int) function addsize( maxn, delta, conf, msgLen, iMsg ) result( answer ) bind(c, name='addsize')
!
use program_constants

implicit none

integer(our_int), value, intent(in) :: maxn
real(our_dble), value, intent(in) :: delta, conf
integer(our_int), value, intent(in) :: msgLen
integer(kind=our_int), intent(inout) :: iMsg(msgLen)

integer(our_int) :: i
character(len=70) :: errmsg

errmsg = "hello"

do i=1,msgLen
    iMsg(i) = iachar( errmsg(i:i) )
end do
! insert a "null" at end of error string (for C)
iMsg( len_trim( errmsg ) + 1 ) = 0

answer = int( maxn + delta + conf ); ! the sample size
return

end function addsize

Thanks a lot!

Liz

 

 

0 Kudos
Steven_L_Intel1
Employee
1,818 Views

It's hard for me to check for errors with only parts of the program, and also when you don't say what goes wrong. 

I see that you've now added a character argument, but I don't see what iMsg is on the C side. You pass a pointer to... something... and treat it as a character string on the Fortran side.

At this point I suggest that you step through the program with a debugger and trace the paths and values to whatever it is that goes wrong. This should give you a clue as to how to fix it.

You had initially said that the code worked on Mac, but later said you had made changes. The Mac code should have worked on Linux with nothing more than dropping DLLEXPORT.

If you get stuck, please come back with a small but complete program, with the all of the C and Fortran code included, and we may be able to help further.

0 Kudos
lxh37
Beginner
1,818 Views

 

Now I puzzled how the codes (they are legacy codes and don't use bind and VALUE) could work in Mac,

the first thing I did earlier in the week is commenting out DLLEXPORT and compile in Linux, but the code will not work. So I tried to read  documents how to call Fortran from C  and implemented a very simple example which only have VALUE inputs, thank you for your help, and I can call it successfully from Ruby On Rails, When you told me that I don't have to use iso_c_binding, I  tried today to make the simple example similar as the legacy code ( tried to see if I don't have to revise too much to make the code work). As you spot the issues so quickly yesterday, I thought if I just post Fortran code, you could tell if there is any obvious error. The legacy code tried to avoid passing string from Fortran to C, it uses int * instead and cast it to C later after getting it from Frotran. Let me send you too the simple C code: add.c which will call Fortran code addsize.f90 I sent this morning, I couldn't see anything wrong....

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern long addsize(long maxn, double delta, double conf, long msgLen, int *iMsg);

long add(long maxn, double delta, double conf, char *errMsg)
{
        long ERR_MSG_LENGTH, answer;
        unsigned i;
        int *iarr;

        ERR_MSG_LENGTH = 70;

        iarr = (int *) malloc(ERR_MSG_LENGTH*sizeof(int));
        for (i=0; i < ERR_MSG_LENGTH; i++) iarr = (int) ' ';

        answer = addsize(maxn, delta, conf, ERR_MSG_LENGTH, iarr);

         for (i=0; ((char) iarr) != '\0'; ++i) errMsg = (char) iarr;

         errMsg =  '\0';

        return answer;
}

add.h

//
//  add.h
//  add
//
//  Created by lxh37 on 11/19/2015.
//  Copyright (c) 2015 Penn State. All rights reserved.
//

#ifndef add_h
#define add_h

long  add(long maxn, double delta, double conf, char *errMsg);

and constants.f90 which defines our_int and our_dble (could use C_DOUBLE instead)

!#######################################################################
module program_constants
  USE, INTRINSIC:: ISO_C_BINDING
  implicit none
  public           ! unlike other modules, everything here is public
  integer, parameter :: our_dble=selected_real_kind(15,307), &
       our_int=C_LONG
end module program_constants
!#######################################################################

 

0 Kudos
lxh37
Beginner
1,818 Views

 

I mean "avoid passing string from Fortran to C, it uses int * instead and cast it to char array later after getting int * back from Frotran".

The issue is mainly with additional int *, when I delete it, revise and comment out all codes related to it, it works. 

Thanks!

Liz

0 Kudos
Steven_L_Intel1
Employee
1,818 Views

What doesn't work? What goes wrong? It would be easier if you could add a short C program that calls "add".

0 Kudos
lxh37
Beginner
1,818 Views

 

I generated .so and called from Ruby On Rails, only error message I got

is error with external library, guess there must be issue with my code. 

I created this simple call_add.c below to call add, 

 #include<stdio.h>
  int main(int argc, char **argv) {
  long result, maxn;
  double delta, conf;
  char *errMsg;
  maxn = 1000;
  delta = 3.2;
  conf = 6.8;
  errMsg = "There!";
  result = add(maxn, delta, conf, errMsg);
  printf("%d\n",result);
  return;
  }

 

0 Kudos
Steven_L_Intel1
Employee
1,818 Views

Thanks. Your Fortran-C code works perfectly. There is a problem with this C line after the Fortran call returns,

    for (i = 0; ((char)iarr) != '\0'; ++i) errMsg = (char)iarr;

I'm not a C expert but I think the issue is that errMsg is pointing to a constant which you're not allowed to overwrite.

0 Kudos
mecej4
Honored Contributor III
1,818 Views

A literal string in C code is placed in the data segment. Whether that is a writable segment or a read-only segment depends on the C compiler and the options given to the C compiler. Perhaps the real issue is when one passes a string pointer to a subprogram when that subprogram writes beyond the end of the literal string to which the pointer was set, without knowing or caring about the allocated length of the target string. Here is C code to illustrate this point.

#include <stdio.h>

void sub(char *s){
char *p="This is a long string";
while(*p)*s++=*p++; *p='\0';   // Intentional bug here! Try also *s='\0' instead of *p='\0'
}

main(){
char *s="Initial String";
char *p="Sacrificial string";
sub(s);
puts(s);
puts(p);
}

When I compile (on Windows) this with cl /Od /MD, the output is

This is a long stringficial string
tringficial string

which is evidence of the clobbering that occurred without triggering any access violations. If I compile (on Windows) with cl /O2 /MD, the program crashes.

In the example code of #15, errmsg points to a character array that is 6+1 bytes long. However, this pointer is passed to another function in which a much longer string is written into the memory that it points to.

Such errors in C can be difficult to catch, as a result of which they can go undetected for long, but tools such as Intel Inspector can help.

0 Kudos
lxh37
Beginner
1,818 Views

 

Steve and mecej4. thanks for your help! I think the bug is still in Fortran. I commented out 

         for (i=0; ((char) iarr) != '\0'; ++i) errMsg = (char) iarr;

         errMsg =  '\0';

and add:

        char *iarr1;
        iarr1 = "Hello!";

        for (i=0; ((char) iarr1) != '\0'; ++i) errMsg = (char) iarr1;
        errMsg =  '\0';

in add.c, but the program  crashes,

If I commented out call to Fortran function

        //answer = addsize(maxn, delta, conf, ERR_MSG_LENGTH, iarr);

       answer = 100;

        char *iarr1;
        iarr1 = "Hello!";

        for (i=0; ((char) iarr1) != '\0'; ++i) errMsg = (char) iarr1;
        errMsg =  '\0';      

The program works fine.

I will try to increase the length of errMsg, see if that helps.

helps...

Liz

0 Kudos
Steven_L_Intel1
Employee
1,818 Views

As best as I can tell, the Fortran code is working correctly.

0 Kudos
lxh37
Beginner
1,818 Views

 

Fortran code seems good to me too, I have passed

ERR_MSG_LENGTH = 70 to Fortran and only assigned a short string

"Hello!", so it should be fine. I feel the crash should be in C where errMsg

passed in from call_c.add is short...

        for (i=0; ((char) iarr1) != '\0'; ++i) errMsg = (char) iarr1;
        errMsg =  '\0';

Don't know why it stlll crashes even I comment out the above...

0 Kudos
Steven_L_Intel1
Employee
1,436 Views

I suggest you take your C driver code and spend some time in gdb to understand what is going wrong.

0 Kudos
Reply