Callbacks
Some Win32 APIs are asynchronous: a function call doesn't complete and
return all the information requested immediately. Dart has the async/await
pattern for handling asynchronous calls within Dart libraries and packages, but
the C-based Win32 API model does not have a similar construct.
Therefore, calling Win32 APIs that are asynchronous is handled through callbacks, where you pass a function that Win32 executes first to pass data back.
Creating Callbacks
Dart currently offers two ways to create callback functions that can be invoked from native functions:
NativeCallable.isolateLocal: Constructs aNativeCallablethat must be invoked from the same thread that created it.NativeCallable.listener: Constructs aNativeCallablethat can be invoked from any thread. However, there is a restriction — onlyvoidfunctions are supported.
In most cases, utilizing NativeCallable.isolateLocal should suffice. However,
if you encounter a Cannot invoke native callback outside an isolate. error, it
indicates that the API you're calling operates in a different thread
context.
In such cases, if the callback is a void function, consider using
NativeCallable.listener. If not, there is currently no way to call that
particular API.
A work-in-progress proposal is underway to introduce a
NativeCallable.shared constructor, enabling callbacks
to be invoked from any thread without restrictions.
For example, let's look at the EnumFontFamiliesEx
function, which enumerates all uniquely-named fonts in the system that
match a specified set of font characteristics. EnumFontFamiliesEx takes a
LOGFONT struct which contains information about the fonts to enumerate.
The Dart function signature looks like this:
int EnumFontFamiliesEx(
int hdc,
Pointer<LOGFONT> lpLogfont,
Pointer<NativeFunction<FONTENUMPROC>> lpProc,
int lParam,
int dwFlags) { ... }
Notice the third parameter — a pointer to the callback function
FONTENUMPROC. This is called once for every enumerated
font, and is defined as:
typedef FONTENUMPROC = Int32 Function(Pointer<LOGFONT> lpelfe,
Pointer<TEXTMETRIC> lpntme, DWORD FontType, LPARAM lParam);
To create a callback function, first define a Dart function that matches the
types in the native callback function above. Replace any integer types
with a Dart int type where applicable.
Here's an example:
int enumerateFonts(
Pointer<LOGFONT> logFont, Pointer<TEXTMETRIC> _, int __, int ___) {
// Get extended information from the font.
final logFontEx = logFont.cast<ENUMLOGFONTEX>();
print(logFontEx.ref.elfFullName);
return TRUE; // continue enumeration.
}
- Since we only use the first parameter, we use the Dart
_convention to indicate that other parameter values are ignored. - This callback returns
TRUEto indicate that the enumeration should continue. Alternatively, we could stop the callback from being fired for the next enumerated value by returningFALSE(for example, if we had found a specific font that we were looking for).
Now that we have our function callback, we can use it with EnumFontFamiliesEx:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
int enumerateFonts(
Pointer<LOGFONT> logFont, Pointer<TEXTMETRIC> _, int __, int ___) {
// Get extended information from the font.
final logFontEx = logFont.cast<ENUMLOGFONTEX>();
print(logFontEx.ref.elfFullName);
return TRUE; // continue enumeration.
}
void main() {
final hDC = GetDC(NULL);
final searchFont = calloc<LOGFONT>()
..ref.lfCharSet = HANGUL_CHARSET;
final lpProc = NativeCallable<FONTENUMPROC>.isolateLocal(
enumerateFonts,
exceptionalReturn: 0,
);
EnumFontFamiliesEx(hDC, searchFont, lpProc.nativeFunction, 0, 0);
lpProc.close(); // Close the callback when it's no longer needed.
free(searchFont);
}
In the above example, we first create a struct LOGFONT containing our required
search characteristics (fonts that support the Hangul, or Korean, character
set). We then create a NativeCallable for the Dart callback function using the
NativeCallable.isolateLocal constructor. Finally, we call the
EnumFontFamiliesEx API to initiate the enumeration. The Dart
enumerateFonts() function will now be called once for every discovered font
that matches the search characteristics.