Skip to main content
Embedding the Dart VM allows you to run Dart code within your C/C++ applications. This is useful for adding scripting capabilities, running Dart as a plugin system, or building custom Dart-based tools.

Overview

Embedding the Dart VM involves:
  1. Initializing the VM with configuration and callbacks
  2. Creating and managing isolates
  3. Loading and executing Dart code
  4. Providing native function implementations
  5. Cleaning up resources on shutdown

Initialization

Setting Up VM Parameters

The Dart_InitializeParams structure configures VM initialization.
typedef struct {
  int32_t version;
  const uint8_t* vm_snapshot_data;
  const uint8_t* vm_snapshot_instructions;
  Dart_IsolateGroupCreateCallback create_group;
  Dart_InitializeIsolateCallback initialize_isolate;
  Dart_IsolateShutdownCallback shutdown_isolate;
  Dart_IsolateCleanupCallback cleanup_isolate;
  Dart_IsolateGroupCleanupCallback cleanup_group;
  Dart_ThreadStartCallback thread_start;
  Dart_ThreadExitCallback thread_exit;
  Dart_FileOpenCallback file_open;
  Dart_FileReadCallback file_read;
  Dart_FileWriteCallback file_write;
  Dart_FileCloseCallback file_close;
  Dart_EntropySource entropy_source;
  Dart_GetVMServiceAssetsArchive get_service_assets;
  bool start_kernel_isolate;
  Dart_CodeObserver* code_observer;
} Dart_InitializeParams;
version
int32_t
Must be set to DART_INITIALIZE_PARAMS_CURRENT_VERSION
vm_snapshot_data
const uint8_t*
Buffer containing VM snapshot data or NULL. Must remain valid until Dart_Cleanup().
vm_snapshot_instructions
const uint8_t*
Buffer containing snapshot instructions or NULL. Must remain valid until Dart_Cleanup().
create_group
Dart_IsolateGroupCreateCallback
Required callback for creating new isolate groups
shutdown_isolate
Dart_IsolateShutdownCallback
Optional callback invoked before isolate shutdown (can run Dart code)
cleanup_isolate
Dart_IsolateCleanupCallback
Optional callback invoked after isolate shutdown (cannot run Dart code)

Basic Initialization Example

#include "dart_api.h"
#include <stdio.h>
#include <stdlib.h>

// Isolate creation callback
Dart_Isolate CreateIsolateGroupCallback(
    const char* script_uri,
    const char* main,
    const char* package_root,
    const char* package_config,
    Dart_IsolateFlags* flags,
    void* isolate_data,
    char** error) {
  
  // Create the isolate
  Dart_Isolate isolate = Dart_CreateIsolateGroupFromKernel(
      script_uri,
      main,
      kernel_buffer,        // Your kernel binary
      kernel_buffer_size,
      flags,
      isolate_data,
      isolate_data,
      error
  );
  
  if (isolate == NULL) {
    return NULL;
  }
  
  // Setup the isolate
  Dart_EnterScope();
  
  // Set up native resolvers, load libraries, etc.
  Dart_Handle library = Dart_RootLibrary();
  // ... setup code ...
  
  Dart_ExitScope();
  
  return isolate;
}

int main(int argc, char** argv) {
  // Initialize VM parameters
  Dart_InitializeParams params = {};
  params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
  params.vm_snapshot_data = vm_snapshot_data;
  params.vm_snapshot_instructions = vm_snapshot_instructions;
  params.create_group = CreateIsolateGroupCallback;
  
  // Initialize the VM
  char* error = Dart_Initialize(&params);
  if (error != NULL) {
    fprintf(stderr, "VM initialization failed: %s\n", error);
    free(error);
    return 1;
  }
  
  printf("Dart VM initialized successfully\n");
  
  // ... use the VM ...
  
  // Cleanup
  error = Dart_Cleanup();
  if (error != NULL) {
    fprintf(stderr, "VM cleanup failed: %s\n", error);
    free(error);
    return 1;
  }
  
  return 0;
}

Isolate Lifecycle Callbacks

Dart_IsolateGroupCreateCallback

typedef Dart_Isolate (*Dart_IsolateGroupCreateCallback)(
    const char* script_uri,
    const char* main,
    const char* package_root,
    const char* package_config,
    Dart_IsolateFlags* flags,
    void* isolate_data,
    char** error);
