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

Assigning a string that will be used in as argument to c function

Timothy_W_
Beginner
335 Views

Hi

I have a list of fixed size strings (character*6) that I want to give to a c function.  To be fully interoperable between Fortran and C I must use an array of single characters.  But how do I assign a character*6 that I have to this array?  The code below works except for this character*6 to character(len=1) var(6) issue.

Example code

    type, bind(C) :: RelayElement

        integer (kind=C_INT) :: lzop_tag
        integer (kind=C_INT) :: relay_tag
        character (kind=C_CHAR) :: codes(6) 
        character (kind=C_CHAR) :: desigs(21)
        character (kind=C_CHAR) :: zuns(2)
    end type RelayElement
    character(len=6) :: new_codes
    type (RelayElement) :: element
    
    interface 
      subroutine c_interop(element) bind(C)
        use iso_c_binding
        import::RelayElement
        implicit none
        type(RelayElement) :: element
      end subroutine c_interop
    end interface  
    new_codes = 'codea'
    !below, it is a pain to assign codes as an array, hard to maintain and read

    element = RelayElement(lzop_tag=1,relay_tag=2, codes=['c','o','d','e','s',C_NULL_CHAR],desigs='desigs',zuns='a')

    element%codes = new_codes  ! this is what I need.  I have the codes in fortran  and need to give it to c. Does not work

    ! element%codes is now just the first character of new_codes repeated

    call c_interop(element) ! this works except for the issue above.

Thanks

Tim

0 Kudos
4 Replies
Timothy_W_
Beginner
335 Views

Hi

I can get it to work by assigning the Fortran string (Character*6) to the Fortran character array in a loop:

    DO i = 1,SIZE(element%codes)
      element%codes(i:i) = new_codes(i:i)
    end do

It works but it seems very busy.  I can't find another way.

In other parts of my Fortran 77 code, it uses conversion which lets me set the character*6 codes [reference] in the interface block.  This works and is straight forward.  The modern way seems noisier.

interface to subroutine c_interop

   &(carray)

   character*6 carray [reference]

end

Tim

0 Kudos
JVanB
Valued Contributor II
335 Views

If you need NUL-termination, you have to put it there explicitly:

new_codes = 'codea'//C_NULL_CHAR

Given that, the natural way to copy raw data from one variable to another is via TRANSFER:

element%codes = TRANSFER(new_codes,element%codes,SIZE(element%codes))

In this expression, if LEN(new_codes) > SIZE(element%codes), only the first SIZE(element%codes) characters will be copied. If LEN(new_codes) < SIZE(element%codes), all of new_codes will be copied with garbage following to pad out to SIZE(element%codes). Assuming the C code uses NUL-termination, that extra garbage will not be a problem. Remember that LEN(new_codes) is the declared length of the variable, not the length of what you usefully assigned to it (normally that would be LEN_TRIM(new_codes)).

 

0 Kudos
FortranFan
Honored Contributor II
335 Views

Be careful about NUL termination when the size of codes is less than the length of the source if it is always expected on the C side  Or you can do something like the following:

      new_codes = ..     !  No null ternination here
      asc: associate( lenm => min(size(element%codes), len_trim(newcode)+1) )
         element%codes(1:lenm-1) = transfer( new_codes, mold=element%codes, size=lenm-1 )
         element%codes(lenm:lenm) = c_null_char
      end associate asc

 

0 Kudos
Timothy_W_
Beginner
335 Views

Thanks!  That helps.

Tim

0 Kudos
Reply