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 aNativeCallable
that must be invoked from the same thread that created it.NativeCallable.listener
: Constructs aNativeCallable
that can be invoked from any thread. However, there is a restriction — onlyvoid
functions 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
TRUE
to 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 = FONT_CHARSET.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.