Called by the VM when a new isolate group needs to be created.
Must call Dart_CreateIsolateGroup() or Dart_CreateIsolateGroupFromKernel() and load the program. If returning NULL, must set *error to a malloc-allocated error message.

Dart_InitializeIsolateCallback

typedef bool (*Dart_InitializeIsolateCallback)(
    void** child_isolate_data,
    char** error);
Called when the VM creates an isolate within an existing isolate group.

Dart_IsolateShutdownCallback

typedef void (*Dart_IsolateShutdownCallback)(
    void* isolate_group_data,
    void* isolate_data);
Called before the VM shuts down an isolate. Safe to run Dart code.

Dart_IsolateCleanupCallback

typedef void (*Dart_IsolateCleanupCallback)(
    void* isolate_group_data,
    void* isolate_data);
Called after the VM shuts down an isolate. Cannot run Dart code.

Creating and Running Isolates

Complete Isolate Creation

#include "dart_api.h"

typedef struct {
  const uint8_t* kernel_buffer;
  intptr_t kernel_buffer_size;
} AppData;

Dart_Isolate CreateAndSetupIsolate(
    const char* script_uri,
    const char* main,
    AppData* app_data,
    char** error) {
  
  // Initialize isolate flags
  Dart_IsolateFlags flags;
  Dart_IsolateFlagsInitialize(&flags);
  flags.null_safety = true;
  
  // Create isolate from kernel
  Dart_Isolate isolate = Dart_CreateIsolateGroupFromKernel(
      script_uri,
      main,
      app_data->kernel_buffer,
      app_data->kernel_buffer_size,
      &flags,
      app_data,  // isolate group data
      app_data,  // isolate data
      error
  );
  
  if (isolate == NULL) {
    return NULL;
  }
  
  // Enter the isolate to set it up
  Dart_EnterScope();
  
  // Get root library
  Dart_Handle library = Dart_RootLibrary();
  if (Dart_IsError(library)) {
    *error = strdup(Dart_GetError(library));
    Dart_ExitScope();
    Dart_ShutdownIsolate();
    return NULL;
  }
  
  // Set up native resolver
  Dart_Handle result = Dart_SetNativeResolver(
      library,
      MyNativeResolver,
      NULL
  );
  
  if (Dart_IsError(result)) {
    *error = strdup(Dart_GetError(result));
    Dart_ExitScope();
    Dart_ShutdownIsolate();
    return NULL;
  }
  
  Dart_ExitScope();
  
  return isolate;
}

// Run main() function
bool RunMain(Dart_Isolate isolate) {
  Dart_EnterIsolate(isolate);
  Dart_EnterScope();
  
  // Get root library
  Dart_Handle library = Dart_RootLibrary();
  
  // Invoke main()
  Dart_Handle main_name = Dart_NewStringFromCString("main");
  Dart_Handle result = Dart_Invoke(library, main_name, 0, NULL);
  
  bool success = !Dart_IsError(result);
  if (!success) {
    fprintf(stderr, "Error running main: %s\n", Dart_GetError(result));
  }
  
  Dart_ExitScope();
  Dart_ExitIsolate();
  
  return success;
}

Loading Dart Code

Kernel files (.dill) are the recommended format for loading Dart code in embedded scenarios.
#include <stdio.h>
#include <stdlib.h>

bool LoadKernelFile(const char* path,
                    uint8_t** buffer,
                    intptr_t* size) {
  FILE* file = fopen(path, "rb");
  if (file == NULL) {
    return false;
  }
  
  // Get file size
  fseek(file, 0, SEEK_END);
  *size = ftell(file);
  fseek(file, 0, SEEK_SET);
  
  // Allocate buffer
  *buffer = (uint8_t*)malloc(*size);
  if (*buffer == NULL) {
    fclose(file);
    return false;
  }
  
  // Read file
  size_t read = fread(*buffer, 1, *size, file);
  fclose(file);
  
  return read == *size;
}

// Usage
uint8_t* kernel_buffer;
intptr_t kernel_size;

if (!LoadKernelFile("app.dill", &kernel_buffer, &kernel_size)) {
  fprintf(stderr, "Failed to load kernel file\n");
  return 1;
}

// Create isolate with kernel
Dart_Isolate isolate = Dart_CreateIsolateGroupFromKernel(
    "app.dill",
    "main",
    kernel_buffer,
    kernel_size,
    &flags,
    app_data,
    app_data,
    &error
);

From Snapshots

