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

Passing C++ strings to Fortran - "proper" method?

theithecker
Beginner
2,132 Views
Greetings,

new poster here.

Just inherited a project where I have a c++ wrapper (VS2005, IVF 10.1)) accessing a Fortran DLL made up of a whole slew of subroutines. My c++ needs to pass/receive strings (or char*) and also integers, to these subroutines. The subs will call other subs using the strings/integers and finally return a string or two to the c++ wrapper.

My question: What is the recommended (i.e., robust, compatible, clean) method for passing/receiving strings to/from fortran dll's? I searched this forum and couldn't find a good answer for me. I have been trying to use C Structs/F90 Types (unsuccessfully), passing char* and an int (length), etc. I like this method but I think I may be having alignment/size issues. Here's an annotated version:

C++ definition:

struct f90Char{
int maxlen;
char* str;
f90Char::f90Char();
f90Char(int mlen, char* sstr ){
//str = new char[mlen];
str = sstr;
maxlen = mlen;
}

};

extern "C" __declspec(dllimport) void __stdcall GETVOLEQ(int *REGN,struct f90Char* FORST);

Fortran:

MODULE CHARMOD
TYPE CHAR_3
SEQUENCE
INTEGER MAXLEN
CHARACTER(3) STR
END TYPE CHAR_3
END MODULE CHARMOD

subroutine GETVOLEQ(REGN,FORST)
!DEC$ ATTRIBUTES DLLEXPORT::GETVOLEQ
!DEC$ ATTRIBUTES ALIAS:'_GETVOLEQ@28' :: GETVOLEQ
!DEC$ATTRIBUTES REFERENCE:: FORST

USE CHARMOD
INTEGER REGN
TYPE(CHAR_3):: FORST



0 Kudos
9 Replies
Steven_L_Intel1
Employee
2,132 Views
I have not seen anyone trying to do it quite this way before, with structs. It seems to me that this makes it hard on both the C++ and Fortran side of things.

I would recommend using the C interoperability features of Fortran 2003, which are implemented in Intel Fortran. From the C++ side, you'd just pass a char* as to any other C++ routine, and pass an extra argument that has the length. On the Fortran side, you would not depend on the "hidden argument" for length but rather use the passed-in length. Something like this:

[plain]subroutine GETVOLEQ (REGN, FORSTR, FORSTR_LEN) BIND(C,name="GETVOLEQ")
use, intrinsic :: ISO_C_BINDING
implicit none
integer(C_INT), intent(OUT) :: REGN
integer(C_INT), intent(IN), VALUE :: FORSTR_LEN
character(FORSTR_LEN,kind=C_CHAR), intent(IN) :: FORSTR
[/plain]
On the C++ side, you'd pass the length by value.
0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,132 Views
From the C++ side, you'd just pass a char* as to any other C++ routine, and pass an extra argument that has the length. On the Fortran side, you would not depend on the "hidden argument" for length but rather use the passed-in length.

Steve, I'm not yet fully into ISO_C_BINDINGS (at least not into the implementation part): in the example you give, is the hidden length completely absent, or is it just ignored in the code? (IOW, stack handling of C calling convention: Would your example link with stdcall calling convention in effect?) If the former, which elements of syntax provide that semantics?
0 Kudos
anthonyrichards
New Contributor III
2,132 Views
Quoting - Jugoslav Dujic

Steve, I'm not yet fully into ISO_C_BINDINGS (at least not into the implementation part): in the example you give, is the hidden length completely absent, or is it just ignored in the code? (IOW, stack handling of C calling convention: Would your example link with stdcall calling convention in effect?) If the former, which elements of syntax provide that semantics?

I am also confused about when and where 'hidden lengths' appear and have to be catered for. Here is an example I havecreated where the 'hidden length' is specifically mentioned in the C++ code and where it is not specifically mentioned in the Fortran code that calls the C++ function. The C++ code is compiled into a DLL (using single threaded debug libraries) andits export library .LIB file included in the Fortran main program that calls the C++ routine read_ras_format. The Fortran program is also linked using single threaded debug libraries so that the C++ cout can write to the Fortran console.

