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

Generate go wrappers using cgo instead of syscall #94

Closed
wants to merge 9 commits into from
Closed

Generate go wrappers using cgo instead of syscall #94

wants to merge 9 commits into from

Conversation

qmuntal
Copy link

@qmuntal qmuntal commented May 7, 2020

Summary of new and updated functionality:

  • Support Win, Linux and macOS
  • Support Linking to static and shared libraries
  • Support all inputs and outputs types. Limitation: Only one callback of each type can be used at the same time
  • Automatic memory management via runtime.SetFinalizer (manual management is still allowed)
  • Auto generated code is 100% idiomatic and type safe for almost all types
  • Better error management

Closes #93

Key features

Examples generated using from lib3mf.

Importing library

It is required to have the .dll, .so, .dylib, .a, ... located next to the executable or in a system wide folder. lib3mf_abi.h and lib3mf_types.h are necessary only when building the executable and they should be located next to the go binding file.

/*
#cgo LDFLAGS: -L./ -l3mf
#include "lib3mf_abi.h"
*/
import "C"

Automatic memory managment

The wrapped C pointer is stored in a Go struct and it uses some dark magic tricks together with runtime.SetFinalizer to release the C memory when Go garbage collector collect the Go struct. It is save to manually call Release to release the memory before the garbage collector passes.

type ref = C.Lib3MFHandle

// Base represents a Lib3MF class.
type Base struct {
  _     [0]func() // uncomparable; to make == not compile
  ref   ref       // identifies a C value, see ref type
  gcPtr *ref      // used to trigger the finalizer when the Value is not referenced any more
}

// NewBase creates a new Base.
// The wrapped C pointer will be freed when the Go pointer is finalized,
// but one can release it manually calling Release.
func NewBase(r ref) Base {
  gcPtr := new(ref)
  *gcPtr = r
  runtime.SetFinalizer(gcPtr, releaseC)
  return Base{ref: r, gcPtr: gcPtr}
}

// Release releases the C pointer.
func (inst Base) Release() error {
  err := Release(inst)
  *inst.gcPtr = nil
  return err
}

Error wrapping

The return errors are of type WrappedError, this way users can inspect which is the error code without losing the error message.

// WrappedError is an error that wraps a Lib3MF error.
type WrappedError struct {
  Code uint32
  Message string
}

func (e *WrappedError) Error() string {
  return fmt.Sprintf("lib3mf: %s (%d)", e.Message, e.Code)
}

C Callbacks

cgo cannot share Go pointers with C, and function are pointers, so it is somehow difficult to pass a function to C as parameter. It has to be done via a bridge C function and and exported Go function, and this is what has been implemented. There is one important limitation, as the Go bridge function is static and only one of each type can exist at the same time. It is somehow difficult to explain, so better document yourself at this blog.

When callbacks are present cfunc.go file is also created, as //export and C function declarations cannot coexist at the same time.

/*
#cgo LDFLAGS: -L./ -l3mf
#include "lib3mf_abi.h"

extern void progressCallback(bool *, Lib3MF_double, eLib3MFProgressIdentifier, Lib3MF_pvoid);
void Lib3MFProgressCallback_cgo(bool * pAbort, Lib3MF_double dProgressValue, eLib3MFProgressIdentifier eProgressIdentifier, Lib3MF_pvoid pUserData){
  progressCallback(pAbort, dProgressValue, eProgressIdentifier, pUserData);
}
*/
import "C"

...

// ProgressCallbackFunc a callback function.
type ProgressCallbackFunc = func(abort *bool, progressValue float64, progressIdentifier ProgressIdentifier, userData interface{})

var progressCallbackFunc ProgressCallbackFunc

//export progressCallback
func progressCallback(abort *C.bool, progressValue C.double, progressIdentifier C.eLib3MFProgressIdentifier, userData C.Lib3MF_pvoid) {
  if progressCallbackFunc == nil {
    return
  }
  progressCallbackFunc((*bool)(abort), (float64)(progressValue), (ProgressIdentifier)(progressIdentifier), *(*interface{})(unsafe.Pointer(userData)))
}

// SetProgressCallback set the progress callback for calls to this writer.
func (inst Writer) SetProgressCallback(progressCallback ProgressCallbackFunc, userData interface{}) (error) {
  ret := C.lib3mf_writer_setprogresscallback(inst.ref, (C.Lib3MFProgressCallback)(unsafe.Pointer(C.Lib3MFProgressCallback_cgo)), (C.Lib3MF_pvoid)(unsafe.Pointer(&userData)))
  if ret != 0 {
    return makeError(uint32(ret))
  }
  progressCallbackFunc = progressCallback
  return nil
}

@qmuntal qmuntal mentioned this pull request May 7, 2020
@qmuntal
Copy link
Author

qmuntal commented May 8, 2020

You can see the full lib3mf auto generated code in https://github.com/qmuntal/go-lib3mf/blob/master/v2/lib3mf.go.

alexanderoster
alexanderoster previously approved these changes Jul 21, 2020
@qmuntal
Copy link
Author

qmuntal commented Jul 28, 2020

Closing as #121 is already in develop. I'll open a new PR with just the static/dynamic linking part.

@qmuntal qmuntal closed this Jul 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants