diff --git a/api.go b/api.go index 0613552..8be9c1b 100644 --- a/api.go +++ b/api.go @@ -23,6 +23,7 @@ import "C" // ask an EventLoop to quit. type api struct { + shareable loop EventLoop callback NotificationCallback eventCallback EventCallback @@ -69,7 +70,8 @@ func BuildAPI(configPath string, userPath string, overrides string) Configurator //defer C.free(unsafe.Pointer(cUserPath)) //defer C.free(unsafe.Pointer(cOverrides) C.startOptions(cConfigPath, cUserPath, cOverrides) - return &api{ + var r *api + r = &api{ loop: defaultEventLoop, callback: nil, eventCallback: defaultEventCallback, @@ -80,6 +82,8 @@ func BuildAPI(configPath string, userPath string, overrides string) Configurator logger: &defaultLogger{}, networks: make(map[uint32]*network), quitDeviceMonitor: make(chan int, 2)} + r.shareable.init(r) + return r } func (a *api) QuitSignal() chan int { diff --git a/api.h b/api.h index 74fd706..5a32b09 100644 --- a/api.h +++ b/api.h @@ -1,4 +1,4 @@ -#ifndef API_H +#ifndef API_H #define API_H // // api.h @@ -39,14 +39,15 @@ extern "C" { #else #endif +#include "api/shareable.h" typedef void API; - #include "api/manager.h" #include "api/node.h" #include "api/value.h" #include "api/notification.h" #include "api/options.h" + #ifdef __cplusplus #include "_cgo_export.h" } diff --git a/api/shareable.h b/api/shareable.h new file mode 100644 index 0000000..26b1f46 --- /dev/null +++ b/api/shareable.h @@ -0,0 +1,5 @@ +typedef struct shareable { + int sharedIndex; +} shareable; + +shareable * newShareable(int i); diff --git a/api_test.go b/api_test.go new file mode 100644 index 0000000..ee1fb5d --- /dev/null +++ b/api_test.go @@ -0,0 +1,13 @@ +package openzwave + +import "testing" + +func Test_RoundtripMarshaling(t *testing.T) { + a := &api{} + a.init(a) + c := a.C() + aa := unmarshal(c).Go().(*api) + if a != aa { + t.Fatalf("failed to round trip") + } +} diff --git a/run.go b/run.go index 631177e..2e31d0c 100644 --- a/run.go +++ b/run.go @@ -108,7 +108,7 @@ func (a *api) Run() int { // go func() { - cSelf := unsafe.Pointer(a) // a reference to a + cSelf := a.C() C.startManager(cSelf) // start the manager defer C.stopManager(cSelf) @@ -214,12 +214,14 @@ func (a *api) Shutdown(exit int) { default: } + a.shareable.destroy() + } //export onNotificationWrapper func onNotificationWrapper(cNotification *C.Notification, context unsafe.Pointer) { // marshal from C to Go - a := (*api)(context) + a := unmarshal(context).Go().(*api) goNotification := newGoNotification(cNotification) if a.callback != nil { a.callback(a, goNotification) diff --git a/shareable.cpp b/shareable.cpp new file mode 100644 index 0000000..725aacf --- /dev/null +++ b/shareable.cpp @@ -0,0 +1,7 @@ +#include "api.h" + +shareable * newShareable(int i) { + shareable * r = (shareable *) malloc(sizeof(shareable)); + r->sharedIndex = i; + return r; +} diff --git a/shareable.go b/shareable.go new file mode 100644 index 0000000..db34d43 --- /dev/null +++ b/shareable.go @@ -0,0 +1,83 @@ +package openzwave + +// #cgo LDFLAGS: -lopenzwave -Lgo/src/github.com/ninjasphere/go-openzwave/openzwave +// #cgo CPPFLAGS: -Iopenzwave/cpp/src/platform -Iopenzwave/cpp/src -Iopenzwave/cpp/src/value_classes +// #include "api.h" +import "C" + +import ( + "fmt" + "sync" + "unsafe" +) + +// This module fixes an issue revealed when Go 1.6 tightened up the rules +// about sharing of Go pointers with C code. https://github.com/golang/go/issues/12416 +// +// Now we register a reference with a map and get a pointer to a structure in the C heap on return. +// We use the integer recorded in this structure to map back into the Go world. +// +// That way there are O(1) marshaling operations, and Go pointer is never shared with or +// dereferenced by the C world +// +var shared = map[int]Shareable{} +var sharedCount = 0 +var mu sync.RWMutex + +type Shareable interface { + C() unsafe.Pointer + Go() interface{} +} + +type shareable struct { + cref *C.shareable + goObject interface{} +} + +func (s *shareable) init(goObject interface{}) { + mu.Lock() + defer mu.Unlock() + + sharedCount++ + s.cref = C.newShareable(C.int(sharedCount)) + shared[int(s.cref.sharedIndex)] = s + s.goObject = goObject +} + +func (s *shareable) destroy() { + if s.cref != nil { + mu.Lock() + defer mu.Unlock() + delete(shared, int(s.cref.sharedIndex)) + C.free(s.C()) + s.cref = nil + } +} + +func (s *shareable) C() unsafe.Pointer { + return unsafe.Pointer(s.cref) +} + +func (s *shareable) Go() interface{} { + if s == nil { + return nil + } else { + return s.goObject + } +} + +func unmarshal(c unsafe.Pointer) Shareable { + mu.RLock() + defer mu.RUnlock() + + if c == nil { + return nil + } + + i := (*C.shareable)(c).sharedIndex + if s, ok := shared[int(i)]; !ok { + panic(fmt.Errorf("failure to unmarshal index %d", int(i))) + } else { + return s + } +}