Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
Beginner
15 Views

New Interoperability: Fortran to C++ (String case)

Jump to solution

I would like to send a Fortran string to my C++ application, I tried to to this, but I got this linker error:

unresolved external symbol for_cpystr referenced in function my_long_calc

My Fortran Static Lib Code (Visual Fortran):

module my_interfaces
    use iso_c_binding
        interface
            subroutine fortran_message(msg) bind(C, name = 'fortran_message')
                use, intrinsic :: iso_c_binding
                
                character(kind = c_char), dimension(*), intent(out) :: msg
                
            end subroutine fortran_message
        end interface
end module my_interfaces

! My long calc subroutine
subroutine my_long_calc() BIND(C, name = 'my_long_calc')
    use, intrinsic :: ISO_C_BINDING
    use my_interfaces
    implicit none
    
    ! A single variable:
    character(len = 40, kind = c_char) :: msg
    
    msg = "this is a msg from fortran"
    
    ! Send a message to the C++ application:
    call fortran_message(msg)
end subroutine my_long_calc

My C++ Code:
 

#include <iostream>

extern "C" {
  void my_long_calc(void);
  void fortran_message(char* msg);
}

void fortran_message(char* msg)
{
  std::cout << *msg << std::endl;
}

int main(int argc, char *argv[])
{
    my_long_calc();

    return 0;
}

Please, what I have to do to link this library and get the "msg" string inside the C++ application?

0 Kudos

Accepted Solutions
Highlighted
Black Belt
15 Views

