Documentation ¶
Overview ¶
Package comshim provides a mechanism for maintaining an initialized multi-threaded component object model apartment.
When working with mutli-threaded apartments, COM requires at least one thread to be initialized, otherwise COM-allocated resources may be released prematurely. This poses a challenge in Go, which can have many goroutines running in parallel with weak thread affinity.
The comshim package provides a solution to this problem by maintaining a single thread-locked goroutine that has been initialized for multi-threaded COM use via a call to CoIntializeEx. A reference counter is used to determine the ongoing need for the shim to stay in place. Once the counter reaches 0, the thread is released and COM may be deinitialized.
The comshim package is designed to allow COM-based libraries to hide the threading requirements of COM from the user. COM interfaces can be hidden behind idomatic Go structures that increment the counter with calls to NewType() and decrement the counter with calls to Type.Close(). To see how this is done, take a look at the WrapperUsage example.
Example (GlobalUsage) ¶
package main import ( "github.com/scjalliance/comshim" ) func main() { // This ensures that at least one thread maintains an initialized // multi-threaded COM apartment. comshim.Add(1) // After we're done using COM the thread will be released. defer comshim.Done() // Do COM things here }
Output:
Example (WrapperUsage) ¶
package main import ( "sync" "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" "github.com/scjalliance/comshim" ) // Object wraps a COM interface in a way that is safe for multi-threaded access. // In this example it wraps IUnknown. type Object struct { m sync.Mutex iface *ole.IUnknown } // NewObject creates a new object. Be sure to document the need to call Close(). func NewObject() (*Object, error) { comshim.Add(1) iunknown, err := oleutil.CreateObject("Excel.Application") if err != nil { comshim.Done() return nil, err } return &Object{iface: iunknown}, nil } // Close releases any resources used by the object. func (o *Object) Close() { o.m.Lock() defer o.m.Unlock() if o.iface == nil { return // Already closed } o.iface.Release() o.iface = nil comshim.Done() } // Foo performs some action using the object's COM interface. func (o *Object) Foo() { o.m.Lock() defer o.m.Unlock() // Make use of o.iface } func main() { obj1, err := NewObject() // Create an object if err != nil { panic(err) } defer obj1.Close() // Be sure to close the object when finished obj2, err := NewObject() // Create a second object if err != nil { panic(err) } defer obj2.Close() // Be sure to close it too // Work with obj1 and obj2 }
Output:
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNegativeCounter is returned when the internal counter of a shim drops // below zero. This may indicate that Done() has been called more than once // for the same object. ErrNegativeCounter = errors.New("component object model shim counter has dropped below zero") // ErrAlreadyInitialized is returned when a shim finds itself on a thread // that has already been initialized. This probably indicates that some // previous goroutine failed to lock the OS thread or failed to call // CoUninitialize when it should have. ErrAlreadyInitialized = errors.New("component object model shim thread has already been initialized") )
Functions ¶
func Add ¶
func Add(delta int)
Add adds delta, which may be negative, to the counter of a global shim. As long as the counter is greater than zero, at least one thread is guaranteed to be initialized for mutli-threaded COM access.
If the counter becomes zero, the shim is released and COM resources may be released if there are no other threads that are still initialized.
If the counter goes negative, Add panics.
If the shim cannot be created for some reason, Add panics.
Types ¶
type Counter ¶
type Counter struct {
// contains filtered or unexported fields
}
Counter wraps an int64 atomic counter in a way that provides proper byte alignment.
type Shim ¶
type Shim struct {
// contains filtered or unexported fields
}
Shim provides control of a thread-locked goroutine that has been initialized for use with a mulithreaded component object model apartment. This is used to ensure that at least one thread within a process maintains an initialized connection to COM, and thus prevents COM resources from being unloaded from that process.
Control is implemented through the use of a counter similar to a waitgroup. As long as the counter is greater than zero then the goroutine will remain in a blocked condition with its COM connection intact.
func New ¶
func New() *Shim
New returns a new shim for keeping component object model resources allocated within a process.
func (*Shim) Add ¶
Add adds delta, which may be negative, to the counter for the shim. As long as the counter is greater than zero, at least one thread is guaranteed to be initialized for mutli-threaded COM access.
If the counter becomes zero, the shim is released and COM resources may be released if there are no other threads that are still initialized.
If the counter goes negative, Add panics.
If the shim cannot be created for some reason, Add panics.