Skip to main content

Application Domain

The AppDomain class lets you load assemblies from a byte stream at runtime instead of baking them into the device's program flash. Common use cases:

  • Field-updatable modules — ship a small core firmware and load feature modules from an SD card or USB drive as needed.
  • Customer-specific features — a single firmware binary that loads different feature sets depending on which .pe file is on the storage.
  • Reducing flash pressure — keep rarely-used code on external storage and load it on demand instead of consuming program flash.
  • Late binding — when you don't know until runtime which assemblies will be needed.

NuGet packages: GHIElectronics.TinyCLR.Devices.Storage and GHIElectronics.TinyCLR.IO for the file access, plus GHIElectronics.TinyCLR.Pins for pin constants.

Workflow

There are two projects involved:

  1. Main application — runs on the SITCore. Mounts a USB drive (or SD card), reads a .pe file, and loads it via AppDomain.
  2. Dynamically-loaded library — a separate TinyCLR Class Library project that builds to a Portable Executable (.pe) file. You build this once, copy the .pe to the storage device, and the main app loads it at runtime.

Main application

using GHIElectronics.TinyCLR.Devices.Storage;
using GHIElectronics.TinyCLR.IO;
using GHIElectronics.TinyCLR.Pins;
using System;
using System.IO;
using System.Reflection;

// Mount the USB mass-storage device.
var storageController = StorageController.FromName(SC20260.StorageController.UsbHostMassStorage);
var drive = FileSystem.Mount(storageController.Hdc);

var filename = drive.Name + "\\TestAppDomain.pe";

// Read the PE file into memory.
byte[] assemblyBytes;
using (var fs = new FileStream(filename, FileMode.Open)) {
assemblyBytes = new byte[fs.Length];
fs.Read(assemblyBytes, 0, assemblyBytes.Length);
}

// Load the assembly and create an instance of TestAssembly.
var assembly = Assembly.Load(assemblyBytes);
var obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap("TestAppDomain", "TestAppDomain.TestAssembly");

// Invoke PrintMessage on the loaded object.
var type = assembly.GetType("TestAppDomain.TestAssembly");
var method = type.GetMethod("PrintMessage");
method.Invoke(obj, null);

Library project

Create a separate TinyCLR Class Library project called TestAppDomain with this single class:

using System.Diagnostics;

namespace TestAppDomain {
public class TestAssembly {
public void PrintMessage() {
Debug.WriteLine("Hello from TestAssembly");
}
}
}

Build settings for the library

Right-click the TestAppDomain project in Solution Explorer, choose Properties, click TinyCLR in the left panel, and check both:

  • Generate native stubs for internal methods
  • Generate bare native stubs

These options tell Visual Studio to produce the Portable Executable (.pe) file that the main app loads at runtime.

Native stubs configuration in the project Properties window

Deploy and run

  1. Build the TestAppDomain project.
  2. Copy TestAppDomain/bin/Debug/pe/TestAppDomain.pe to a USB flash drive.
  3. Insert the drive into the SITCore.
  4. Deploy and run the main application.

Expected output:

'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'TestAppDomain'
Assembly: TestAppDomain (1.0.0.0)
Hello from TestAssembly

The first two lines come from the loader confirming the assembly was found and parsed; Hello from TestAssembly is the Debug.WriteLine call inside the dynamically-loaded code.

API reference

NamespaceDescription
GHIElectronics.TinyCLR.Devices.StorageStorage controller for mounting file systems
GHIElectronics.TinyCLR.IOFile system I/O classes