extern "C" {

Jump to solution
extern "C" {
	void fortran_subroutine(char* jpath, char* lpath);
}

int main(void)
{
	char json_path[100] = "../files/json/my_example.json";
	char log_path[100] = "../files/log/my_log.json";

	fortran_subroutine(json_path, log_path);

	return 0;
}
module str_routines
    contains
    subroutine C_F_STRPOINTER (STRARRAY, FSTRPTR, MAXLEN)
    use, intrinsic :: ISO_C_BINDING
    implicit none
    character, dimension(*), target, intent(in) :: STRARRAY
    character(:), pointer, intent(out) :: FSTRPTR
    integer, intent(in), optional :: MAXLEN

    integer :: curlen

    curlen = 0
    
    do
        curlen = curlen +1
        if (PRESENT(MAXLEN)) THEN
            if (curlen > MAXLEN) exit
        end if
        if (STRARRAY(CURLEN) == CHAR(0)) exit
    end do

    call doassign(C_LOC(STRARRAY), FSTRPTR, curlen-1)

    contains

    subroutine doassign(CSTRPTR, FSTRPTR, STRLEN)
        type(C_PTR), intent(in) :: CSTRPTR
        character(:), pointer, intent(out) :: FSTRPTR
        integer, intent(in) :: STRLEN

        character(STRLEN), pointer :: p

        call C_F_POINTER(CSTRPTR, p)
        FSTRPTR => p
        return
    end subroutine doassign
    end subroutine C_F_STRPOINTER
    end module str_routines
    
    
subroutine fortran_subroutine(json_path, log_path) bind(C)
    use, intrinsic :: ISO_C_BINDING
                
!        use json_module
         use str_routines
        
        implicit none
        character(kind=C_CHAR), target, intent(in) :: json_path(*)
        character(kind=C_CHAR), target, intent(in) :: log_path(*)
        
        character(kind=C_CHAR, len=:), pointer :: jpath, lpath
        
        call C_F_STRPOINTER (json_path, jpath, 80)
        call C_F_STRPOINTER (log_path, lpath, 80)
        
        print "('""',A,'""')", jpath, lpath
        
!        type(json_file) :: my_data, my_log
        
        ! initialize the class
!        call my_data%initialize()
!        call my_log%initialize()
        
        ! read the file: SHOUD BE filename = json_path
!        call my_data%load_file(filename = 'inout.json')

        ! read the file: SHOUD BE filename = log_path
!        call my_log%load_file(filename = 'inout.json')

end subroutine fortran_subroutine

 

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran

View solution in original post

0 Kudos
25 Replies
Highlighted
Black Belt
15 Views

Two things:

Jump to solution

Two things:

In the Fortran code, you have to NUL-terminate the string:

msg = "this is a msg from fortran"//C_NULL_CHAR

In the C++ code, you have an extra level of indirection. Remove the * in *msg in the output line, like so:

std::cout << msg << std::endl;

 

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Valued Contributor III
15 Views

See your other thread, Quote

Jump to solution

See your other thread, Quote #8:

https://software.intel.com/en-us/forums/intel-visual-fortran-compiler-for-windows/topic/815889

While passing "string" data from/to Fortran with other languages such as C++,  passing the string length as shown can simplify the interoperation considerably.

0 Kudos
Highlighted
Beginner
15 Views

@Steve, thanks for your quick

Jump to solution

@Steve, thanks for your quick response...

I did the 2 things that you propose to me, but I got the same error yet.

unresolved external symbol for_cpystr referenced in function my_long_calc



My modified Fortran Code:
 

module my_interfaces
    use iso_c_binding
        interface
            subroutine fortran_message(msg) bind(C, name = 'fortran_message')
                use, intrinsic :: iso_c_binding
                
                character(kind = c_char), dimension(*), intent(out) :: msg
                
            end subroutine fortran_message
        end interface
end module my_interfaces

! My long calc subroutine
subroutine my_long_calc() BIND(C, name = 'my_long_calc')
    use, intrinsic :: ISO_C_BINDING
    use my_interfaces
    implicit none
    
    ! A single variable:
    character(len = 40, kind = c_char) :: msg
    
    msg = "this is a msg from fortran"//C_NULL_CHAR
    
    ! Send a message to the C++ application:
    call fortran_message(msg)
end subroutine my_long_calc

My modified C++ code:

#include <iostream>

extern "C" {
    void my_long_calc(void);
    void fortran_message(char* msg);
}

void fortran_message(char* msg)
{
    std::cout << msg << std::endl;
}

int main(void)
{
    my_long_calc();

    return 0;
}

Could you help me again?

In this small example, I'm trying to get a simple string from fortran to C++....but if you are can make me one additional favor, my goal is to send 1 integer value argument and 2 independent strings (the string size can be constant) from fortran to C++. Is it possible?

I think that my C++ header could be:
 

void fortran_message(int* value, char* msg1, size_t* len1, char* msg2, size_t* len2)

That´s it?

Thank you again,

0 Kudos
Highlighted
Black Belt
15 Views

You will need to link in the

Jump to solution

You will need to link in the Fortran support libraries. If you are using Intel Fortran and Visual Studio, see https://software.intel.com/en-us/articles/configuring-visual-studio-for-mixed-language-applications

The interface for your revised fortran_message function would be:

interface
  subroutine fortran_message (value, msg1, len1, msg2, len2) bind(C)
    use, intrinsic :: ISO_C_BINDING
    integer(C_INT), intent(in) :: value
    character(kind=C_CHAR), intent(in) :: msg1(*)
    integer(C_SIZE_T), intent(in) :: len1
    character(kind=C_CHAR), intent(in) :: msg2(*)
    integer(C_SIZE_T), intent(in) :: len2
  end subroutine fortran_message
end interface

You will, of course, need to pass the lengths separately. I might suggest that it is actually easier to nul-terminate the strings than pass the lengths. If you pass lengths you will need to change the C++ code, and I don't know what that change would be (I am not a C++ programmer - I can stumble around in C.)

You might also choose to pass the lengths by value (size_t instead of size_t*). If so, add the VALUE attribute to those arguments in the Fortran interface. (I will comment that VALUE changes its meaning when you add BIND(C) to the interface or procedure. For a BIND(C) routine it means what you would expect - pass by value. Otherwise it means "pass a writable, anonymous copy".)

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
15 Views

@Thank Steve, I think that we

Jump to solution

@Thank Steve, I think that we are close to the perfect linking process...

2 things:

First: Thanks for your sugestion about not pass the lenghts of the string. Yes, I would like to pass only the string itself.
I undertand that C++ is not your main language, but could you show me how will be this "print string function" using pure C?

Second: Trying to solve the linker error above, I added to my compile command line (using QT Creator IDE) all the Intel Visual Fortran "include" and "library" folders that Microsoft Visual Studio 2019 are using inside of itself.

I looked at: Visual Studio 2019 -> Tools menu -> Options -> Intel Compilers and Tools -> Visual Fortran -> Compilers -> x64 (tab)
ivf.jpg

I expand each VS macro and I got this:
Includes:
 

C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/um 
C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/shared 
C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2019/windows/compiler/include
C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2019/windows/compiler/include/Intel64 
C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2019/windows/mkl/include 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/VC/Tools/MSVC/14.21.27702/atlmfc/include 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/PlatformSDK/include/um 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/PlatformSDK/include/shared 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/PlatformSDK/include 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/VC/Tools/MSVC/14.21.27702/lib/x64 
C:/Program Files (x86)/Windows Kits/10/Lib/10.0.17763.0/um/x64 
C:/Program Files (x86)/Windows Kits/10/Lib/10.0.17763.0/ucrt/x64 
C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2019/windows/compiler/lib/Intel64_win 
C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2019/windows/mkl/lib/Intel64_win 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/VC/Tools/MSVC/14.21.27702/atlmfc/lib/x64 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/PlatformSDK/lib/winv6.3/um/x64



Libraries:
 

C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/VC/Tools/MSVC/14.21.27702/lib/x64 
C:/Program Files (x86)/Windows Kits/10/Lib/10.0.17763.0/um/x64 
C:/Program Files (x86)/Windows Kits/10/Lib/10.0.17763.0/ucrt/x64 
C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2019/windows/compiler/lib/Intel64_win 
C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2019/windows/mkl/lib/Intel64_win 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/VC/Tools/MSVC/14.21.27702/atlmfc/lib/x64 
C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/VC/PlatformSDK/lib/winv6.3/um/x64



Inside QT Creator IDE (Windows 10 x64), my compiler is Microsoft Visual C++ 16.1, so my Intel Visual Fortran static library should be compatible, right?

But I got the same linker error:
 

unresolved external symbol for_cpystr referenced in function my_long_calc

Maybe should I need to pass each IVF library file to my compile command line? I don't know...And, if yes, how can I know which library?

Could you help me a little bit more?

0 Kudos
Highlighted
Beginner
15 Views

UPDATE:

Inside Visual Fortran project now I set the "Disable Default Library Search Rules" to NO...and I passed the linker error above.

ivf.jpg

Now, the QT Creator IDE gives me a error about "ia32" folder ....I found this ia32 folder and added it in the QT Creator libraries path.
So, the error now is:
 

error: LNK1181: cannot open input file 'debug\.obj'

What I have to do now?

0 Kudos
Highlighted
Beginner
15 Views

UPDATE 2:

Jump to solution

UPDATE 2:

I closed QT Creator IDE and cleaned all temp files...

Open it again and tried to compile/link my project. So, I got:
 

LNK1104: cannot open file 'C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2019.4.245\windows\compiler\lib\ia32_win.obj'

First the linker asked me about "ifconsol.lib"...then I added the path to this lib:
 

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2019.4.245\windows\compiler\lib\ia32_win

Now it is asking about a *.obj file? What is wrong?

0 Kudos
Highlighted
Beginner
15 Views

UPDATE 3: Almost there!

Jump to solution

UPDATE 3: Almost there!

@Steve, I finally got the Fortran string in my C++ application, but the string are just the first character repeating multiple times...
Please, if you could look my code again, I think that will be that last post of this topic.

My Fortran Code:
 

module my_interfaces
interface
  subroutine fortran_message (value, msg1, len1, msg2, len2) bind(C)
    use, intrinsic :: ISO_C_BINDING
    integer(C_INT), intent(in) :: value
    character(kind=C_CHAR), intent(in) :: msg1(*)
    integer(C_SIZE_T), intent(in) :: len1
    character(kind=C_CHAR), intent(in) :: msg2(*)
    integer(C_SIZE_T), intent(in) :: len2
  end subroutine fortran_message
end interface
end module my_interfaces

! My long calc subroutine
subroutine my_long_calc() BIND(C, name = 'my_long_calc')
    use, intrinsic :: ISO_C_BINDING
    use my_interfaces
    implicit none
    
    integer(C_INT) :: value
    character(kind=C_CHAR) :: msg1(5)
    integer(C_SIZE_T) :: len1
    character(kind=C_CHAR) :: msg2(5)
    integer(C_SIZE_T) :: len2
    
    value = 10
    msg1 = "nyck"//C_NULL_CHAR
    len1 = 5
    msg2 = "sara"//C_NULL_CHAR
    len2 = 5
    
    ! Send a message to the C++ application:
    call fortran_message(value, msg1, len1, msg2, len2)
end subroutine my_long_calc

My C++ code:
 

#include <iostream>

extern "C" {
  void my_long_calc(void);
  void fortran_message(int* value, char* msg1, size_t* len1, char* msg2, size_t* len2);
}

void fortran_message(int* value, char* msg1, size_t* len1, char* msg2, size_t* len2)
{
  std::cout << "value: " << *value << std::endl;
  std::cout << "msg1: " << msg1 << std::endl;
  std::cout << "len1: " << *len1 << std::endl;
  std::cout << "msg2: " << msg2 << std::endl;
  std::cout << "len2: " << *len2 << std::endl;
}

int main(void)
{
    my_long_calc();

    return 0;
}

My console:

console.jpg

If is possible, I would like to send only the Fortran string (without the length parameter).....this string maybe will varying the size in run-time...
You can write in pure C function...no problems...

Thank you!

0 Kudos
Highlighted
Black Belt
15 Views

The thing to remember about

Jump to solution

The thing to remember about passing strings is that a Fortran character string (any length) is interoperable with an array of C characters. You didn't want to declare msg1 and msg2 as arrays of characters. Use instead:

character(kind=C_CHAR, len=5) :: msg1
character(kind=C_CHAR, len=5) :: msg2

With that change your program works (using Intel Fortran and Microsoft C++). I can't comment on other combinations or tools.

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
15 Views

@Steve, now I would like to

Jump to solution

@Steve, now I would like to know how to do  the string conversion in the opposite direction: From C char array to Fortran String:
Please, look the this input argument called 'json_path'...this is a C char array that is passed to this Fortran subroutine using Fortran 2003 binding.

The value of this argument should be a full path of a JSON file.

 

subroutine fortran_subroutine(json_path) bind(C)
    use, intrinsic :: ISO_C_BINDING
                
        use json_module
        
        implicit none
        character(kind=C_CHAR), intent(in) :: json_path(*)
        
        type(json_file) :: json
        
        ! initialize the class
        call json%initialize()
        
        ! read the file: '../files/my_json.json'
        call json%load_file(filename = json_path)

end subroutine fortran_subroutine

How can I convert C char array to a Fortran string?

0 Kudos
Highlighted
Black Belt
15 Views

You've identified a big hole

Jump to solution

You've identified a big hole in Fortran's C interop features. I have proposed new intrinsics for the next revision to address this. (See here and here. The first link includes an implementation you can use today.)

Basically what you need to do is declare a CHARACTER(:), POINTER, determine the string length by searching for the NUL, then pointer assign using that length using C_LOC(string-array) and C_F_POINTER. This gets you a Fortran-friendly character value with length.

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
15 Views

@Steve, thank you for your

Jump to solution

@Steve, thank you for your Fortran example...but I'm a C/C++ guy and I'm newer in Fortran world.
I tried to understand this example, but it is very complex to my current Fortran knowledge.
But I can't wait to implement this feature in my job...could you please help me a little bit more?

My C code is like:

int main(void)
{
    char json_path[100] = "../files/json/my_example.json";
    char log_path[100] = "../files/log/my_log.json";
    
    fortran_subroutine(json_path, log_path);

    return 0;
}

I copied your Fortran subroutine, but I don't understand how to use it?

My Fortran code:
 

subroutine C_F_STRPOINTER (STRARRAY, FSTRPTR, MAXLEN)
    use, intrinsic :: ISO_C_BINDING
    implicit none
    character, dimension(*), target, intent(in) :: STRARRAY
    character(:), pointer, intent(out) :: FSTRPTR
    integer, intent(in), optional :: MAXLEN

    integer :: curlen

    curlen = 0
    
    do
        curlen = curlen +1
        if (PRESENT(MAXLEN)) THEN
            if (curlen > MAXLEN) exit
        end if
        if (STRARRAY(CURLEN) == CHAR(0)) exit
    end do

    call doassign(C_LOC(STRARRAY), FSTRPTR, curlen-1)

    contains

    subroutine doassign(CSTRPTR, FSTRPTR, STRLEN)
        type(C_PTR), intent(in) :: CSTRPTR
        character(:), pointer, intent(out) :: FSTRPTR
        integer, intent(in) :: STRLEN

        character(STRLEN), pointer :: p

        call C_F_POINTER(CSTRPTR, p)
        FSTRPTR => p
        return
    end subroutine doassign
end subroutine C_F_STRPOINTER
    
    
subroutine fortran_subroutine(json_path, log_path) bind(C)
    use, intrinsic :: ISO_C_BINDING
                
        use json_module
        
        implicit none
        character(kind=C_CHAR), intent(in) :: json_path(*)
        character(kind=C_CHAR), intent(in) :: log_path(*)
        
        type(json_file) :: my_data, my_log
        
        ! initialize the class
        call my_data%initialize()
        call my_log%initialize()
        
        ! read the file: SHOUD BE filename = json_path
        call my_data%load_file(filename = 'inout.json')

        ! read the file: SHOUD BE filename = log_path
        call my_log%load_file(filename = 'inout.json')

end subroutine fortran_subroutine

How can I pass the 2 C char arrays ('json_path' and 'log_path') to my Fortran subroutine in a way that I can use these Fortran commands?
 

call my_data%load_file(filename = json_path)
call my_log%load_file(filename = log_path)

Could you help me?

0 Kudos
Highlighted
Black Belt
16 Views

extern "C" {

Jump to solution
extern "C" {
	void fortran_subroutine(char* jpath, char* lpath);
}

int main(void)
{
	char json_path[100] = "../files/json/my_example.json";
	char log_path[100] = "../files/log/my_log.json";

	fortran_subroutine(json_path, log_path);

	return 0;
}
module str_routines
    contains
    subroutine C_F_STRPOINTER (STRARRAY, FSTRPTR, MAXLEN)
    use, intrinsic :: ISO_C_BINDING
    implicit none
    character, dimension(*), target, intent(in) :: STRARRAY
    character(:), pointer, intent(out) :: FSTRPTR
    integer, intent(in), optional :: MAXLEN

    integer :: curlen

    curlen = 0
    
    do
        curlen = curlen +1
        if (PRESENT(MAXLEN)) THEN
            if (curlen > MAXLEN) exit
        end if
        if (STRARRAY(CURLEN) == CHAR(0)) exit
    end do

    call doassign(C_LOC(STRARRAY), FSTRPTR, curlen-1)

    contains

    subroutine doassign(CSTRPTR, FSTRPTR, STRLEN)
        type(C_PTR), intent(in) :: CSTRPTR
        character(:), pointer, intent(out) :: FSTRPTR
        integer, intent(in) :: STRLEN

        character(STRLEN), pointer :: p

        call C_F_POINTER(CSTRPTR, p)
        FSTRPTR => p
        return
    end subroutine doassign
    end subroutine C_F_STRPOINTER
    end module str_routines
    
    
subroutine fortran_subroutine(json_path, log_path) bind(C)
    use, intrinsic :: ISO_C_BINDING
                
!        use json_module
         use str_routines
        
        implicit none
        character(kind=C_CHAR), target, intent(in) :: json_path(*)
        character(kind=C_CHAR), target, intent(in) :: log_path(*)
        
        character(kind=C_CHAR, len=:), pointer :: jpath, lpath
        
        call C_F_STRPOINTER (json_path, jpath, 80)
        call C_F_STRPOINTER (log_path, lpath, 80)
        
        print "('""',A,'""')", jpath, lpath
        
!        type(json_file) :: my_data, my_log
        
        ! initialize the class
!        call my_data%initialize()
!        call my_log%initialize()
        
        ! read the file: SHOUD BE filename = json_path
!        call my_data%load_file(filename = 'inout.json')

        ! read the file: SHOUD BE filename = log_path
!        call my_log%load_file(filename = 'inout.json')

end subroutine fortran_subroutine

 

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran

View solution in original post

0 Kudos
Highlighted
Beginner
15 Views

Thank you so much @Steve for

Jump to solution

Thank you so much @Steve for your patience and quick response! You are the best!

0 Kudos
Highlighted
15 Views

Steve,

Can you comment on the subtleties?

The subroutine fortran_subroutine is declared as taking two assumed-length character arguments. Which to my understanding actually has four arguments (two are the hidden lengths of the (*) two arguments.

It is clear that your use in this subroutine does NOT use the assumed length arguments other than to pass the references to another subroutine that uses only the address of the arguments and not the missing assumed length.

The temptation for some junior programmer to see this example, and assume the malformed assumed-length arguments are correct is too high. I would recommend that due to the requirement to scan for NULL character termination is required, that the scan be performed on the C/C++ side via a help function and that the call pass the length along with the address as a C_PTR.

As it stands, a helper function is required on one side or the other. My preference is to do this on the C/C++ side.

Jim Dempsey

0 Kudos
Highlighted
Black Belt
15 Views

Jim,

The arguments to fortran_subroutine are assumed-size arrays of CHARACTER*1, not assumed-length! There are no hidden arguments because of the BIND(C). The (Fortran) standard says that there is a 1-1 correspondence between the dummy arguments declared in Fortran and the "parameters" in C.

Traditionally, one (sometimes) passed string lengths from C to Fortran, but there was no standard about how Fortran would accept these. In the Windows/UNIX world Fortran compilers typically used hidden arguments, but there was inconsistency as to where in the argument list they were placed (at the end or after each address). Some other platforms (I am familiar with VMS) use different mechanisms, such as descriptors.

With the advent of Fortran 2003, if you chose to use the interoperability features you were required to do away with hidden arguments. Some chose to have declared dummy arguments to accept the lengths, others (such as in this case) have to assume the C scheme of a trailing null.

As of Fortran 2018 (and the TS29113 Technical Specification published in 2012, supported by Intel as of version 16), a third possibility opened up: a CHARACTER(*) (assumed-length) dummy argument is interoperable with a "C descriptor", but it's unlikely this would be the first choice of most C programmers. It does, however, lay a trap for the unwary. Prior to this, you could not have a CHARACTER(*) argument to a BIND(C) routine, now you can. Programmers not aware of the semantics of this might wonder why their programs fail when they used the familiar CHARACTER(*).

There are many ways to solve string length problems - a lot depends on which side of the call you want to put the effort into.

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Valued Contributor III
15 Views

Quote:jimdempseyatthecove

Jump to solution

jimdempseyatthecove (Blackbelt) wrote:

..

The temptation for some junior programmer to see this example, and assume the malformed assumed-length arguments are correct is too high. I would recommend that due to the requirement to scan for NULL character termination is required, that the scan be performed on the C/C++ side via a help function and that the call pass the length along with the address as a C_PTR.

As it stands, a helper function is required on one side or the other. My preference is to do this on the C/C++ side. ..

Jim,

You make excellent points here and I hope programmers reading this thread would pay close attention.

There is a reason why in Quote #3 upthread I wrote this:

FortranFan wrote:

.. While passing "string" data from/to Fortran with other languages such as C++,  passing the string length as shown can simplify the interoperation considerably.

which is to both ease the exchange of "string" data across the 2 programming language environments but also to facilitate good coding practices (GCP) by those programmers out there, especially the one you refer to, who read this thread.  Because as you allude to, there are several issues of concern which can easily be overlooked by unsuspecting readers or be thought of as the way to do things e.g., the "magic number" of 80 in lines 53 and 54 in the second code snippet in Quote #14.

As you also say correctly with " My preference is to do this on the C/C++ side" given "the requirement to scan for NULL character termination", it is not only trivial but can also lead to very concise and clear code on both sides as can be seen in an alternate example below:

#include <cstring>
#include <iostream>

extern "C" {

   // Function prototype
   void Fortran_subroutine( const char *, size_t, const char *, size_t );

}

int main(void)
{

   std::cout << "Option 1 using C-style char array" << std::endl;
   {
      char json_path[] = "../files/json/my_example.json";
      char log_path[] = "../files/log/my_log.json";

      Fortran_subroutine(json_path, strlen(json_path), log_path, strlen(log_path) );
   }

   std::cout << "Option 2 using std::string C++ class" << std::endl;
   {
      std::string JsonFilePath = "../files/json/my_example.json";
      std::string LogFilePath = "../files/log/my_log.json";

      Fortran_subroutine(JsonFilePath.c_str(), JsonFilePath.length(), LogFilePath.c_str(), LogFilePath.length() );
   }

   return 0;

}
module m

   use, intrinsic :: iso_c_binding, only : c_char, c_size_t, c_f_pointer, c_loc

   implicit none

contains

   subroutine Fsub(json_path, len_json, log_path, len_log) bind(C, name="Fortran_subroutine")

      ! Argument list
      character(kind=c_char,len=1), target, intent(in) :: json_path(*)
      integer(c_size_t), intent(in), value             :: len_json
      character(kind=c_char,len=1), target, intent(in) :: log_path(*)
      integer(c_size_t), intent(in), value             :: len_log

      ! Local variables
      character(kind=c_char,len=len_json), pointer :: path_json
      character(kind=c_char,len=len_log), pointer :: path_log

      call c_f_pointer( cptr=c_loc(json_path), fptr=path_json )
      print *, "path_json = ", path_json
      print *, "length: ", len(path_json), "; expected is ", len_json

      call c_f_pointer( cptr=c_loc(log_path), fptr=path_log )
      print *, "path_log = ", path_log
      print *, "length: ", len(path_log), "; expected is ", len_log

      path_json => null()
      path_log => null()

      return

   end subroutine

end module

Upon execution of the above, the expected output as seen using both Intel Fortran+Microsoft C++ and GCC/gfortran toolsets is:

Option 1 using C-style char array
 path_json = ../files/json/my_example.json
 length:           29 ; expected is                    29
 path_log = ../files/log/my_log.json
 length:           24 ; expected is                    24
Option 2 using std::string C++- class
 path_json = ../files/json/my_example.json
 length:           29 ; expected is                    29
 path_log = ../files/log/my_log.json
 length:           24 ; expected is                    24

 

0 Kudos
Highlighted
New Contributor II
15 Views

This example gives lots of C+

Jump to solution

This example gives lots of C++ compile warnings, but works, with old tools - MSVC 14 (i.e. VS 8) and IVF 11.0.075.  I'm impressed!

0 Kudos
Highlighted
Valued Contributor III
15 Views

Quote:gib wrote:

Jump to solution

gib wrote:

This example gives lots of C++ compile warnings, but works, with old tools - MSVC 14 (i.e. VS 8) and IVF 11.0.075.  I'm impressed!

Which example, which Quote #??

0 Kudos