Skip to content

Commit

Permalink
Merge pull request #623 from timsneath/native-finalizer
Browse files Browse the repository at this point in the history
Automatically dispose COM and WinRT objects with `NativeFinalizer`
  • Loading branch information
timsneath authored Dec 23, 2022
2 parents 6d46521 + 6a3aac3 commit d5fc339
Show file tree
Hide file tree
Showing 198 changed files with 1,423 additions and 1,413 deletions.
15 changes: 8 additions & 7 deletions doc/com.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ final fileDialog2 = IFileDialog2(
```

`createFromID` returns a `Pointer<COMObject>` containing the requested object,
which can then be cast into the appropriate interface as shown above. It is the
caller's responsibility to free the returned pointer when all interfaces that
derive from it are released.
which can then be cast into the appropriate interface as shown above.

### Asking a COM object for an interface

Expand All @@ -60,14 +58,18 @@ documentation](https://docs.microsoft.com/en-us/windows/win32/learnwin32/asking-
COM interfaces supply a method that wraps `queryInterface`. If you
have an existing COM object, you can call it as follows:

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

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

```dart
final modalWindow = IModalWindow.from(fileDialog2);
```

Where `createFromID` creates a new COM object, `toInterface` casts an existing
COM object to a new interface. As with `createFromID`, it is the caller's
responsibility to free the returned pointer when all interfaces that derive from
it are released.
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`.
Expand Down Expand Up @@ -102,7 +104,6 @@ When you have finished using a COM interface, you should release it with the `re

```dart
fileOpenDialog.release(); // Release the interface
free(fileOpenDialog.ptr); // Release the pointer to the interface
```

Often this will be called as part of a `try` / `finally` block, to guarantee
Expand Down
4 changes: 2 additions & 2 deletions example/calendar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ void main() {
final dateTime = calendar.getDateTime();
print(dateTime);

free(calendar.ptr);
free(clonedCalendar.ptr);
clonedCalendar.release();
calendar.release();
} finally {
winrtUninitialize();
}
Expand Down
7 changes: 2 additions & 5 deletions example/com_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:win32/win32.dart';

/// Return the current reference count.
int refCount(IUnknown unk) {
// Call AddRef() and Release(), which are inherited from IUnknown. Both return
// Call addRef() and release(), which are inherited from IUnknown. Both return
// the refcount after the operation, so by adding a reference and immediately
// removing it, we can get the original refcount.

Expand Down Expand Up @@ -52,7 +52,6 @@ void main() {
print('refCount is now ${refCount(modalWindow)}\n');

fileDialog2.release();
free(fileDialog2.ptr);
print('Release fileDialog2.\n'
'refCount is now ${refCount(modalWindow)}\n');

Expand All @@ -64,7 +63,6 @@ void main() {
print('refCount is now ${refCount(fileOpenDialog)}\n');

modalWindow.release();
free(modalWindow.ptr);
print('Release modalWindow.\n'
'refCount is now ${refCount(fileOpenDialog)}\n');

Expand All @@ -79,10 +77,9 @@ void main() {
}

fileOpenDialog.release();
free(fileOpenDialog.ptr);
print('Released fileOpenDialog.');

free(fileDialog.ptr);
fileDialog.release();
print('Released fileDialog.');

// Uninitialize COM now that we're done with it.
Expand Down
15 changes: 7 additions & 8 deletions example/knownfolder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Demonstrates usage of various shell APIs to retrieve known folder locations

import 'dart:ffi';

import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';

Expand Down Expand Up @@ -81,23 +82,21 @@ String getDesktopPath3() {
try {
final knownFolderManager = KnownFolderManager.createInstance();
var hr = knownFolderManager.getFolder(appsFolder, ppkf.cast());
if (FAILED(hr)) {
throw WindowsException(hr);
}
if (FAILED(hr)) throw WindowsException(hr);

final knownFolder = IKnownFolder(ppkf);
hr = knownFolder.getPath(0, ppszPath);
if (FAILED(hr)) {
throw WindowsException(hr);
}
if (FAILED(hr)) throw WindowsException(hr);

knownFolder.release();
knownFolderManager.release();

final path = ppszPath.value.toDartString();
CoUninitialize();
return path;
} finally {
free(appsFolder);
free(ppkf);
free(ppszPath);
CoUninitialize();
}
}

Expand Down
22 changes: 8 additions & 14 deletions example/network.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,16 @@ import 'package:win32/win32.dart';
void main() {
// Initialize COM
var hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) {
throw WindowsException(hr);
}
if (FAILED(hr)) throw WindowsException(hr);

final netManager = NetworkListManager.createInstance();
final nlmConnectivity = calloc<Int32>();
final enumPtr = calloc<COMObject>();
final netPtr = calloc<COMObject>();
final descPtr = calloc<Pointer<Utf16>>();
final elements = calloc<Uint32>();

try {
hr = netManager.getConnectivity(nlmConnectivity);
if (FAILED(hr)) {
throw WindowsException(hr);
}
if (FAILED(hr)) throw WindowsException(hr);

final connectivity = nlmConnectivity.value;
var isInternetConnected = false;
Expand All @@ -49,14 +43,14 @@ void main() {
print('Not connected to the Internet.');
}

final enumPtr = calloc<COMObject>();
hr = netManager.getNetworks(
NLM_ENUM_NETWORK.NLM_ENUM_NETWORK_ALL, enumPtr.cast());
if (FAILED(hr)) {
throw WindowsException(hr);
}
if (FAILED(hr)) throw WindowsException(hr);

print('\nNetworks (connected and disconnected) on this machine:');
final enumerator = IEnumNetworkConnections(enumPtr);
var netPtr = calloc<COMObject>();
hr = enumerator.next(1, netPtr.cast(), elements);
while (elements.value == 1) {
final network = INetwork(netPtr);
Expand All @@ -68,15 +62,15 @@ void main() {
'$networkName: ${isNetworkConnected ? 'connected' : 'disconnected'}');
}

network.release();
netPtr = calloc<COMObject>();
hr = enumerator.next(1, netPtr.cast(), elements);
}
} finally {
free(elements);
free(netPtr);
free(enumPtr);
free(descPtr);
free(nlmConnectivity);
free(netManager.ptr);
netManager.release();

CoUninitialize();
}
Expand Down
14 changes: 4 additions & 10 deletions example/shortcut.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,20 @@ void createShortcut(String path, String pathLink, String? description) {
final lpPath = path.toNativeUtf16();
final lpPathLink = pathLink.toNativeUtf16();
final lpDescription = description?.toNativeUtf16() ?? nullptr;
final ptrIID_IPersistFile = convertToCLSID(IID_IPersistFile);
final ppf = calloc<COMObject>();

try {
shellLink.setPath(lpPath);
if (description != null) shellLink.setDescription(lpDescription);

final hr = shellLink.queryInterface(ptrIID_IPersistFile, ppf.cast());
if (SUCCEEDED(hr)) {
IPersistFile(ppf)
..save(lpPathLink, TRUE)
..release();
}
final persistFile = IPersistFile.from(shellLink);
shellLink.release();
persistFile
..save(lpPathLink, TRUE)
..release();
} finally {
free(lpPath);
free(lpPathLink);
if (lpDescription != nullptr) free(lpDescription);
free(ptrIID_IPersistFile);
free(ppf);
}
}

Expand Down
4 changes: 4 additions & 0 deletions example/speech.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ const textToSpeak =

void main() {
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

final speechEngine = SpVoice.createInstance();
final pText = textToSpeak.toNativeUtf16();
speechEngine.speak(pText, SPEAKFLAGS.SPF_IS_NOT_XML, nullptr);

free(pText);
speechEngine.release();

CoUninitialize();
}
4 changes: 0 additions & 4 deletions example/wasapi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ void main() {
0, // role: system notification sound
ppDevice));
pDeviceEnumerator.release();
free(pDeviceEnumerator.ptr);

// Activate an IAudioClient interface for the output device.
final pDevice = IMMDevice(ppDevice.cast());
Expand Down Expand Up @@ -164,13 +163,10 @@ void main() {
free(pData);

pDevice.release();
free(ppDevice);

pAudioClient.release();
free(ppAudioClient);

pAudioRenderClient.release();
free(ppAudioRenderClient);

free(ppFormat);

Expand Down
3 changes: 2 additions & 1 deletion example/winrt_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void main() async {
print('Vector has ${filters.size} elements.');

free(pIndex);
free(filters.ptr);
filters.release();
picker.release();
winrtUninitialize();
}
6 changes: 2 additions & 4 deletions example/wmi_perf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ void main() {

using((Arena arena) {
final pLoc = WbemLocator.createInstance();
final ppNamespace = arena<Pointer<COMObject>>();
final ppNamespace = calloc<Pointer<COMObject>>();

connectWMI(pLoc, ppNamespace);

final refresher = WbemRefresher.createInstance();
final pConfig = IWbemConfigureRefresher.from(refresher);
final ppRefreshable = arena<Pointer<COMObject>>();
final ppRefreshable = calloc<Pointer<COMObject>>();

final pszQuery =
'Win32_PerfRawData_PerfProc_Process.Name="$processToMonitor"'
Expand Down Expand Up @@ -111,10 +111,8 @@ void main() {

refresher.release();
pConfig.release();
free(refresher.ptr);

pLoc.release();
free(pLoc.ptr);

CoUninitialize();
});
Expand Down
39 changes: 24 additions & 15 deletions lib/src/com/iunknown.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
// iunknown.dart

// THIS FILE IS GENERATED AUTOMATICALLY AND SHOULD NOT BE EDITED DIRECTLY.

// ignore_for_file: unused_import
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
// ignore_for_file: no_leading_underscores_for_local_identifiers

import 'dart:ffi';

import 'package:ffi/ffi.dart';

import '../callbacks.dart';
import '../combase.dart';
import '../constants.dart';
import '../exceptions.dart';
import '../guid.dart';
import '../macros.dart';
import '../structs.g.dart';
import '../utils.dart';
import '../variant.dart';
import '../win32/ole32.g.dart';

/// @nodoc
const IID_IUnknown = '{00000000-0000-0000-c000-000000000046}';
Expand All @@ -33,15 +24,28 @@ const IID_IUnknown = '{00000000-0000-0000-c000-000000000046}';
///
/// {@category Interface}
/// {@category com}
class IUnknown {
class IUnknown implements Finalizable {
// vtable begins at 0, is 3 entries long.
Pointer<COMObject> ptr;

IUnknown(this.ptr);
IUnknown(this.ptr) {
_finalizer.attach(this, ptr.cast(),
detach: this, externalSize: sizeOf<IntPtr>());
}

static final _ole32Lib = DynamicLibrary.open('ole32.dll');
static final _winCoTaskMemFree = _ole32Lib
.lookup<NativeFunction<Void Function(Pointer pv)>>('CoTaskMemFree');
static final _finalizer = NativeFinalizer(_winCoTaskMemFree.cast());

factory IUnknown.from(IUnknown interface) =>
IUnknown(interface.toInterface(IID_IUnknown));

/// Queries a COM object for a pointer to one of its interface; identifying
/// the interface by a reference to its interface identifier (IID).
///
/// If the COM object implements the interface, then it returns a pointer to
/// that interface after calling `addRef` on it.
int queryInterface(Pointer<GUID> riid, Pointer<Pointer> ppvObject) => ptr
.ref.vtable
.elementAt(0)
Expand All @@ -55,12 +59,17 @@ class IUnknown {
int Function(Pointer, Pointer<GUID> riid,
Pointer<Pointer> ppvObject)>()(ptr.ref.lpVtbl, riid, ppvObject);

/// Increments the reference count for an interface pointer to a COM object.
///
/// You should call this method whenever you make a copy of an interface
/// pointer.
int addRef() => ptr.ref.vtable
.elementAt(1)
.cast<Pointer<NativeFunction<Uint32 Function(Pointer)>>>()
.value
.asFunction<int Function(Pointer)>()(ptr.ref.lpVtbl);

/// Decrements the reference count for an interface on a COM object.
int release() => ptr.ref.vtable
.elementAt(2)
.cast<Pointer<NativeFunction<Uint32 Function(Pointer)>>>()
Expand All @@ -71,14 +80,14 @@ class IUnknown {
///
/// Takes a string (typically a constant such as `IID_IModalWindow`) and does
/// a COM QueryInterface to return a reference to that interface. This method
/// reduces the boilerplate associated with calling QueryInterface manually.
/// reduces the boilerplate associated with calling `queryInterface` manually.
Pointer<COMObject> toInterface(String iid) {
final pIID = convertToIID(iid);
final pObject = calloc<COMObject>();
final objectPtr = calloc<COMObject>();
try {
final hr = queryInterface(pIID, pObject.cast());
final hr = queryInterface(pIID, objectPtr.cast());
if (FAILED(hr)) throw WindowsException(hr);
return pObject;
return objectPtr;
} finally {
free(pIID);
}
Expand Down
Loading

0 comments on commit d5fc339

Please sign in to comment.