Library / SDK
go-ole/go-ole avatar
go-ole/go-ole

go-ole calls Windows COM from Go without cgo

win32 ole implementation for golang

1,321 stars214 forksGoMIT

At a glance

What is it?
go-ole is a Go binding for Windows COM that loads the OLE shared libraries directly instead of going through cgo, which keeps a cross compiled Go program a single static binary. The cost is that almost everything it does is Windows specific, that the test suite needs a registered COM server, and that threading discipline is your problem.
Who is it for?
go-ole suits a Go developer on Windows who needs to drive a COM component, such as an Excel automation server, from a program that must stay a pure Go build, and it suits anyone who wants to avoid a cgo toolchain requirement in a cross compiled binary.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

COM from Go, without a C toolchain

go-ole is described as Go bindings for Windows COM using shared libraries instead of cgo. That single phrase describes both the point of the library and its main constraint.

COM is the component model that Windows applications have used for decades, and it is how you automate Excel, Word, or any other application that exposes an automation interface. Reaching it from Go normally means either shelling out to a program, or writing a cgo wrapper around the OLE32 and oleaut32 libraries. The cgo route is a real cost: a cgo package needs a C compiler on the machine that builds it, and a cross compiled Go binary that uses cgo needs a matching cross C toolchain, which is one of the reasons people avoid Go for Windows automation in the first place.

go-ole takes the other route. It loads the OLE shared libraries at run time and calls into them directly, so the package stays pure Go and the resulting binary does not need a C runtime beside it. For a team shipping a single Windows executable, that difference is often the whole decision.

The package was written by Yasuhiro Matsumoto, is licensed under the MIT licence, and is a dependency of a number of Go libraries that talk to Windows automation. The module is github.com/go-ole/go-ole and the manifest declares Go 1.12 as the language version, with dependencies on stretchr/testify for the assertions and golang.org/x/sys for the system calls the Windows specific files need.

The audience is narrow and clear: Go developers on Windows who need to talk to a COM server, especially in contexts where cgo is a problem.

The file names show the architecture exactly

The repository root is a wall of Go files, and their names describe the design more clearly than any documentation could.

There is one family of files per COM interface, and each interface appears in three variants. There is idispatch.go, iunknown.go, ienumvariant.go, iinspectable.go, iconnectionpoint.go, iconnectionpointcontainer.go, iprovideclassinfo.go, itypeinfo.go, com.go, connect.go and safearray.go, and then for most of them there is a _windows.go file and a _func.go file.

The split is the point. The plain file holds the portable part, the code that is valid on any operating system and contains the type definitions and the interface wrappers. The _windows.go file holds the parts that can only compile on Windows, where the actual calls into ole32.dll and oleaut32.dll happen. The _func.go file holds the shared implementation that is called from both worlds, so the logic is written once and the platform specific plumbing stays in the platform specific file.

Two directories sit alongside that. oleutil/ holds the higher level helpers, the kind of convenience layer a caller reaches for first, and data/ holds data files the tests use. There is also build/, the _example/ directory with a sample program per interface, an appveyor.yml for the Windows build, a ChangeLog.md, a SECURITY.md and a go.sum.

The test files follow the same three way split, with com_test.go, connect_test.go, guid_test.go and the per interface test files, and the Windows specific ones carry a _windows_test.go suffix, which is how the package keeps a build that compiles on Linux while the COM calls do not exist there.

COM apartments are your responsibility, and the pairing rule is absolute

The multithreading section is short, and it is the part of this library most likely to cause you a debugging session.

The project offers two solutions for handling goroutines and multithreading. You may lock the function or goroutine to a single thread:

go
runtime.LockOSThread()
defer runtime.UnlockOSThread()

Or you use the scjalliance/comshim package, which handles the apartment concerns for you.

Then comes the rule that has no exception in the documentation: the key to any solution is that you must call CoUninitialize() for every CoInitialize(). COM initialises a thread into an apartment, and the matching uninitialise has to happen on the same thread.

This is inherent to COM rather than a quirk of this binding. A COM object may have an apartment affinity, meaning it must be called from the thread it was created on, and Go's scheduler moves goroutines between threads freely. Locking the OS thread is the standard way to keep a goroutine pinned to the thread that initialised COM, and the defer is what guarantees the unlock happens even if the function panics.

The practical consequence for a Go program is that COM calls are not goroutine friendly by default. If you have an application with a worker pool talking to a COM server, either every worker locks its thread and initialises COM in it, or you put all COM work behind a single dedicated goroutine that owns the apartment. Neither is complicated, but both have to be decided rather than stumbled into.

