Skip to main content
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.
args
Dart_NativeArguments
Native arguments structure passed to the function
index
int
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.
value
int64_t*
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.
retval
Dart_Handle
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

// 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.
name
Dart_Handle
Handle to a Dart string containing the function name
num_of_arguments
int
Number of arguments expected by the native function
auto_setup_scope
bool*
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.
library
Dart_Handle
Handle to the library
resolver
Dart_NativeEntryResolver
Function that resolves native function names
symbol
Dart_NativeEntrySymbol
Optional reverse resolver for mapping function pointers to symbols (can be NULL)

Complete Extension Example

#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
const char*
Name of the port for debugging
handler
Dart_NativeMessageHandler
Callback function to handle incoming messages
handle_concurrently
bool
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.
port_id
Dart_Port
Destination port ID
message
Dart_CObject*
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

  1. Always use scopes - Call Dart_EnterScope() at the start and Dart_ExitScope() at the end of native functions unless auto_setup_scope is true
  2. Check errors - Verify return values from API calls and propagate errors appropriately
  3. Clean up resources - Use Dart_SetReturnValue() to propagate errors while still allowing cleanup code to run
  4. Use type-specific functions - Prefer Dart_GetNativeIntegerArgument() over Dart_GetNativeArgument() for better performance
  5. Set auto_setup_scope correctly - Most functions need this to be true; only set false for lightweight functions
  6. 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

Build docs developers (and LLMs) love