Skip to main content

Basic Concepts

Since the win32 package primarily focuses on providing a lightweight wrapper for the underlying Windows API primitives, you can use the same API calls as described in Microsoft documentation to create and manipulate objects (e.g., CoCreateInstance and IUnknown->QueryInterface).

However, this approach introduces a certain amount of boilerplate and non-idiomatic Dart code. To address this, the library provides helper functions that reduce the labor compared to a pure C-style calling convention.

Initializing the COM Library

Before calling any COM APIs, you must first initialize the COM library by calling the CoInitializeEx function. Details of the threading models are outside the scope of this guide, but typically, you should write something like this:

final hr = CoInitializeEx(
nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr)) throw WindowsException(hr);

Creating COM Objects

You can create COM objects using the CoCreateInstance function:

hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, iid, ppv);

However, instead of manually allocating GUID structs for the clsid and iid values, checking the hr result code, and dealing with casting the ppv return object, it is easier to use the createFromID static helper function:

final fileDialog2 = IFileDialog2(
COMObject.createFromID(CLSID_FileOpenDialog, IID_IFileDialog2));

createFromID returns a Pointer<COMObject> containing the requested object, which can then be cast into the appropriate interface as shown above. This approach simplifies the creation process and reduces boilerplate code.

Requesting an Interface from a COM Object

COM objects can implement multiple interfaces, but you cannot simply cast an object to a different interface. Instead, pointers are returned to a specific interface. Every COM interface in the win32 package derives from IUnknown, allowing you to call queryInterface on any object to retrieve a pointer to a different supported interface.

For more information on COM interfaces, refer to the Microsoft documentation.

COM interfaces provide a method that wraps queryInterface. If you have an existing COM object, you can call it as follows:

  final modalWindow = IModalWindow(fileDialog2.toInterface(IID_IModalWindow));

Alternatively, you can use the from constructor that wraps toInterface for you:

  final modalWindow = IModalWindow.from(fileDialog2);

While createFromID creates a new COM object, toInterface casts an existing COM object to a new interface.

Attempting to cast a COM object to an unsupported interface will fail, and a WindowsException will be thrown with an hr of E_NOINTERFACE.

Calling Methods on a COM Object

When calling methods on a COM object, it's wise to assign the return value to a variable and test it for success or failure. You can use the SUCCEEDED() or FAILED() functions for this purpose.

For example:

final hr = fileOpenDialog.show(NULL);
if (SUCCEEDED(hr)) {
// Do something with the returned dialog box values.
}

Failures are reported as HRESULT values (e.g., E_ACCESSDENIED). Occasionally, a Win32 error code is converted to an HRESULT, such as when a user cancels a common dialog box:

final hr = fileOpenDialog.show(NULL);
if (FAILED(hr) && hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
// User clicked cancel.
}

Releasing COM Objects

In general, releasing COM objects isn't something you need to worry about because when the object becomes inaccessible to the program, the Finalizer automatically releases it for you.

WARNING

If you are manually managing the lifetime of an object, such as by calling the .detach() method, it is important to ensure that you release it properly by calling the .release() method. Additionally, you should free up the memory that was allocated for the object by calling the free() helper function as follows:

fileOpenDialog.release(); // Release the COM object.
free(fileOpenDialog.ptr); // Release the allocated memory for the object.

This is necessary to prevent memory leaks and ensure that the memory used by the object is properly released.

Good to know:

It is important to include this code as part of a try/finally block to ensure that the object is released properly, even if an exception is thrown during the execution of your code.