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, since this introduces a certain amount of boilerplate and non-idiomatic Dart code, the library also provides some helper functions that reduce the labor compared to a pure C-style calling convention.

Initializing the COM library

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

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

Creating a COM object

You can create COM objects using the C library:

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

However, rather than manually allocate GUID structs for the clsid and iid values, checking the hr result code and deal 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.

Asking a COM object for an interface

COM allows objects to implement multiple interfaces, but it does not let you merely cast an object to a different interface. Instead, returned pointers are to a specific interface. However, every COM interface in the win32 package derives from IUnknown, so as in other language implementations of COM, you may call queryInterface on any object to retrieve a pointer to a different supported interface.

More information on COM interfaces may be found in the Microsoft documentation.

COM interfaces supply 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));

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

  final modalWindow = IModalWindow.from(fileDialog2);

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

Attempting to cast a COM object to an interface it does not support will fail, of course. A WindowsException will be thrown with an hr of E_NOINTERFACE.

Calling a method on a COM object

No special considerations are needed here; however, it is wise to assign the return value to a variable and test it for success or failure. You can use the SUCCEEDED() or FAILED() top-level functions to do this, 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). Sometimes a Win32 error code is converted to an HRESULT, as in the case where 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.

caution

If you are manually managing the lifetime of an object, such as by calling the .detach() method, then 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.

tip

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.