{"id":17321453,"url":"https://github.com/jamesits/goinvoke","last_synced_at":"2025-04-14T15:41:00.974Z","repository":{"id":65199661,"uuid":"586955587","full_name":"Jamesits/goinvoke","owner":"Jamesits","description":"Load dynamic libraries (DLL/so/dylib) with ease, without cgo.","archived":false,"fork":false,"pushed_at":"2024-09-09T02:59:53.000Z","size":108,"stargazers_count":30,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T04:34:55.740Z","etag":null,"topics":["go","golang","loadlibrary"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/jamesits/goinvoke","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Jamesits.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-09T16:16:16.000Z","updated_at":"2025-03-08T21:43:53.000Z","dependencies_parsed_at":"2024-06-21T00:57:45.681Z","dependency_job_id":"198c5b52-88f9-4c29-b304-d4036c38f9c6","html_url":"https://github.com/Jamesits/goinvoke","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jamesits%2Fgoinvoke","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jamesits%2Fgoinvoke/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jamesits%2Fgoinvoke/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jamesits%2Fgoinvoke/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Jamesits","download_url":"https://codeload.github.com/Jamesits/goinvoke/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248907895,"owners_count":21181426,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","loadlibrary"],"created_at":"2024-10-15T13:37:42.298Z","updated_at":"2025-04-14T15:41:00.947Z","avatar_url":"https://github.com/Jamesits.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# goinvoke\r\n\r\nLoad DLLs and import functions with ease. \r\n\r\nIf all you need is an equivalent of `LoadLibrary`/`dlopen` and `GetProcAddress`/`dlsym` in Go, this library is a \r\nlot easier to work with than cgo. It does not require a C header to start with, and allows you to dynamically load \r\ndifferent DLLs exposing the same set of functions.\r\n\r\n[![Go Reference](https://pkg.go.dev/badge/github.com/jamesits/goinvoke.svg)](https://pkg.go.dev/github.com/jamesits/goinvoke)\r\n\r\n## Usage\r\n\r\nSimply define a struct with attributes in the type of `*windows.Proc` or `*windows.LazyProc`, and call \r\n`goinvoke.Unmarshal(\"path\\\\to\\\\file.dll\", pointerToStruct)`. ([Other OSes](#cross-platform-usage))\r\n\r\n```go\r\n//go:build windows\r\n\r\npackage main\r\n\r\nimport (\r\n\t\"errors\"\r\n\t\"fmt\"\r\n\t\"github.com/jamesits/goinvoke\"\r\n\t\"golang.org/x/sys/windows\"\r\n)\r\n\r\ntype Kernel32 struct {\r\n\tGetTickCount *windows.Proc\r\n\r\n\t// you can override the function name with a tag\r\n\tGetStartupInfo *windows.Proc `func:\"GetStartupInfoW\"`\r\n}\r\n\r\nfunc main() {\r\n\tk := Kernel32{}\r\n\terr := goinvoke.Unmarshal(\"kernel32.dll\", \u0026k)\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\r\n\t// a minimal example\r\n\tcount, _, err := k.GetTickCount.Call()\r\n\tif !errors.Is(err, windows.ERROR_SUCCESS) {\r\n\t\tpanic(err)\r\n\t}\r\n\tfmt.Printf(\"GetTickCount() = %d\\n\", count)\r\n\r\n\t// a more complete example\r\n\tstartupInfo := windows.StartupInfo{}\r\n\t_, _, err = k.GetStartupInfo.Call(uintptr(unsafe.Pointer(\u0026startupInfo)))\r\n\tif !errors.Is(err, windows.ERROR_SUCCESS) {\r\n\t\tpanic(err)\r\n\t}\r\n\tlpTitle := windows.UTF16PtrToString(startupInfo.Title)\r\n\tfmt.Printf(\"lpTitle = %s\\n\", lpTitle)\r\n}\r\n```\r\n\r\nFor more examples of using this library, [`unmarshal_test.go`](unmarshal_test.go) is a good start point. If you need \r\nto define callback functions, see [`cgo_callback.go`](internal/test/cgo_callback.go) for an example. \r\n\r\n[Go: WindowsDLLs](https://github.com/golang/go/wiki/WindowsDLLs) offers a great view of using the \r\n`(*windows.Proc).Call()` method. \r\n\r\n## Type Generator (Windows only)\r\n\r\nHave a large DLL with a lot of functions and want to access all of them at once? Use our convenient `invoker` tool to\r\ngenerate the struct required! For example, if we want to call multiple functions in `user32.dll`, use the following \r\ncommands to generate a \"header\":\r\n```shell\r\ngo install github.com/jamesits/goinvoke/cmd/invoker@latest\r\ninvoker -dll \"user32.dll\" -generate\r\n```\r\n\r\nA file named `user32_dll.go` will be generated in the current directory with all the exports from that DLL. To use it \r\nin your code:\r\n```go\r\n//go:build windows\r\n\r\npackage main\r\n\r\nimport (\r\n\t\"github.com/jamesits/goinvoke\"\r\n)\r\n\r\nfunc main() {\r\n\tvar err error\r\n\t\r\n\tk := User32{}\r\n\r\n\t// either use the object method\r\n\terr = k.Unmarshal(\"user32.dll\")\r\n\t// or use the global Unmarshal function\r\n\terr = goinvoke.Unmarshal(\"user32.dll\", \u0026k)\r\n\t\r\n    // ...\r\n}\r\n```\r\n\r\nIn the future, when your DLL is updated with new exported functions, just re-generate the file:\r\n```shell\r\ngo generate .\r\n```\r\n\r\nFor advanced usage of this tool, run `invoker -help`.\r\n\r\n# Caveats\r\n\r\n## Relative Import (Windows only)\r\n\r\nOn Windows, due to security concerns, if the path is relative and only contains a base name (e.g. `\"kernel32.dll\"`), \r\nfile lookup is limited to *only* `%WINDIR%\\System32`. On platforms other than Windows, we always use`dlopen(3)` lookup \r\norder.\r\n\r\nIf you want to load a DLL packaged with your program (the DLL sits right beside your EXE, or under some sub-folder), \r\nthe safe way is to get the directory where your program exists first:\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"github.com/jamesits/goinvoke\"\r\n\t\"github.com/jamesits/goinvoke/utils\"\r\n\t\"path/filepath\"\r\n)\r\n\r\ntype MyDll struct {\r\n\t// ...\r\n}\r\n\r\nfunc main() {\r\n\tvar err error\r\n\t\r\n\texecutableDir, err := utils.ExecutableDir()\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\t\r\n\tmyDll := MyDll{}\r\n\terr = goinvoke.Unmarshal(filepath.Join(executableDir, /* optional */ \"sub-folder\", \"MyDll.dll\"), \u0026myDll)\r\n}\r\n```\r\n\r\nIf you really want to load a DLL from your *working directory*, specify your intention explicitly \r\nwith `\".\\\\filename.dll\"`.\r\nLoading a DLL from an arbitrary working directory might lead to serious security issues. \r\nDO NOT do this unless you know exactly what you are doing.\r\n\r\n## Cross Platform Usage\r\n\r\nSince v1.3.0, goinvoke supports Linux, BSD and macOS. For example, on Linux you can:\r\n\r\n```go\r\n//go:build linux\r\n\r\npackage main\r\n\r\nimport (\r\n\t\"github.com/jamesits/goinvoke\"\r\n\t\"github.com/jamesits/goinvoke/utils\"\r\n)\r\n\r\ntype LibC struct {\r\n\tPuts *goinvoke.Proc `func:\"puts\"`\r\n}\r\n\r\nvar libC LibC\r\n\r\nfunc main() {\r\n\terr := goinvoke.Unmarshal(\"libc.so.6\", \u0026libC)\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\r\n\t_, _, _ = libC.Puts.Call(utils.StringToUintPtr(\"114514\\n\"))\r\n}\r\n```\r\n\r\nFor true cross-platform code, you can use `goinvoke.FunctionPointer` interface instead of `*windows.Proc` \r\nand `*goinvoke.Proc`.\r\n\r\n## Error Processing\r\n\r\nThe `Unmarshal()` method returns an error with type `(*multierror.Error)` if any of the following case happens:\r\n- DLL load fails (file does not exist, permission/ACL problem, WDAC/Code Integration policy, etc. )\r\n- The DLL file exists, but a function defined in the struct is not exported by that DLL\r\n\r\nIt always trys to fill as much as function pointers it can find, and will not be stopped by non-critical errors.\r\nSo, depending on your use case, you can ignore certain errors reported by `Unmarshal()`, and use whether the struct \r\nfield is `nil` as an indicator of exported function existence of your loaded DLL file.\r\n\r\nIf you really want to decode individual errors, use `err.(*multierror.Error).Errors`. There are some examples \r\nin [`unmarshal_test.go`](unmarshal_test.go).\r\n\r\n## Importing Functions by Ordinal (Windows only)\r\n\r\nImporting functions by ordinal is fully supported, just use `*windows.Proc` and add a `ordinal` tag. The `ordinal` tag, \r\nif exists, always overrides the `func` tag.\r\n\r\n```go\r\npackage main\r\n\r\nimport (\r\n\t\"github.com/jamesits/goinvoke\"\r\n\t\"golang.org/x/sys/windows\"\r\n)\r\n\r\ntype shlwapi struct {\r\n\t// function definition compatible with Windows XP or earlier\r\n\tSHCreateMemStream *windows.Proc `ordinal:\"12\"`\r\n}\r\n\r\nfunc main() {\r\n\tvar err error\r\n\r\n\ts := shlwapi{}\r\n\terr = goinvoke.Unmarshal(\"shlwapi.dll\", \u0026s)\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\t\r\n\t// ...\r\n}\r\n```\r\n\r\n`*windows.LazyProc` does not support a `ordinal` tag.\r\n\r\n## Performance\r\n\r\n`syscall.Syscall` is somewhat slower due to it allocating heap twice more than a cgo call (variable length arguments, \r\nand another copy inside `syscall.Syscall()`). There is a `internal/benchmark` package to compare the performance of \r\n`*windows.Proc`, `*windows.LazyProc` and cgo. \r\nExample result under Go 1.19.1:\r\n\r\n```text\r\ngoos: windows\r\ngoarch: amd64\r\npkg: github.com/jamesits/goinvoke/internal/benchmark\r\ncpu: AMD Ryzen 9 5900X 12-Core Processor\r\nBenchmarkSyscallIsDebuggerPresent\r\nBenchmarkSyscallIsDebuggerPresent-24            30003824                38.32 ns/op\r\nBenchmarkSyscallIsDebuggerPresentLazy\r\nBenchmarkSyscallIsDebuggerPresentLazy-24        29269077                41.75 ns/op\r\nBenchmarkCgoIsDebuggerPresent\r\nBenchmarkCgoIsDebuggerPresent-24                41355494                30.92 ns/op\r\nPASS\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesits%2Fgoinvoke","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesits%2Fgoinvoke","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesits%2Fgoinvoke/lists"}