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

Problems with passing data between C++ and F90

ferrad1
New Contributor I
833 Views

I have a situation where a C++ main program is calling functions in a F90 DLL.  This comes from a very large project, so I have trimmed it down a simple example which reproduces the problem I am having, which is:

The main C++ program creates a variable "stuff" of type "tstuff".  "tstuff" contains just one element "substuff" which is of type "void"  (it has to be this way as in practice substuff is extremely large and complex on the F90 side and I can't and indeed don't need to do reproduce it on the C++ side)

It then calls crfl() on the F90 side which populates "substuff" on the F90 side.

Then it calls the function stfl to send the character "TEST" into the member substuff%clb%corf on the F90 side.  I step through the debugger and all is good up to this point and "TEST" does indeed appear in the variable on the F90 side.

Now it goes strange.  The C++ code then calls the F90 routine test2 passing the variable stuff.  When I step into the F90 code, stuff%substuff%clb%corf contains garbage.

 

It won't let me attach the CPP file for some reason, so I list it below:

#include <iostream>
#include <cstddef>
#include <vector>

using namespace std;

struct t_stuff {
    void *substuff;
};
//Fortran subroutine definitions
extern "C" {
   void test2(t_stuff *stuff);
}

extern "C" 
{
   void * crfl();
   int    stfl(void * cstuff, char * name);
}

int main()
{
    int ierr;
    t_stuff *stuff;

    stuff = new t_stuff;
    (*stuff).substuff = crfl();
    ierr = stfl((*stuff).substuff, "TEST");
    test2(stuff);
}

Fortran code is attached.

0 Kudos
10 Replies
FortranFan
Honored Contributor II
818 Views

@ferrad1 ,

First, please try to refer to the language as Fortran and not F90.  You will note a F90 reference is meaningless, especially as you are using features from Fortran 2003 standard revision.

Then please note the following:

  1. As you have hinted, there is no interoperability between C++ and Fortran and therefore, toward the interoperation your C++ code will be safer enclosing all the constructs (and instructions) within the extern C clause,
  2. An opaque pointer such as `void *` under extern C only interoperates with `type(c_ptr)` on the Fortran side.

Given above, give this a try:

 

#include <iostream>
#include <cstddef>
#include <vector>

using namespace std;

//Fortran subroutine definitions
extern "C" {
    struct t_stuff {
        void *substuff;
    };
   void test2(t_stuff *stuff);
   void * crfl();
   int stfl(void *, char *);
}

int main()
{
    int ierr;
    t_stuff *stuff;

    stuff = new t_stuff;
    (*stuff).substuff = crfl();
    ierr = stfl((*stuff).substuff, "TEST");
    test2(stuff);
}
module ftncode_mod
   use, intrinsic :: iso_c_binding
   implicit none

   type, public :: t_clb
     character(8) :: corf
   end type

   type, public  :: t_bstuff
     type (t_clb) :: clb
   end type t_bstuff

   type, public, bind(C) :: t_stuff
     type(c_ptr) :: psubstuff
   end type t_stuff

   contains

      subroutine c2fstr(cptr,fstr)  !<-- eventually replace this with C_F_STRPOINTER from Fortran 2023 standard
        type(c_ptr), value, intent(in ) :: cptr
        character(kind=c_char,len=*), intent(out) :: fstr  !<-- Technically this works with c_char KIND only
        character(kind=c_char,len=256), pointer :: lfstr
        integer :: id
        call c_f_pointer(cptr,lfstr)
        id = index(lfstr, c_null_char)-1  
        if(id.le.0) id=len(lfstr)
        fstr = lfstr(1:id)
      end subroutine

      function crfl() result(cp) bind(C)
      !DEC$ ATTRIBUTES DLLEXPORT :: crfl
        type(c_ptr     )          :: cp
        type(t_bstuff), pointer :: fp
        allocate(fp)
        cp = c_loc(fp)
      end function

      function stfl(cstuff,cne) result(ierr) bind(C)
      !DEC$ Attributes dllexport :: stfl
        type(c_ptr), value, intent(in)     :: cstuff
        type(c_ptr), value, intent(in)     :: cne 
        integer                            :: ierr  
        type(t_bstuff)    ,  pointer       :: fstuff
        character(24)                      :: cwk 
        ierr = 0  
        call c_f_pointer(cstuff,fstuff)
        call c2fstr(cne,cwk)
        fstuff%clb%corf = trim(cwk)
      end function

      subroutine test2(stuff) bind(C)
      !DEC$ATTRIBUTES DLLEXPORT :: test2
        use iso_c_binding
        type(t_stuff), target, intent(in) :: stuff
        type(t_bstuff), pointer :: fstuff
        call c_f_pointer(stuff%psubstuff,fstuff)
        print *, "stuff%substuff%clb%corf: ", fstuff%clb%corf
        fstuff => null() !<-- Not a must, just good practice
      end subroutine test2
end module

 

  • Build and execute on Windows with Intel Fortran and Microsoft C++ toolchain:

 

C:\temp>ifort /c /standard-semantics ftncode.f90
Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel(R) 64, Version 2021.8.0 Build 20221119_000000
Copyright (C) 1985-2022 Intel Corporation.  All rights reserved.


C:\temp>cl /c /W3 /EHsc c++.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.34.31937 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

c++.cpp

C:\temp>link c++.obj ftncode.obj /subsystem:console /out:c++.exe
Microsoft (R) Incremental Linker Version 14.34.31937.0
Copyright (C) Microsoft Corporation.  All rights reserved.

   Creating library c++.lib and object c++.exp

C:\temp>c++.exe
 stuff%substuff%clb%corf: TEST

 

Last but not the least: note the above verifies your expected result in a console test.  Do such verification first because you may face issues with Intel Fortran integration with Visual Studio if that is the debugger you are using whereby depending on your version(s) and installation aspects, the debugger may fail to render the objects accurately in the Fortran subprogram instance on the call stack.  That is a separate problem.

 

ferrad1
New Contributor I
811 Views

(Reiterating on this reply!)

Thanks.  Yes I'm so used to calling the Fortran code F90 due to the extension...

I have taken a look at the code changes and see the main change is the void <> c_ptr connectivity.

The main problem still exists however.  In test2, I need to operate on stuff not fstuff.  stuff is the structure which needs to be passed on to subsequent routines so stuff%substuff must be correct.  It is still garbage.

 

0 Kudos
FortranFan
Honored Contributor II
805 Views

Re: "One question I have is, by changing the type of substuff to c_ptr I have problems in the rest of the code which uses it.  Is there a way to avoid this or must I make changes in all the code that uses it?"

- it points to nonconforming aspects (some would call them bugs) somewhere in your code.  You can either spend the time to thoroughly review and fix your code wherever needed or try to kluge your way through such that you somehow get it to work with both the debugger and the compiler version today.  With the latter, you also run the risk the code stops working in a future compiler version with stricter conformance requirements or one that does not work with any other compiler from another vendor or an open source effort out there (presently gfortran but there will be others like it).

0 Kudos
ferrad1
New Contributor I
796 Views

I updated my reply later with a more specific issue:

The main problem still exists however.  In test2, I need to operate on stuff not fstuff.  stuff is the structure which needs to be passed on to subsequent routines so stuff%substuff must be correct.  It is still garbage.

0 Kudos
FortranFan
Honored Contributor II
793 Views

Toward the C_F_STRPOINTER option from the Fortran 2023 revision, see this comment.

For the code in your original post at least, here is a variant to consider.  Note the `bind(C)` on type `t_stuff` - look into that in your actual code.

module cstring_m
   use, intrinsic :: iso_c_binding, only : c_char, c_size_t, c_ptr, c_f_pointer
   generic :: c_f_strpointer => c_f_strpointer_cstrptr  !<-- Toward the eventual Fortran 2023 implementation
contains
   subroutine c_f_strpointer_cstrptr( cstrptr, fstrptr, nchars )
      ! Argument list
      type(c_ptr), intent(in)                             :: cstrptr
      character(kind=c_char, len=:), pointer, intent(out) :: fstrptr
      integer(c_size_t), intent(in)                       :: nchars
      if ( nchars >= 0 ) then
         block 
            character(kind=c_char, len=nchars), pointer :: sptr
            call c_f_pointer( cstrptr, sptr )
            fstrptr => sptr
            sptr => null()
         end block
      end if
      return
   end subroutine 
end module

module ftncode_mod

   use, intrinsic :: iso_c_binding
   use cstring_m, only : c_f_strpointer
   implicit none

   type, public :: t_clb
      character(8) :: corf
   end type

   type, public  :: t_bstuff
      type (t_clb) :: clb
   end type t_bstuff

   type, public, bind(C) :: t_stuff
      type(c_ptr) :: psubstuff
   end type t_stuff

   type(t_bstuff), allocatable, target, save :: fp

contains

   function crfl() result(cp) bind(C)
   !DEC$ ATTRIBUTES DLLEXPORT :: crfl
      type(c_ptr) :: cp
      if (.not. allocated(fp) ) allocate(fp)
      cp = c_loc(fp)
   end function

   function stfl(cstuff,cne) result(ierr) bind(C)
   !DEC$ Attributes dllexport :: stfl
      type(c_ptr), value, intent(in) :: cstuff
      type(c_ptr), value, intent(in) :: cne
      ! Function result 
      integer :: ierr
      ! Local variables  
      type(t_bstuff), pointer :: fstuff
      character(kind=c_char,len=:), pointer :: cwk 
      ierr = 0  
      call c_f_pointer(cstuff,fstuff)
      call c_f_strpointer( cne, cwk, len(fstuff%clb%corf,kind=c_size_t) )
      fstuff%clb%corf = cwk
   end function

   subroutine test2(stuff) bind(C)
   !DEC$ATTRIBUTES DLLEXPORT :: test2
      type(t_stuff), target, intent(in) :: stuff
      type(t_bstuff), pointer :: fstuff
      call c_f_pointer(stuff%psubstuff,fstuff)
      print *, "stuff%substuff%clb%corf: ", fstuff%clb%corf
      fstuff => null() !<-- Not a must, just good practice
   end subroutine test2

end module
C:\temp>c++.exe
 stuff%substuff%clb%corf: TEST

 

 

0 Kudos
ferrad1
New Contributor I
791 Views

Will look at it in full later, but I still see in your example:

  print *, "stuff%substuff%clb%corf: ", fstuff%clb%corf

I need to see: 

  print *, "stuff%substuff%clb%corf: ", stuff%substuff%clb%corf

This still fails for me.  

0 Kudos
FortranFan
Honored Contributor II
773 Views

Re: "I need to see: .. stuff%substuff%clb%corf"

Ultimately that will only be feasible in a standard-conforming and portable manner if the type corresponding to object `substuff` is fully interoperable with the C companion processor.  But that will also call for refactoring your code if what is shown in the original post reflects closely your actual code.  Now, refactoring should help simplify the code if done properly.  Your code in the original post has all the elements of hacking away at it until something works and then complaining about problems in the debugger.  Keeping it simple and structured can help avoid problems in the first place.

With the code in the original post, one quick option at restructuring it to get to "I need to see: stuff%substuff%clb%corf" in the Fortran side will be like so:

 

module ftncode_mod

   use, intrinsic :: iso_c_binding
   implicit none

   integer, parameter :: SLEN = 8

   type, public, bind(C) :: t_clb
      character(kind=c_char,len=1) :: corf(SLEN)
   end type

   type, public, bind(C)  :: t_bstuff
      type(t_clb) :: clb
   end type t_bstuff

   type, public, bind(C) :: t_stuff
      type(t_bstuff) :: substuff
   end type t_stuff

contains

   function stfl(bstuff,cne) result(ierr) bind(C)
   !DEC$ Attributes dllexport :: stfl
      type(t_bstuff), intent(inout) :: bstuff
      type(c_ptr), value, intent(in) :: cne
      ! Function result 
      integer :: ierr
      ! Local variables  
      character(kind=c_char,len=1), pointer :: cwk(:) 
      ierr = 0  
      call c_f_pointer( cne, cwk, shape=[ SLEN ] )
      bstuff%clb%corf = cwk
   end function

   subroutine test2(stuff) bind(C)
   !DEC$ATTRIBUTES DLLEXPORT :: test2
      type(t_stuff), target, intent(in) :: stuff
      print *, "stuff%substuff%clb%corf: ", stuff%substuff%clb%corf
   end subroutine test2

end module
#include <iostream>
#include <cstddef>
#include <vector>

using namespace std;

//Fortran subroutine definitions
extern "C" {
   enum SLEN { SLEN = 8 };
   typedef struct {
      char corf[SLEN];
   } t_clb;
   typedef struct  {
      t_clb clb;
   } t_bstuff;
   typedef struct {
      t_bstuff substuff;
   } t_stuff;
   void test2(t_stuff *stuff);
   int stfl(t_bstuff *, char *);
}

int main()
{
   int ierr;
   t_stuff *stuff;
   stuff = new t_stuff;
   ierr = stfl(&(*stuff).substuff, "TEST");
   test2(stuff);
}
C:\temp>ifort /c /standard-semantics ftncode.f90
Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel(R) 64, Version 2021.8.0 Build 20221119_000000
Copyright (C) 1985-2022 Intel Corporation.  All rights reserved.


C:\temp>cl /c /W3 /EHsc c++.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.34.31937 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

c++.cpp

C:\temp>link c++.obj ftncode.obj /subsystem:console /out:c++.exe
Microsoft (R) Incremental Linker Version 14.34.31937.0
Copyright (C) Microsoft Corporation.  All rights reserved.

   Creating library c++.lib and object c++.exp

C:\temp>c++.exe
 stuff%substuff%clb%corf: TEST

 

0 Kudos
ferrad1
New Contributor I
750 Views

This won't work as substuff has to be a void on the C++ side.  This is because t_bstuff is in practice enormous and complex and can't be represented on the C++ side.  substuff is only used on the Fortran side which needs to manipulate the C++ void pointer to look like a Fortran pointer to a substuff structure which the Fortran will populate.

0 Kudos
FortranFan
Honored Contributor II
699 Views

@ferrad1 ,

Hopefully others can show how you can have your cake and eat it too .. or you can try other compiler-specific hacks and stuff and also search far and wide .. comp.lang.fortran, StackOverflow, and https://fortran-lang.discourse.group/ are other places to ask!

0 Kudos
ferrad1
New Contributor I
686 Views

I guess you're saying what I'm asking is not possible?

0 Kudos
Reply