Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically dispose COM and WinRT objects with NativeFinalizer #623

Merged
merged 19 commits into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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