Fortran code:
!****************************************************************************
!
! PROGRAM: fcallsCPP
!
! PURPOSE: Example of Fortran calling C++
! Since the C++ function in a DLL calls cout, and since the DLL
! is linked using single threaded DLL libraries, this Fortran
! project should be linked using the same libraries if you want
! the cout output to appear in the console window.
!
!****************************************************************************

PROGRAM CALL_DLL
IMPLICIT NONE
! VARIABLES
CHARACTER*33 SFILENAME
INTEGER RETURNVALUE

INTERFACE
INTEGER FUNCTION READ_RAS_FORMAT(SFILENAME, LENGTH)
!DEC$ ATTRIBUTES DLLIMPORT,ALIAS:'_read_ras_format' :: READ_RAS_FORMAT
!DEC$ ATTRIBUTES REFERENCE :: SFILENAME
!DEC$ ATTRIBUTES VALUE :: LENGTH
INTEGER LENGTH
CHARACTER*LENGTH SFILENAME
END FUNCTION READ_RAS_FORMAT
END INTERFACE

PRINT *, 'HELLO WORLD'

! Body of simple_dll_test
SFILENAME = 'E:tempSHA010046RF.ras'
! Make sure the null-terminated string fits into the character buffer assigned to it
IF(LEN_TRIM(SFILENAME).LT.33 ) THEN
SFILENAME=SFILENAME(1:LEN_TRIM(SFILENAME))//CHAR(0)
ELSE
SFILENAME=SFILENAME(1:32)//CHAR(0)
ENDIF
!NOTE: read_ras_format IS CALLED WITHOUT INCLUDING THE 'HIDDEN' STRING BUFFER
! CLENGTH AS THE SECOND ARGUMENT
RETURNVALUE = read_ras_format(SFILENAME, LEN_TRIM(SFILENAME))
PRINT *, 'SFILENAME BUFFER LENGTH = ', LEN(SFILENAME)
PRINT *, 'LEN_TRIM(SFILENAME)=', LEN_TRIM(SFILENAME)
PRINT *, 'RETURN VALUE =', RETURNVALUE
END PROGRAM

C++ DLL code:
// FortrancallsCPP.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "iostream.h"

extern "C" { __declspec(dllexport) int read_ras_format(char* in_fileName, int hidden_stringlength, int fileName_length); };

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
};

// This is an example of an exported function.
int read_ras_format(char* in_fileName, int hidden_stringlength, int fileName_length)
{
cout<<"in_fileName: "<< in_fileName<<"n fileName_length: "<<<"n";
cout<<"in_fileName: "<< in_fileName<<"n hidden stringlength: "<<<"n";
return 1;
};

When the fortran Main program is executed, it produces the following console output, which shows that the 'hidden length' argument is found by the C++ function, along with the file name length argument.

console output:

HELLO WORLD
SFILENAME BUFFER LENGTH = 33
LEN_TRIM(SFILENAME)= 24
RETURN VALUE = 1
in_fileName: E:tempSHA010046RF.ras
fileName_length: 24
in_fileName: E:tempSHA010046RF.ras
hidden stringlength: 33
Press any key to continue

Can you please indicate how to suppress this 'hidden length' argument so that such argument manipulation as is shown aboveis not necessary?
0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,132 Views
Quoting - anthonyrichards

I am also confused about when and where 'hidden lengths' appear and have to be catered for.

INTERFACE
INTEGER FUNCTION READ_RAS_FORMAT(SFILENAME, LENGTH)
!DEC$ ATTRIBUTES DLLIMPORT,ALIAS:'_read_ras_format' :: READ_RAS_FORMAT
!DEC$ ATTRIBUTES REFERENCE :: SFILENAME
!DEC$ ATTRIBUTES VALUE :: LENGTH
INTEGER LENGTH
CHARACTER*LENGTH SFILENAME
END FUNCTION READ_RAS_FORMAT
END INTERFACE

// This is an example of an exported function.
int read_ras_format(char* in_fileName, int hidden_stringlength, int fileName_length)
{
cout<<"in_fileName: "<< in_fileName<<"n fileName_length: "<<<"n";
cout<<"in_fileName: "<< in_fileName<<"n hidden stringlength: "<<<"n";
return 1;
};

When the fortran Main program is executed, it produces the following console output, which shows that the 'hidden length' argument is found by the C++ function, along with the file name length argument.

console output:


Can you please indicate how to suppress this 'hidden length' argument so that such argument manipulation as is shown aboveis not necessary?

Tony, here's a part of the answer. Let's distinguish 3 cases:
  1. when you use plain vanilla Fortran without any extensions
  2. when you use !DEC$ATTRIBUTES Visual Fortran extension
  3. when you use ISO_C_BINDINGS module, designed by F2003 for cross-platform multi-language binding (and VALUE attribute from F2003).
Also, have in mind that Fortran always has to have the string length on disposal, either passed, or explicitly declared.

Tony, are you using CVF, or /iface:cvf, or /mixed_str_len_arg in IVF? Your example does not match the Intel Fortran default (/nomixed_str_len_arg), where the hidden lenghts are passed in the end of the argument list.

  1. In case 1, you don't have any Fortran tool to adjust, so generally you must make adjustments on C side. However, there's a hack concerning strings: when C (_cdecl) calling convention is in effect, and string lengths are passed on the end of argument-lists (as in current Intel Fortran and most other modern implementation), mismatches in the number of arguments are harmless, as long as you don't access the unused ones (because the caller cleans the stack). Fortran will push the hidden length it onto stack, C won't use it, Fortran will pop it from the stack, no damage done. That won't work with STDCALL, nor with mixed_str_len_arg (as in CVF). That's one of reasons they switched to C/nomixed_str_len_arg in Intel.
  2. When you use !DEC$ATTRIBUTES, you can really suppress the hidden length by specifying REFERENCE for the character argument. (However, you also have to specify !DEC$ATTRIBUTES C or STDCALL for the function itself). When I add "C" to your example above, you will get garbage in place of one of C arguments (depending on /[no]mixed_str_len_arg):
    [cpp]INTERFACE
    INTEGER FUNCTION READ_RAS_FORMAT(SFILENAME, LENGTH)
    !DEC$ ATTRIBUTES C, DLLIMPORT, ALIAS: '_read_ras_format' :: READ_RAS_FORMAT
    !DEC$ ATTRIBUTES REFERENCE :: SFILENAME
    !DEC$ ATTRIBUTES VALUE :: LENGTH
    INTEGER LENGTH
    CHARACTER*LENGTH SFILENAME
    END FUNCTION READ_RAS_FORMAT[/cpp]
  3. If you use ISO_C_BINDINGS, which is the "proper" method, I don't know exactly. That's why I asked Steve above.


0 Kudos
anthonyrichards
New Contributor III
2,132 Views
Thanks, Jugoslav. To answer your query, I use CVF and the project options specify default argument
passing conventions for external procedures and string length passing after string argument.

Your comment about suppressing thebuffer length argument by using REFERENCE attribute still leaves me confused, as I have specified REFERENCE on the Fortran side and specified a pointer (char*) onthe C++ side but still the buffer length appears.
0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,132 Views
Quoting - anthonyrichards
Your comment about suppressing thebuffer length argument by using REFERENCE attribute still leaves me confused, as I have specified REFERENCE on the Fortran side and specified a pointer (char*) onthe C++ side but still the buffer length appears.

No wonder you're confused -- the semantic rules for !DEC$ATTRIBUTES are arcane, and REFERENCE is the worst one. To be fair, they were inherited from Microsoft Fortran PowerStation, but they're still an abomination.

Short answer: you must use STDCALL or C attribute for the routine (as appropriate) in order for REFERENCE to have an effect.

Long answer: here's the full REFERENCE semantics. Let's take the prototype:

subroutine Foo(i, a, s)
integer:: i
integer:: a(*)
character(*):: s

When REFERENCE::Foo, then all scalar arguments are by reference. Strings still have passed length.

When STDCALL or C::Foo, then all scalar arguments are by value. Strings are also by value, and hidden length is passed. Arrays are still by reference.

When STDCALL or C::Foo and REFERENCE:: Foo, then all scalar arguments are by reference. Strings still have passed lentgh.

When STDCALL or C::Foo and REFERENCE:: S, then S is by reference, and hidden length is not passed

When nothing is said for Foo, and REFERENCE:: S, then S is by reference, but hidden length is passed.
----
Thank God they invented ISO_C_BINDINGS, as you see. I'm not quite used to it yet though.
0 Kudos
Steven_L_Intel1
Employee
2,131 Views
Quoting - Jugoslav Dujic

Steve, I'm not yet fully into ISO_C_BINDINGS (at least not into the implementation part): in the example you give, is the hidden length completely absent, or is it just ignored in the code? (IOW, stack handling of C calling convention: Would your example link with stdcall calling convention in effect?) If the former, which elements of syntax provide that semantics?

First, I know it's convenient, but I'm uncomfortable using "ISO_C_BINDINGS" as a shorthand for all of the interoperability features of the language. I'll agree it's a big part of it, but not all. In particular, the BIND attribute is not part of that module. Ok, with that out of the way...

Intel Fortran does not allow you to use BIND(C) and STDCALL. We thought/discussed/argued a lot about this but came down on the side of BIND(C) is for when you use the "companion C processor's" default conventions. If you want STDCALL or something else, use the !DEC$ ATTRIBUTES syntax instead.

In my example, I assumed default calling conventions. If you're writing both the C++ and Fortran code, you can choose to not use STDCALL. If you use STDCALL, then this gets harder.

In this case, and with the character length explicitly specified, the Fortran code will ignore (not look for) any passed string length. (Hmm - I need to test that some more...) The problem with trying to deal with a passed length is that there's no standard about it, so I chose to suggest a coding style that does not depend on it. I could argue (and I think I have in the past) that BIND(C) should mean no passed length.

I also fudged a bit in my suggestion - according to the standard, the Fortran type that is "interoperable" with a C char* is an array of CHARACTER(1). The language has some rules that allow passing a CHARACTER(n) string to a CHARACTER(1) array, but it doesn't really have the reverse. It will work, though, as long as you're not depending on the length to be a hidden argument.
0 Kudos
theithecker
Beginner
2,131 Views
So,

I got this to work with the struct and I think it is pretty straight forward. I kind of used a modified version of some code I found on this forum. I essentially create a struct with a char array of a size larger than I will need and the "actual" length of my c string using this def:

include
struct f90Char{
int length;
char str[256];
f90Char::f90Char();
f90Char(int alen, std::string sstr){
for(int i=0; i<256; i++)str=' ';//pad the char array
strcpy (str, sstr.c_str());
length = alen;
}
};


Then pass a reference to my struct to fortran. On the fortran side simply use and defined type:

TYPE CHAR256
SEQUENCE
INTEGER LENGTH
CHARACTER(256) STR
END TYPE CHAR256


Then I can use the type(define a variable as follows:

CHARACTER(FORSTC%LENGTH) FORST

and anywhere I need the "actual" string I use FORSTC%STR(1:(FORSTC(%LENGTH)), or just assign that portion of the "string" to a CHARACTER(*)

So, I am now having the same problem(s) passing mulitdimensional arrays from C++ to Fortran. Should I post my question as a seperate thread?

thanks,

-troy


0 Kudos
Steven_L_Intel1
Employee
2,131 Views
Yes, you should.
0 Kudos
Reply