2

I've been trying to pass a Fortran string to a shared C library generated from GO using buildmode c-shared. I've unsuccessfully tried to define a derived type to match what the generated function signature expects. I'm trying to avoid passing the strings to my own C glue code (that takes a Fortran string and converts it to a go string and calls the go library) if possible.

My GO signature

//export MyFunc
func MyFunc(str1, str2 string) bool {

The generated GO header file defines

mylib.h

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif

typedef unsigned char GoUint8;

And my function signature

extern GoUint8 MyFunc(GoString str1, GoString str2);

My attempt

module go_types
    use iso_c_binding
    type, bind(c) :: go_string
        type(c_ptr) :: msg
        integer(c_ptrdiff_t) :: n
    end type go_string
end module go_types

program test_go
    ! use iso_c_binding, only: C_NULL_CHAR, c_char !! Use to terminate strings
    use, intrinsic :: iso_c_binding
    use go_types
    implicit none

    interface        
        integer(c_ptrdiff_t) function MyFunc(str1, str2) bind(C, name="MyFunc")
            use go_types            
            type(go_string) :: str1
            type(go_string) :: str2
        end function Validate
        
    end interface

    type(go_string) :: str1, str2
    character(len=25), target :: fortstr1, fortstr2

    integer :: ret

    fortstr1 = "Bar"//C_NULL_CHAR
    str1%msg = c_loc(fortstr1)
    str1%n = len(trim(fortstr1))


    fortstr2 = "Foo"//C_NULL_CHAR
    str2%msg = c_loc(fortstr2)
    str2%n = len(trim(fortstr2))

    ret = MyFunc(str1, str2)
    print *, 'return code:', ret
contains

end program

The code compiles fine but when I try to access the string in the GO library I get

runtime: out of memory: cannot allocate 140732811706368-byte block (3833856 in use)
fatal error: out of memory

goroutine 17 [running, locked to thread]:
runtime.throw({0x10698a55a, 0x400})
        /usr/local/Cellar/go/1.17/libexec/src/runtime/panic.go:1198 +0x71 fp=0xc000056990 sp=0xc000056960 pc=0x1068ceaf1
runtime.(*mcache).allocLarge(0x106f1b040, 0x7ffee9366000, 0x1, 0x1)
        /usr/local/Cellar/go/1.17/libexec/src/runtime/mcache.go:229 +0x22e fp=0xc0000569f0 sp=0xc000056990 pc=0x1068b28ee
runtime.mallocgc(0x7ffee9366000, 0x0, 0x0)
        /usr/local/Cellar/go/1.17/libexec/src/runtime/malloc.go:1082 +0x5c5 fp=0xc000056a70 sp=0xc0000569f0 pc=0x1068a9ba5
runtime.growslice(0x203000, {0xc0000b2240, 0x12da19c40, 0x12da19b00}, 0x0)
        /usr/local/Cellar/go/1.17/libexec/src/runtime/slice.go:261 +0x4ac fp=0xc000056ad8 sp=0xc000056a70 pc=0x1068e56cc
...

I've also tried a few permutations with msg being character(kind=c_char) instead of a c_ptr but it doesn't seem to work either.

francescalus
  • 30,576
  • 16
  • 61
  • 96
Guillaume
  • 1,022
  • 1
  • 9
  • 17
  • 1
    `25` is not an interoperable length type parameter for characters, so can you give some detail of how you are using the result of `c_loc` for those non-interoperable variables? – francescalus Sep 02 '21 at 19:04
  • 1
    It looks like varying-length strings were new in F95 and are still not standardized in terms of internal data structure layout. You'll need to know that layout to tie into either C or Go strings. – torek Sep 02 '21 at 19:09
  • I don't know much about interop. When i tried to define the derived type initially, i tried setting character(kind= c_char) :: msg(*) and the compiler complained so I added 25 to get it to stop complaining since the strings will never be larger. I just copy pasted it when i switched to the type(c_ptr). How should I be declaring the strings? – Guillaume Sep 02 '21 at 19:09
  • 1
    @torek, these are not F95 varying-length strings, but (other than being targets) just like the characters one would have found 44 years ago. – francescalus Sep 02 '21 at 19:12
  • 1
    Oh: if these are fixed-length, space-padded strings, like the ones I used in F66/F77 so long ago, just treat them as fixed-size arrays in Go. – torek Sep 02 '21 at 19:13
  • 1
    You can declare the characters to be `character(len=1, kind=c_char) :: msg(25)` and use them as _arrays_, not as scalars. (Meaning, among other things, `LEN_TRIM` isn't going to be much use.) See also [this question](https://stackoverflow.com/q/68461722/3157076). – francescalus Sep 02 '21 at 19:13
  • I get the same (runtime) error with fortstr2 = (/ 'F', 'O', 'O', C_NULL_CHAR/) and character(len=1, kind=c_char), target :: str1(4), str2(4). The error only occurs if I try to access the string, If i don't access it everything is fine... – Guillaume Sep 02 '21 at 19:25
  • https://gcc.gnu.org/onlinedocs/gfortran/Interoperable-Subroutines-and-Functions.html Theres this example showing how to handle strings but derived tyeps don't let me set msg(*) – Guillaume Sep 02 '21 at 19:29
  • 1
    Have you tried with C code directly? Possibly most of the Fortran people here would be much more familiar and able to help with using C than Go. (I, for example, have no clue whether you should be passing `str1` by value or not, looking at what is here, or what dereferencing may be required.) – francescalus Sep 02 '21 at 19:39
  • Yeah I know it's very niche... I was trying to avoid using C glue code. For my use case, theres a finite amount of strings and passing the other primitive types works without issue. So i'm going to go with plan B of passing an int and storing the dict of int -> string in the binary. I'll write the C code if I ever need strings in general. Thanks a lot for the help. – Guillaume Sep 02 '21 at 19:42

0 Answers0