Snapshots provide faster startup for AOT-compiled code.
// Load snapshot files
uint8_t* isolate_snapshot_data;
uint8_t* isolate_snapshot_instructions;

LoadSnapshot("isolate_snapshot_data", &isolate_snapshot_data);
LoadSnapshot("isolate_snapshot_instructions", &isolate_snapshot_instructions);

// Create isolate from snapshot
Dart_Isolate isolate = Dart_CreateIsolateGroup(
    "myapp",
    "main",
    isolate_snapshot_data,
    isolate_snapshot_instructions,
    &flags,
    app_data,
    app_data,
    &error
);

Calling Dart from C

Invoking Functions

void CallDartFunction(Dart_Isolate isolate) {
  Dart_EnterIsolate(isolate);
  Dart_EnterScope();
  
  // Get the library
  Dart_Handle library = Dart_RootLibrary();
  
  // Prepare arguments
  Dart_Handle args[2];
  args[0] = Dart_NewInteger(42);
  args[1] = Dart_NewStringFromCString("hello");
  
  // Invoke the function
  Dart_Handle function_name = Dart_NewStringFromCString("processData");
  Dart_Handle result = Dart_Invoke(library, function_name, 2, args);
  
  if (Dart_IsError(result)) {
    fprintf(stderr, "Error: %s\n", Dart_GetError(result));
  } else {
    // Process result
    if (Dart_IsInteger(result)) {
      int64_t value;
      Dart_IntegerToInt64(result, &value);
      printf("Result: %lld\n", value);
    }
  }
  
  Dart_ExitScope();
  Dart_ExitIsolate();
}

Accessing Fields

void ManipulateFields(Dart_Handle object) {
  Dart_EnterScope();
  
  // Get field value
  Dart_Handle name_field = Dart_NewStringFromCString("name");
  Dart_Handle name_value = Dart_GetField(object, name_field);
  
  if (!Dart_IsError(name_value)) {
    const char* name;
    Dart_StringToCString(name_value, &name);
    printf("Name: %s\n", name);
  }
  
  // Set field value
  Dart_Handle count_field = Dart_NewStringFromCString("count");
  Dart_Handle new_value = Dart_NewInteger(100);
  Dart_Handle result = Dart_SetField(object, count_field, new_value);
  
  if (Dart_IsError(result)) {
    fprintf(stderr, "Error setting field: %s\n", Dart_GetError(result));
  }
  
  Dart_ExitScope();
}

Complete Embedding Example

#include "dart_api.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Application data
typedef struct {
  uint8_t* kernel_buffer;
  intptr_t kernel_size;
  int request_count;
} AppData;

// Native function: getRequestCount
void GetRequestCount(Dart_NativeArguments args) {
  AppData* app_data = (AppData*)Dart_GetNativeIsolateGroupData(args);
  Dart_SetIntegerReturnValue(args, app_data->request_count);
}

// Native function: incrementRequestCount
void IncrementRequestCount(Dart_NativeArguments args) {
  AppData* app_data = (AppData*)Dart_GetNativeIsolateGroupData(args);
  app_data->request_count++;
}

// Native resolver
Dart_NativeFunction ResolveName(Dart_Handle name,
                                int argc,
                                bool* auto_setup_scope) {
  *auto_setup_scope = true;
  const char* c_name;
  Dart_StringToCString(name, &c_name);
  
  if (strcmp(c_name, "GetRequestCount") == 0) return GetRequestCount;
  if (strcmp(c_name, "IncrementRequestCount") == 0) return IncrementRequestCount;
  
  return NULL;
}

// Isolate creation callback
Dart_Isolate CreateIsolateGroup(
    const char* script_uri,
    const char* main,
    const char* package_root,
    const char* package_config,
    Dart_IsolateFlags* flags,
    void* isolate_data,
    char** error) {
  
  AppData* app_data = (AppData*)isolate_data;
  
  Dart_Isolate isolate = Dart_CreateIsolateGroupFromKernel(
      script_uri,
      main,
      app_data->kernel_buffer,
      app_data->kernel_size,
      flags,
      app_data,
      app_data,
      error
  );
  
  if (isolate == NULL) return NULL;
  
  Dart_EnterScope();
  
  Dart_Handle library = Dart_RootLibrary();
  Dart_SetNativeResolver(library, ResolveName, NULL);
  
  Dart_ExitScope();
  
  return isolate;
}