The tests need a real COM server on Windows

Testing this library is not a self contained matter, and the documentation is explicit about the three steps.

First you download a release from the test-com-server repository. Then you register the COM server, using RegAsm.exe with the codebase and nologo flags, from the 32 bit Framework directory on a 32 bit Windows and from the Framework64 directory on 64 bit Windows. Then you run go test.

The registration step is the one that is easy to get wrong, since the path differs with the architecture and the machine, and a server that is not registered shows up as a test failure rather than as a setup error.

The continuous integration configuration explains what can and cannot be automated, and the answer is instructive. Travis-CI was added to check builds on Linux, to ensure that go get works when cross building, and it is explicitly noted that Travis-CI is not used to test cross building even though that may change. The reason for the limit is stated plainly: it is not currently possible to test the library on Linux, since the COM API is specific to Windows, and it is not currently possible to run a COM server on Linux or even connect to a remote COM server.

AppVeyor is used to build on Windows using the in-development test COM server, and it is currently only used to test the build and ensure that the code works on Windows, with the plan to register a COM server and then run the test cases against it.

The claim that the tests do run and do pass, and that this should be maintained with commits, is the project's own statement about its own test suite.

Trying it out, and the compatibility promise

The quick way to see the library work is the example program, and the instructions are three commands:

bash
go get github.com/go-ole/go-ole
cd /path/to/go-ole/
go test

followed by running the Excel example from its directory with go run excel.go. That example is the interesting part, because Excel automation is the classic use case, and it means you can see the binding drive a real COM server without writing any of the interface plumbing yourself.

The versioning policy is the other thing to read. The project uses semantic versioning for its version numbers, in a form similar to the version contract of the Go language, which means the major version will always maintain backwards compatibility with minor versions, minor versions will only add new additions and changes, and fixes will always be in patch releases. The stated intent is that this contract should let you upgrade to new minor and patch versions without breakage or modifications to your existing code, and that if you find breakage you should leave a ticket so it can be fixed.

The release history puts that promise in context. v1.1.1 was tagged in 2015, v1.2.0 in November 2015 and v1.3.0 in August 2023, so the project has had three releases in its life, with long quiet periods between them. The last push was on 2026-10-12 and the repository is not archived, so work continues even though the version number has moved slowly.

The practical reading is that the semver promise is real but rarely exercised, since most of the interesting work happened years ago. If you are pinning a version, you are most likely pinning 1.3.0, and the migration cost of staying there is low.

Editorial conclusion

go-ole suits a Go developer on Windows who needs to drive a COM component, such as an Excel automation server, from a program that must stay a pure Go build, and it suits anyone who wants to avoid a cgo toolchain requirement in a cross compiled binary. It is the wrong choice if you need the library to run on Linux, because COM is Windows specific and the project states you cannot run a COM server on Linux or connect to a remote one, and a poor match if you want COM work on a goroutine without thinking about apartments, because the rules are yours to enforce. Before adopting, read the apartment threading rules, register the test COM server on a Windows machine if you intend to run the tests, and check that the version you depend on still matches the semver compatibility promise the project makes.

Frequently asked questions

Why use go-ole instead of cgo bindings for Windows COM?

go-ole loads the OLE shared libraries directly rather than going through cgo, so the package stays pure Go and a cross compiled binary does not need a matching C toolchain or a C runtime beside it. The trade-off is that the COM calls themselves only work on Windows.

How does go-ole handle COM threading rules?

You either lock the goroutine to a single thread with runtime.LockOSThread and defer runtime.UnlockOSThread, or you use the scjalliance/comshim package. The project states that you must call CoUninitialize() for every CoInitialize().

How do I run the go-ole tests?

Download a release from the test-com-server repository, register the COM server with RegAsm.exe using the 32 bit or 64 bit Framework path depending on the architecture, and then run go test. The tests cannot be run on Linux, since COM is Windows specific.

What version compatibility does go-ole promise?

The project follows semantic versioning with a contract close to the Go language's own: the major version keeps backwards compatibility, minor versions only add, and fixes are in patch releases. Recent tags are v1.1.1, v1.2.0 in 2015 and v1.3.0 in 2023.

Official sources

  1. go-ole/go-ole on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/go-ole-go-ole.svg)](https://hysenlabs.com/projects/go-ole-go-ole)

Was this useful?