Native extensions allow you to call C/C++ code from Dart using the Dart VM C API. This enables performance-critical operations, hardware access, and integration with existing native libraries.
Native Functions
Native functions are C functions that can be called from Dart code. They receive arguments through the Dart_NativeArguments structure and set return values using Dart_SetReturnValue().
Function Signature
typedef void ( * Dart_NativeFunction)(Dart_NativeArguments arguments);
All native functions follow this signature - they return void and receive a single Dart_NativeArguments parameter.
Dart_NativeArguments
typedef struct _Dart_NativeArguments * Dart_NativeArguments;
This opaque structure provides access to:
Function arguments
Isolate group data
Return value setting
Accessing Arguments
Dart_GetNativeArgument
Dart_EXPORT Dart_Handle Dart_GetNativeArgument (
Dart_NativeArguments args ,
int index );
Gets the native argument at the specified index as a handle.
Native arguments structure passed to the function
Zero-based index of the argument to retrieve
Returns: Handle to the argument
Dart_GetNativeArgumentCount
Dart_EXPORT int Dart_GetNativeArgumentCount (Dart_NativeArguments args );
Returns the number of arguments passed to the native function.
Type-Specific Argument Functions
Dart_GetNativeIntegerArgument
Dart_EXPORT Dart_Handle Dart_GetNativeIntegerArgument (
Dart_NativeArguments args ,
int index ,
int64_t * value );
Extracts an integer argument directly without creating a handle.
Pointer to store the extracted integer value
Returns: Success handle or error handle if argument is not an integer
Dart_GetNativeBooleanArgument
Dart_EXPORT Dart_Handle Dart_GetNativeBooleanArgument (
Dart_NativeArguments args ,
int index ,
bool * value );
Extracts a boolean argument.
Dart_GetNativeDoubleArgument
Dart_EXPORT Dart_Handle Dart_GetNativeDoubleArgument (
Dart_NativeArguments args ,
int index ,
double * value );
Extracts a double argument.
Dart_GetNativeStringArgument
Dart_EXPORT Dart_Handle Dart_GetNativeStringArgument (
Dart_NativeArguments args ,
int index ,
void ** peer );
Gets a string argument and its peer pointer if available.
Setting Return Values
Dart_SetReturnValue
Dart_EXPORT void Dart_SetReturnValue (
Dart_NativeArguments args ,
Dart_Handle retval );
Sets the return value for a native function.
Handle to the return value. Can be an error handle to propagate errors.
If retval is an error handle, the error will be propagated once the native function exits. The function will still complete normally, allowing cleanup code to run.
Type-Specific Return Functions
Dart_SetIntegerReturnValue
Dart_EXPORT void Dart_SetIntegerReturnValue (
Dart_NativeArguments args ,
int64_t retval );
Sets an integer return value directly without creating a handle.
Dart_SetBooleanReturnValue
Dart_EXPORT void Dart_SetBooleanReturnValue (
Dart_NativeArguments args ,
bool retval );
Sets a boolean return value.
Dart_SetDoubleReturnValue
Dart_EXPORT void Dart_SetDoubleReturnValue (
Dart_NativeArguments args ,
double retval );
Sets a double return value.
Complete Native Function Example
Simple Native Function
String Processing
Resource Management
// Native function that adds two integers
void Add (Dart_NativeArguments args ) {
// Get arguments
int64_t a, b;
Dart_Handle result;
result = Dart_GetNativeIntegerArgument (args, 0 , & a);
if ( Dart_IsError (result)) {
Dart_SetReturnValue (args, result);
return ;
}
result = Dart_GetNativeIntegerArgument (args, 1 , & b);
if ( Dart_IsError (result)) {
Dart_SetReturnValue (args, result);
return ;
}
// Set return value
Dart_SetIntegerReturnValue (args, a + b);
}
Native Resolvers
Native resolvers map Dart function names to C function pointers.
Dart_NativeEntryResolver
typedef Dart_NativeFunction ( * Dart_NativeEntryResolver )(
Dart_Handle name,
int num_of_arguments,
bool * auto_setup_scope);
Callback that resolves a native function name to a C function pointer.
Handle to a Dart string containing the function name
Number of arguments expected by the native function
Set to true if the VM should automatically setup a Dart API scope before calling the function. Most functions need this.
Returns: Function pointer or NULL if not found
Dart_SetNativeResolver
Dart_EXPORT Dart_Handle Dart_SetNativeResolver (
Dart_Handle library ,
Dart_NativeEntryResolver resolver ,
Dart_NativeEntrySymbol symbol );
Sets the native resolver for a library.
Function that resolves native function names
Optional reverse resolver for mapping function pointers to symbols (can be NULL)
Complete Extension Example
Native Extension
Dart Side
#include "dart_api.h"
#include <string.h>
// Native function implementations
void Add (Dart_NativeArguments args ) {
int64_t a, b;
Dart_GetNativeIntegerArgument (args, 0 , & a);
Dart_GetNativeIntegerArgument (args, 1 , & b);
Dart_SetIntegerReturnValue (args, a + b);
}
void Multiply (Dart_NativeArguments args ) {
int64_t a, b;
Dart_GetNativeIntegerArgument (args, 0 , & a);
Dart_GetNativeIntegerArgument (args, 1 , & b);
Dart_SetIntegerReturnValue (args, a * b);
}
void GetVersion (Dart_NativeArguments args ) {
Dart_Handle version = Dart_NewStringFromCString ( "1.0.0" );
Dart_SetReturnValue (args, version);
}
// Native resolver
Dart_NativeFunction ResolveName (Dart_Handle name ,
int argc ,
bool * auto_setup_scope ) {
// auto_setup_scope should be true for most functions
* auto_setup_scope = true ;
const char * c_name;
Dart_StringToCString (name, & c_name);
if ( strcmp (c_name, "Add" ) == 0 && argc == 2 ) {
return Add;
}
if ( strcmp (c_name, "Multiply" ) == 0 && argc == 2 ) {
return Multiply;
}
if ( strcmp (c_name, "GetVersion" ) == 0 && argc == 0 ) {
return GetVersion;
}
return NULL ; // Not found
}
// Extension initialization
Dart_EXPORT Dart_Handle my_extension_Init (Dart_Handle parent_library ) {
if ( Dart_IsError (parent_library)) {
return parent_library;
}
// Set the native resolver
Dart_Handle result = Dart_SetNativeResolver (
parent_library,
ResolveName,
NULL // No symbol resolver
);
if ( Dart_IsError (result)) {
return result;
}
return Dart_Null ();
}
Native Ports and Messaging
Native ports allow asynchronous communication between Dart and native code.
Dart_NewNativePort
Dart_EXPORT Dart_Port Dart_NewNativePort (
const char * name ,
Dart_NativeMessageHandler handler ,
bool handle_concurrently );
Creates a new native port for receiving messages.
Name of the port for debugging
handler
Dart_NativeMessageHandler
Callback function to handle incoming messages
Whether multiple messages can be processed concurrently
Returns: Port ID or ILLEGAL_PORT on error
Dart_NativeMessageHandler
typedef void ( * Dart_NativeMessageHandler)(
Dart_Port dest_port_id,
Dart_CObject * message);
Callback for handling messages received on a native port.
Dart_PostCObject
Dart_EXPORT bool Dart_PostCObject (
Dart_Port port_id ,
Dart_CObject * message );
Posts a message to a port. Can be called from any thread.
Message object graph to send
Returns: True if message was enqueued
Advanced Features
Accessing Isolate Data
Dart_EXPORT void * Dart_GetNativeIsolateGroupData (Dart_NativeArguments args );
Extracts the isolate group data from native arguments. Useful for accessing embedder-specific context.
typedef struct {
int connection_count;
void * database;
} MyAppData;
void QueryDatabase (Dart_NativeArguments args ) {
MyAppData * app_data = (MyAppData * ) Dart_GetNativeIsolateGroupData (args);
// Use app_data->database to perform query
PerformQuery ( app_data -> database );
// Update connection count
app_data -> connection_count ++ ;
}
Batch Argument Retrieval
Dart_EXPORT Dart_Handle Dart_GetNativeArguments (
Dart_NativeArguments args ,
int num_arguments ,
const Dart_NativeArgument_Descriptor * arg_descriptors ,
Dart_NativeArgument_Value * arg_values );
Retrieves multiple arguments efficiently in a single call.
void ProcessMultiple (Dart_NativeArguments args ) {
// Describe what we want to extract
const Dart_NativeArgument_Descriptor descriptors [ 3 ] = {
DART_NATIVE_ARG_DESCRIPTOR (Dart_NativeArgument_kInt64, 0 ),
DART_NATIVE_ARG_DESCRIPTOR (Dart_NativeArgument_kDouble, 1 ),
DART_NATIVE_ARG_DESCRIPTOR (Dart_NativeArgument_kBool, 2 ),
};
// Extract all at once
Dart_NativeArgument_Value values [ 3 ];
Dart_Handle result = Dart_GetNativeArguments (args, 3 , descriptors, values);
if ( ! Dart_IsError (result)) {
int64_t int_val = values [ 0 ]. as_int64 ;
double double_val = values [ 1 ]. as_double ;
bool bool_val = values [ 2 ]. as_bool ;
// Process values...
}
}
Best Practices
Always use scopes - Call Dart_EnterScope() at the start and Dart_ExitScope() at the end of native functions unless auto_setup_scope is true
Check errors - Verify return values from API calls and propagate errors appropriately
Clean up resources - Use Dart_SetReturnValue() to propagate errors while still allowing cleanup code to run
Use type-specific functions - Prefer Dart_GetNativeIntegerArgument() over Dart_GetNativeArgument() for better performance
Set auto_setup_scope correctly - Most functions need this to be true; only set false for lightweight functions
Thread safety - Native message handlers can run on any thread; ensure proper synchronization
Common Pitfalls
Memory leaks - Always delete persistent handles and free allocated memory
Scope mismanagement - Unbalanced Enter/Exit scope calls cause leaks or crashes
Invalid handles - Don’t use handles after their scope has been exited
Thread violations - Don’t call most Dart API functions from finalizers or message handlers without proper setup
See Also