// Cleanup callback
void CleanupIsolate(void* isolate_group_data, void* isolate_data) {
  AppData* app_data = (AppData*)isolate_group_data;
  printf("Total requests processed: %d\n", app_data->request_count);
}

int main(int argc, char** argv) {
  // Load kernel file
  AppData app_data = {0};
  if (!LoadKernelFile("app.dill", &app_data.kernel_buffer, &app_data.kernel_size)) {
    fprintf(stderr, "Failed to load kernel\n");
    return 1;
  }
  
  // Initialize VM
  Dart_InitializeParams params = {};
  params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
  params.create_group = CreateIsolateGroup;
  params.cleanup_isolate = CleanupIsolate;
  
  char* error = Dart_Initialize(&params);
  if (error) {
    fprintf(stderr, "Init failed: %s\n", error);
    free(error);
    return 1;
  }
  
  // Create isolate
  Dart_IsolateFlags flags;
  Dart_IsolateFlagsInitialize(&flags);
  
  Dart_Isolate isolate = Dart_CreateIsolateGroupFromKernel(
      "app.dill",
      "main",
      app_data.kernel_buffer,
      app_data.kernel_size,
      &flags,
      &app_data,
      &app_data,
      &error
  );
  
  if (isolate == NULL) {
    fprintf(stderr, "Failed to create isolate: %s\n", error);
    free(error);
    Dart_Cleanup();
    return 1;
  }
  
  // Run main()
  Dart_EnterScope();
  Dart_Handle lib = Dart_RootLibrary();
  Dart_Handle main_name = Dart_NewStringFromCString("main");
  Dart_Handle result = Dart_Invoke(lib, main_name, 0, NULL);
  
  if (Dart_IsError(result)) {
    fprintf(stderr, "Error: %s\n", Dart_GetError(result));
  }
  
  Dart_ExitScope();
  
  // Cleanup
  Dart_ShutdownIsolate();
  Dart_Cleanup();
  free(app_data.kernel_buffer);
  
  return 0;
}

File I/O Callbacks

Provide file I/O callbacks if your Dart code needs file access.
void* FileOpen(const char* name, bool write) {
  return fopen(name, write ? "wb" : "rb");
}

void FileRead(uint8_t** data, intptr_t* file_length, void* stream) {
  FILE* file = (FILE*)stream;
  
  fseek(file, 0, SEEK_END);
  *file_length = ftell(file);
  fseek(file, 0, SEEK_SET);
  
  *data = (uint8_t*)malloc(*file_length);
  if (*data == NULL) {
    *file_length = -1;
    return;
  }
  
  size_t read = fread(*data, 1, *file_length, file);
  if (read != *file_length) {
    free(*data);
    *data = NULL;
    *file_length = -1;
  }
}

void FileWrite(const void* data, intptr_t length, void* stream) {
  FILE* file = (FILE*)stream;
  fwrite(data, 1, length, file);
}

void FileClose(void* stream) {
  fclose((FILE*)stream);
}

// Set in params
params.file_open = FileOpen;
params.file_read = FileRead;
params.file_write = FileWrite;
params.file_close = FileClose;

Best Practices

  1. Snapshot Management - Keep snapshot buffers valid for the lifetime of isolates using them
  2. Error Handling - Always check return values and handle errors appropriately
  3. Resource Cleanup - Use cleanup callbacks to free embedder-allocated resources
  4. Thread Safety - Only one thread can be in an isolate at a time; use proper synchronization
  5. Scope Management - Balance Dart_EnterScope()/Dart_ExitScope() calls
  6. Isolate Data - Use isolate_group_data for shared context, isolate_data for per-isolate context

Common Patterns

Event Loop Integration

void MessageNotifyCallback(Dart_Isolate isolate) {
  // Called when isolate has messages
  // Integrate with your event loop
  ScheduleIsolateProcessing(isolate);
}

Dart_SetMessageNotifyCallback(isolate, MessageNotifyCallback);

Multi-Isolate Management

typedef struct {
  Dart_Isolate isolate;
  pthread_t thread;
  bool running;
} IsolateContext;

void* IsolateThread(void* arg) {
  IsolateContext* ctx = (IsolateContext*)arg;
  
  Dart_EnterIsolate(ctx->isolate);
  
  while (ctx->running) {
    // Process messages
    Dart_RunLoop();
  }
  
  Dart_ExitIsolate();
  return NULL;
}

See Also

Build docs developers (and LLMs) love