Return errors from `Dlopen`, `Dlsym`, and `Dlclose` (#103)
This PR returns errors from the Dlopen, Dlsym, and Dlclose functions in order to provide a more idiomatic API. Closes #100
- commit
- 0befdc3778764be7c9997dbe0326b2761eeafcd1
- author
- Arsen Musayelyan · · 2023-02-24 12:48 UTC
- committer
- GitHub · · 2023-02-24 12:48 UTC
- parent
- 0c858ac
- signature
- GPG Unverified
4AEE18F83AFDEB23The key is unknown - refs
- tag: v0.3.0-alpha
8 files · +56 -34
M README.md +2 -2
| @@ -41,8 +41,8 @@ func getSystemLibrary() string { | |||
| 41 | 41 | } | |
| 42 | 42 | ||
| 43 | 43 | func main() { | |
| 44 | - | libc := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 45 | - | if err := purego.Dlerror(); err != "" { | |
| 44 | + | libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 45 | + | if err != nil { | |
| 46 | 46 | panic(err) | |
| 47 | 47 | } | |
| 48 | 48 | var puts func(string) | |
| @@ -41,8 +41,8 @@ func getSystemLibrary() string { | |||||
| 41 | } | 41 | } | ||
| 42 | 42 | ||||
| 43 | func main() { | 43 | func main() { | ||
| 44 | - | libc := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) | 44 | + | libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) |
| 45 | - | if err := purego.Dlerror(); err != "" { | 45 | + | if err != nil { |
| 46 | panic(err) | 46 | panic(err) | ||
| 47 | } | 47 | } | ||
| 48 | var puts func(string) | 48 | var puts func(string) | ||
M dlfcn.go +17 -17
| @@ -33,34 +33,34 @@ func init() { | |||
| 33 | 33 | // A second call to Dlopen with the same path will return the same handle, but the internal | |
| 34 | 34 | // reference count for the handle will be incremented. Therefore, all | |
| 35 | 35 | // Dlopen calls should be balanced with a Dlclose call. | |
| 36 | - | func Dlopen(path string, mode int) uintptr { | |
| 37 | - | return fnDlopen(path, mode) | |
| 36 | + | func Dlopen(path string, mode int) (uintptr, error) { | |
| 37 | + | u := fnDlopen(path, mode) | |
| 38 | + | if errStr := fnDlerror(); errStr != "" { | |
| 39 | + | return 0, Dlerror{errStr} | |
| 40 | + | } | |
| 41 | + | return u, nil | |
| 38 | 42 | } | |
| 39 | 43 | ||
| 40 | 44 | // Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. | |
| 41 | 45 | // It returns the address where that symbol is loaded into memory. If the symbol is not found, | |
| 42 | 46 | // in the specified library or any of the libraries that were automatically loaded by Dlopen | |
| 43 | 47 | // when that library was loaded, Dlsym returns zero. | |
| 44 | - | func Dlsym(handle uintptr, name string) uintptr { | |
| 45 | - | return fnDlsym(handle, name) | |
| 46 | - | } | |
| 47 | - | ||
| 48 | - | // Dlerror returns a human-readable string describing the most recent error that | |
| 49 | - | // occurred from Dlopen, Dlsym or Dlclose since the last call to Dlerror. It | |
| 50 | - | // returns an empty string if no errors have occurred since initialization or | |
| 51 | - | // since it was last called. | |
| 52 | - | func Dlerror() string { | |
| 53 | - | // msg is only valid until the next call to Dlerror | |
| 54 | - | // which is why it gets copied into a Go string | |
| 55 | - | return fnDlerror() | |
| 48 | + | func Dlsym(handle uintptr, name string) (uintptr, error) { | |
| 49 | + | u := fnDlsym(handle, name) | |
| 50 | + | if errStr := fnDlerror(); errStr != "" { | |
| 51 | + | return 0, Dlerror{errStr} | |
| 52 | + | } | |
| 53 | + | return u, nil | |
| 56 | 54 | } | |
| 57 | 55 | ||
| 58 | 56 | // Dlclose decrements the reference count on the dynamic library handle. | |
| 59 | 57 | // If the reference count drops to zero and no other loaded libraries | |
| 60 | 58 | // use symbols in it, then the dynamic library is unloaded. | |
| 61 | - | // Dlclose returns false on success, and true on error. | |
| 62 | - | func Dlclose(handle uintptr) bool { | |
| 63 | - | return fnDlclose(handle) | |
| 59 | + | func Dlclose(handle uintptr) error { | |
| 60 | + | if fnDlclose(handle) { | |
| 61 | + | return Dlerror{fnDlerror()} | |
| 62 | + | } | |
| 63 | + | return nil | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | 66 | // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go | |
| @@ -33,34 +33,34 @@ func init() { | |||||
| 33 | // A second call to Dlopen with the same path will return the same handle, but the internal | 33 | // A second call to Dlopen with the same path will return the same handle, but the internal | ||
| 34 | // reference count for the handle will be incremented. Therefore, all | 34 | // reference count for the handle will be incremented. Therefore, all | ||
| 35 | // Dlopen calls should be balanced with a Dlclose call. | 35 | // Dlopen calls should be balanced with a Dlclose call. | ||
| 36 | - | func Dlopen(path string, mode int) uintptr { | 36 | + | func Dlopen(path string, mode int) (uintptr, error) { |
| 37 | - | return fnDlopen(path, mode) | 37 | + | u := fnDlopen(path, mode) |
| 38 | + | if errStr := fnDlerror(); errStr != "" { | |||
| 39 | + | return 0, Dlerror{errStr} | |||
| 40 | + | } | |||
| 41 | + | return u, nil | |||
| 38 | } | 42 | } | ||
| 39 | 43 | ||||
| 40 | // Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. | 44 | // Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. | ||
| 41 | // It returns the address where that symbol is loaded into memory. If the symbol is not found, | 45 | // It returns the address where that symbol is loaded into memory. If the symbol is not found, | ||
| 42 | // in the specified library or any of the libraries that were automatically loaded by Dlopen | 46 | // in the specified library or any of the libraries that were automatically loaded by Dlopen | ||
| 43 | // when that library was loaded, Dlsym returns zero. | 47 | // when that library was loaded, Dlsym returns zero. | ||
| 44 | - | func Dlsym(handle uintptr, name string) uintptr { | 48 | + | func Dlsym(handle uintptr, name string) (uintptr, error) { |
| 45 | - | return fnDlsym(handle, name) | 49 | + | u := fnDlsym(handle, name) |
| 46 | - | } | 50 | + | if errStr := fnDlerror(); errStr != "" { |
| 47 | - | 51 | + | return 0, Dlerror{errStr} | |
| 48 | - | // Dlerror returns a human-readable string describing the most recent error that | 52 | + | } |
| 49 | - | // occurred from Dlopen, Dlsym or Dlclose since the last call to Dlerror. It | 53 | + | return u, nil |
| 50 | - | // returns an empty string if no errors have occurred since initialization or | |||
| 51 | - | // since it was last called. | |||
| 52 | - | func Dlerror() string { | |||
| 53 | - | // msg is only valid until the next call to Dlerror | |||
| 54 | - | // which is why it gets copied into a Go string | |||
| 55 | - | return fnDlerror() | |||
| 56 | } | 54 | } | ||
| 57 | 55 | ||||
| 58 | // Dlclose decrements the reference count on the dynamic library handle. | 56 | // Dlclose decrements the reference count on the dynamic library handle. | ||
| 59 | // If the reference count drops to zero and no other loaded libraries | 57 | // If the reference count drops to zero and no other loaded libraries | ||
| 60 | // use symbols in it, then the dynamic library is unloaded. | 58 | // use symbols in it, then the dynamic library is unloaded. | ||
| 61 | - | // Dlclose returns false on success, and true on error. | 59 | + | func Dlclose(handle uintptr) error { |
| 62 | - | func Dlclose(handle uintptr) bool { | 60 | + | if fnDlclose(handle) { |
| 63 | - | return fnDlclose(handle) | 61 | + | return Dlerror{fnDlerror()} |
| 62 | + | } | |||
| 63 | + | return nil | |||
| 64 | } | 64 | } | ||
| 65 | 65 | ||||
| 66 | // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go | 66 | // these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go | ||
M dlfcn_test.go +4 -4
| @@ -30,8 +30,8 @@ func TestRegisterFunc(t *testing.T) { | |||
| 30 | 30 | if err != nil { | |
| 31 | 31 | t.Errorf("couldn't get system library: %s", err) | |
| 32 | 32 | } | |
| 33 | - | libc := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 34 | - | if err := purego.Dlerror(); err != "" { | |
| 33 | + | libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 34 | + | if err != nil { | |
| 35 | 35 | t.Errorf("failed to dlopen: %s", err) | |
| 36 | 36 | } | |
| 37 | 37 | var puts func(string) | |
| @@ -71,8 +71,8 @@ func Test_qsort(t *testing.T) { | |||
| 71 | 71 | if err != nil { | |
| 72 | 72 | t.Errorf("couldn't get system library: %s", err) | |
| 73 | 73 | } | |
| 74 | - | libc := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 75 | - | if err := purego.Dlerror(); err != "" { | |
| 74 | + | libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 75 | + | if err != nil { | |
| 76 | 76 | t.Errorf("failed to dlopen: %s", err) | |
| 77 | 77 | } | |
| 78 | 78 | ||
| @@ -30,8 +30,8 @@ func TestRegisterFunc(t *testing.T) { | |||||
| 30 | if err != nil { | 30 | if err != nil { | ||
| 31 | t.Errorf("couldn't get system library: %s", err) | 31 | t.Errorf("couldn't get system library: %s", err) | ||
| 32 | } | 32 | } | ||
| 33 | - | libc := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) | 33 | + | libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) |
| 34 | - | if err := purego.Dlerror(); err != "" { | 34 | + | if err != nil { |
| 35 | t.Errorf("failed to dlopen: %s", err) | 35 | t.Errorf("failed to dlopen: %s", err) | ||
| 36 | } | 36 | } | ||
| 37 | var puts func(string) | 37 | var puts func(string) | ||
| @@ -71,8 +71,8 @@ func Test_qsort(t *testing.T) { | |||||
| 71 | if err != nil { | 71 | if err != nil { | ||
| 72 | t.Errorf("couldn't get system library: %s", err) | 72 | t.Errorf("couldn't get system library: %s", err) | ||
| 73 | } | 73 | } | ||
| 74 | - | libc := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) | 74 | + | libc, err := purego.Dlopen(library, purego.RTLD_NOW|purego.RTLD_GLOBAL) |
| 75 | - | if err := purego.Dlerror(); err != "" { | 75 | + | if err != nil { |
| 76 | t.Errorf("failed to dlopen: %s", err) | 76 | t.Errorf("failed to dlopen: %s", err) | ||
| 77 | } | 77 | } | ||
| 78 | 78 | ||||
A error.go +13 -0
| @@ -0,0 +1,13 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | package purego | |
| 5 | + | ||
| 6 | + | // Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose. | |
| 7 | + | type Dlerror struct { | |
| 8 | + | s string | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | func (e Dlerror) Error() string { | |
| 12 | + | return e.s | |
| 13 | + | } | |
| @@ -0,0 +1,13 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package purego | |||
| 5 | + | ||||
| 6 | + | // Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose. | |||
| 7 | + | type Dlerror struct { | |||
| 8 | + | s string | |||
| 9 | + | } | |||
| 10 | + | ||||
| 11 | + | func (e Dlerror) Error() string { | |||
| 12 | + | return e.s | |||
| 13 | + | } | |||
M examples/libc/main.go +2 -2
| @@ -24,8 +24,8 @@ func getSystemLibrary() string { | |||
| 24 | 24 | } | |
| 25 | 25 | ||
| 26 | 26 | func main() { | |
| 27 | - | libc := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 28 | - | if err := purego.Dlerror(); err != "" { | |
| 27 | + | libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 28 | + | if err != nil { | |
| 29 | 29 | panic(err) | |
| 30 | 30 | } | |
| 31 | 31 | var puts func(string) | |
| @@ -24,8 +24,8 @@ func getSystemLibrary() string { | |||||
| 24 | } | 24 | } | ||
| 25 | 25 | ||||
| 26 | func main() { | 26 | func main() { | ||
| 27 | - | libc := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) | 27 | + | libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) |
| 28 | - | if err := purego.Dlerror(); err != "" { | 28 | + | if err != nil { |
| 29 | panic(err) | 29 | panic(err) | ||
| 30 | } | 30 | } | ||
| 31 | var puts func(string) | 31 | var puts func(string) | ||
M func.go +3 -3
| @@ -19,9 +19,9 @@ import ( | |||
| 19 | 19 | // | |
| 20 | 20 | // Windows does not support this function. | |
| 21 | 21 | func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { | |
| 22 | - | sym := Dlsym(handle, name) | |
| 23 | - | if sym == 0 { | |
| 24 | - | panic("purego: couldn't find symbol: " + Dlerror()) | |
| 22 | + | sym, err := Dlsym(handle, name) | |
| 23 | + | if err != nil { | |
| 24 | + | panic(err) | |
| 25 | 25 | } | |
| 26 | 26 | RegisterFunc(fptr, sym) | |
| 27 | 27 | } | |
| @@ -19,9 +19,9 @@ import ( | |||||
| 19 | // | 19 | // | ||
| 20 | // Windows does not support this function. | 20 | // Windows does not support this function. | ||
| 21 | func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { | 21 | func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { | ||
| 22 | - | sym := Dlsym(handle, name) | 22 | + | sym, err := Dlsym(handle, name) |
| 23 | - | if sym == 0 { | 23 | + | if err != nil { |
| 24 | - | panic("purego: couldn't find symbol: " + Dlerror()) | 24 | + | panic(err) |
| 25 | } | 25 | } | ||
| 26 | RegisterFunc(fptr, sym) | 26 | RegisterFunc(fptr, sym) | ||
| 27 | } | 27 | } | ||
M objc/objc_runtime_darwin.go +11 -5
| @@ -38,13 +38,19 @@ var ( | |||
| 38 | 38 | ) | |
| 39 | 39 | ||
| 40 | 40 | func init() { | |
| 41 | - | objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | |
| 42 | - | if err := purego.Dlerror(); err != "" { | |
| 43 | - | panic("objc: " + err) | |
| 41 | + | objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | |
| 42 | + | if err != nil { | |
| 43 | + | panic(fmt.Errorf("objc: %w", err)) | |
| 44 | + | } | |
| 45 | + | objc_msgSend_fn, err = purego.Dlsym(objc, "objc_msgSend") | |
| 46 | + | if err != nil { | |
| 47 | + | panic(fmt.Errorf("objc: %w", err)) | |
| 44 | 48 | } | |
| 45 | - | objc_msgSend_fn = purego.Dlsym(objc, "objc_msgSend") | |
| 46 | 49 | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | |
| 47 | - | objc_msgSendSuper2_fn = purego.Dlsym(objc, "objc_msgSendSuper2") | |
| 50 | + | objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") | |
| 51 | + | if err != nil { | |
| 52 | + | panic(fmt.Errorf("objc: %w", err)) | |
| 53 | + | } | |
| 48 | 54 | purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn) | |
| 49 | 55 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | |
| 50 | 56 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | |
| @@ -38,13 +38,19 @@ var ( | |||||
| 38 | ) | 38 | ) | ||
| 39 | 39 | ||||
| 40 | func init() { | 40 | func init() { | ||
| 41 | - | objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | 41 | + | objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) |
| 42 | - | if err := purego.Dlerror(); err != "" { | 42 | + | if err != nil { |
| 43 | - | panic("objc: " + err) | 43 | + | panic(fmt.Errorf("objc: %w", err)) |
| 44 | + | } | |||
| 45 | + | objc_msgSend_fn, err = purego.Dlsym(objc, "objc_msgSend") | |||
| 46 | + | if err != nil { | |||
| 47 | + | panic(fmt.Errorf("objc: %w", err)) | |||
| 44 | } | 48 | } | ||
| 45 | - | objc_msgSend_fn = purego.Dlsym(objc, "objc_msgSend") | |||
| 46 | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | 49 | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | ||
| 47 | - | objc_msgSendSuper2_fn = purego.Dlsym(objc, "objc_msgSendSuper2") | 50 | + | objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") |
| 51 | + | if err != nil { | |||
| 52 | + | panic(fmt.Errorf("objc: %w", err)) | |||
| 53 | + | } | |||
| 48 | purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn) | 54 | purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn) | ||
| 49 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | 55 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | ||
| 50 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | 56 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | ||
M objc/objc_runtime_darwin_test.go +4 -1
| @@ -77,7 +77,10 @@ func ExampleID_SendSuper() { | |||
| 77 | 77 | ||
| 78 | 78 | func TestSend(t *testing.T) { | |
| 79 | 79 | // NSNumber comes from Foundation so make sure we have linked to that framework. | |
| 80 | - | _ = purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL) | |
| 80 | + | _, err := purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL) | |
| 81 | + | if err != nil { | |
| 82 | + | t.Fatal(err) | |
| 83 | + | } | |
| 81 | 84 | const double = float64(2.34) | |
| 82 | 85 | // Initialize a NSNumber | |
| 83 | 86 | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | |
| @@ -77,7 +77,10 @@ func ExampleID_SendSuper() { | |||||
| 77 | 77 | ||||
| 78 | func TestSend(t *testing.T) { | 78 | func TestSend(t *testing.T) { | ||
| 79 | // NSNumber comes from Foundation so make sure we have linked to that framework. | 79 | // NSNumber comes from Foundation so make sure we have linked to that framework. | ||
| 80 | - | _ = purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL) | 80 | + | _, err := purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL) |
| 81 | + | if err != nil { | |||
| 82 | + | t.Fatal(err) | |||
| 83 | + | } | |||
| 81 | const double = float64(2.34) | 84 | const double = float64(2.34) | ||
| 82 | // Initialize a NSNumber | 85 | // Initialize a NSNumber | ||
| 83 | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | 86 | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | ||