Lakshaya U.
Lakshaya U.

Reputation: 1171

Is there a data type for c FILE in Golang?

I recently ran into an issue trying to compile https://github.com/robpike/strings as a DLL (shared lib), where cgo doesn't recognise os.File as an exportable data type. I want to import this dll into a python script with ctypes.

I am relatively new to golang, and after a lot of googling I couldn't find an answer to my question.

strings.go

package main

import (
    "bufio"
    "flag"
    "fmt"
    "io"
    "log"
    "os"
    "strconv"
    "C"
)

var (
    min    = flag.Int("min", 6, "minimum length of UTF-8 strings printed, in runes")
    max    = flag.Int("max", 256, "maximum length of UTF-8 strings printed, in runes")
    ascii  = flag.Bool("ascii", false, "restrict strings to ASCII")
    offset = flag.Bool("offset", false, "show file name and offset of start of each string")
)

var stdout *bufio.Writer

func main() {
}

//export do
func do(file *os.File) {
    in := bufio.NewReader(file)
    str := make([]rune, 0, *max)
    filePos := int64(0)
    print := func() {
        if len(str) >= *min {
            s := string(str)
            // if *offset {
                // fmt.Printf("%s:#%d:\t%s\n", name, filePos-int64(len(s)), s)
            // } else {
            fmt.Println(s)
            // }
        }
        str = str[0:0]
    }
    for {
        var (
            r   rune
            wid int
            err error
        )
        // One string per loop.
        for ; ; filePos += int64(wid) {
            r, wid, err = in.ReadRune()
            if err != nil {
                if err != io.EOF {
                    log.Print(err)
                }
                return
            }
            if !strconv.IsPrint(r) || *ascii && r >= 0xFF {
                print()
                continue
            }
            // It's printable. Keep it.
            if len(str) >= *max {
                print()
            }
            str = append(str, r)
        }
    }
}

Expectation: go build -o strings.dll -buildmode=c-shared outputs strings.dll with exported function do

Problem: .\strings.go:41:15: Go type not supported in export: os.File

EDIT:I am running into this issue: read file descriptor: The handle is invalid.

Python driver (3.10.5 on windows amd64):

from ctypes import *
mydll = cdll.LoadLibrary("./strings.dll")

strings = mydll.dodo
strings.argtypes = [c_int]
strings.restype = c_void_p

with open("./go.mod", "rb") as h:
    print(strings(h.fileno()))

Upvotes: 2

Views: 198

Answers (1)

Pak Uula
Pak Uula

Reputation: 3425

Cgo doesn't support mapping structures to C. Actually, all you can use in the exported functions is strings, integers and native C types.

Instead of os.File the calling function can pass file decriptor. It is an integer.

I added func dodo(fd int) to your code and export this function instead of do.

package main

import (
    "C"
    "bufio"
    "flag"
    "fmt"
    "io"
    "log"
    "os"
    "strconv"
)

var (
    min    = flag.Int("min", 6, "minimum length of UTF-8 strings printed, in runes")
    max    = flag.Int("max", 256, "maximum length of UTF-8 strings printed, in runes")
    ascii  = flag.Bool("ascii", false, "restrict strings to ASCII")
    offset = flag.Bool("offset", false, "show file name and offset of start of each string")
)

var stdout *bufio.Writer

func main() {
}

//export dodo
func dodo(fd int) {
    file := os.NewFile(uintptr(fd), "file descriptor")
    if file == nil {
        return
    }
    do(file)
}
func do(file *os.File) {
    in := bufio.NewReader(file)
    str := make([]rune, 0, *max)
    filePos := int64(0)
    print := func() {
        if len(str) >= *min {
            s := string(str)
            // if *offset {
            // fmt.Printf("%s:#%d:\t%s\n", name, filePos-int64(len(s)), s)
            // } else {
            fmt.Println(s)
            // }
        }
        str = str[0:0]
    }
    for {
        var (
            r   rune
            wid int
            err error
        )
        // One string per loop.
        for ; ; filePos += int64(wid) {
            r, wid, err = in.ReadRune()
            if err != nil {
                if err != io.EOF {
                    log.Print(err)
                }
                return
            }
            if !strconv.IsPrint(r) || *ascii && r >= 0xFF {
                print()
                continue
            }
            // It's printable. Keep it.
            if len(str) >= *max {
                print()
            }
            str = append(str, r)
        }
    }
}

Build the code (Linux):

go build -o libstrings.so -buildmode=c-shared .

This command produced libstrings.h and libstrings.so

Example some.c program that uses libstrings.so

// perror
#include <stdio.h>
// open and O_RDONLY
#include<fcntl.h>
// close
#include <unistd.h>
// dodo
#include "libstrings.h"

int main(int argc, char **argv) {
    int fd = open("go.mod", O_RDONLY);
    if (fd < 0) {
        perror("Failed to open file");
        return 1;
    }
    dodo(fd);
    close(fd);
    return 0;
}

Build:

gcc -o some.exe some.c -L ./ -lstrings

Execute:

LD_LIBRARY_PATH=. ./some.exe 

Result: it prints the contents of go.mod file as expected.

module example.org
go 1.19

Upvotes: 4

Related Questions