Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Struct support for Windows #237

Open
1 of 6 tasks
JupiterRider opened this issue Apr 21, 2024 · 2 comments
Open
1 of 6 tasks

Struct support for Windows #237

JupiterRider opened this issue Apr 21, 2024 · 2 comments
Labels
enhancement New feature or request

Comments

@JupiterRider
Copy link
Contributor

Operating System

  • Windows
  • macOS
  • Linux
  • FreeBSD
  • Android
  • iOS

What feature would you like to be added?

Support structs as return values and parameters of functions on Windows.

Why is this needed?

Technically purego already supports structs, but you have to make some weird stunts:

If the struct's size is less than or equal 8 bytes, you just can convert them into a uintptr. Same goes for return values. Example:

C:

RLAPI void ClearBackground(Color color); 

Golang:

var clearBackground func(col uintptr)

purego.RegisterLibFunc(&clearBackground, raylibDll, "ClearBackground")

func ClearBackground(col color.RGBA) {
	clearBackground(*(*uintptr)(unsafe.Pointer(&col)))
}

If the struct's size is lager then 8 bytes, you have to use a pointer as parameter. For return values you have to pass it as first argument. Here is another example from raylib-go:

C:

RLAPI Texture2D LoadTextureCubemap(Image image, int layout);   

Golang:

var loadTextureCubemap func(texture *Texture2D, image *Image, layout int32)

purego.RegisterLibFunc(&loadTextureCubemap, raylibDll, "LoadTextureCubemap")

func LoadTextureCubemap(image Image, layout int32) Texture2D {
	var texture Texture2D
	loadTextureCubemap(&texture, &image, layout)
	return texture
}
@TotallyGamerJet TotallyGamerJet added the enhancement New feature or request label Apr 21, 2024
@TotallyGamerJet
Copy link
Collaborator

My comment applies here too

@JupiterRider
Copy link
Contributor Author

Here is a first example of how it could look like:

func RegisterNew(fptr any, handle uintptr, name string) {
	if fptr == nil {
		panic("argument is nil")
	}
	typ := reflect.TypeOf(fptr)
	kind := typ.Kind()
	if kind != reflect.Ptr {
		panic("wrong kind " + kind.String())
	}
	typ = typ.Elem()
	kind = typ.Kind()
	if kind != reflect.Func {
		panic("wrong kind " + kind.String())
	}
	numOut := typ.NumOut()
	if numOut > 1 {
		panic("at most one return value is supported")
	}
	numIn := typ.NumIn()
	var outToIn bool
	var outToUintptr bool
	ptrPositions := make([]bool, numIn)
	uintptrPositions := make([]bool, numIn)
	outs := []reflect.Type{}
	ins := []reflect.Type{}

	uintptrTyp := reflect.TypeFor[uintptr]()

	for i := 0; i < numOut; i++ {
		out := typ.Out(i)
		if out.Kind() == reflect.Struct {
			if out.Size() > 8 {
				out = reflect.PointerTo(out)
				outToIn = true
				ins = append(ins, out)
				continue
			}
			outs = append(outs, uintptrTyp)
			outToUintptr = true
			continue
		}
		outs = append(outs, out)
	}

	for i := 0; i < numIn; i++ {
		in := typ.In(i)
		if in.Kind() == reflect.Struct {
			if in.Size() > 8 {
				in = reflect.PointerTo(in)
				ptrPositions[i] = true
			} else {
				in = uintptrTyp
				uintptrPositions[i] = true
			}
		}
		ins = append(ins, in)
	}

	fnTyp := reflect.FuncOf(ins, outs, false)
	fn := reflect.New(fnTyp)

	purego.RegisterLibFunc(fn.Interface(), handle, name)
	impl := reflect.MakeFunc(typ, func(args []reflect.Value) (results []reflect.Value) {
		newArgs := make([]reflect.Value, 0, len(args))
		var newResult reflect.Value
		if outToIn {
			newResult = reflect.New(ins[0].Elem())
			newArgs = append(newArgs, newResult)
		}
		for i := 0; i < len(args); i++ {
			if ptrPositions[i] {
				value := reflect.New(args[i].Type())
				value.Elem().Set(args[i])
				newArgs = append(newArgs, value)
				continue
			} else if uintptrPositions[i] {
				value := reflect.New(args[i].Type())
				value.Elem().Set(args[i])
				newArgs = append(newArgs, reflect.ValueOf(*(*uintptr)(value.UnsafePointer())))
				continue
			}
			newArgs = append(newArgs, args[i])
		}

		res := fn.Elem().Call(newArgs)
		if outToIn {
			return []reflect.Value{newResult.Elem()}
		} else if outToUintptr {
			value := reflect.New(res[0].Type())
			value.Elem().Set(res[0])
			newValue := reflect.NewAt(typ.Out(0), value.UnsafePointer())
			return []reflect.Value{newValue.Elem()}
		}
		return res
	})
	reflect.ValueOf(fptr).Elem().Set(impl)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants