Reputation: 1171
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
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