> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nelieo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CLR / .NET Probe: Managed Heap Walk for .NET Applications

> How NSP injects a C# bootstrapper via the CLR Hosting API and reads .NET app state over a Memory-Mapped File bridge — no code changes required.

The CLR probe attaches to any running .NET application — including WinForms, WPF, Mission Planner, Visual Studio, and Microsoft Office — and reads its live heap state via a managed C# bootstrapper injected through the Windows CLR Hosting API. Once injected, the bootstrapper communicates with `axon-daemon` over a zero-copy Memory-Mapped File (MMF) channel, walking the CLR heap using managed reflection to produce a fully-typed `SubstrateState`. You do not need to deploy any DLL, modify any project file, or make any code changes to the target application.

***

## Architecture

The CLR probe uses a two-process architecture: the native `axon-daemon` on one side, and a managed C# bootstrapper running inside the target application's AppDomain on the other. They communicate over a named MMF channel.

```text theme={null}
axon-daemon (native Rust)
  │
  ├─ Injects: axon_clr_bridge.dll (managed C# assembly)
  │   via Windows CLR Hosting API (ICLRRuntimeHost)
  │
  ├─ Memory-Mapped File: \\axon-clr-{pid}   (one channel per target process)
  │   ┌──────────────────────────────────────────────────┐
  │   │ Command:  { action, type_name, method, args }    │
  │   │ Response: { success, return_value, fields }      │
  │   └──────────────────────────────────────────────────┘
  │
  └── Target .NET Process
        └─ axon_clr_bridge.dll (running in target AppDomain)
              ├─ CLR Heap Walk  (Assembly.GetTypes() + reflection)
              ├─ Method Invoke  (MethodInfo.Invoke())
              └─ Field Read     (FieldInfo.GetValue())
```

**Why MMF instead of named pipes?** MMF communication is synchronous and zero-copy. The managed bridge writes the response directly into the shared memory region — no serialization overhead and no buffering latency. Round-trip latency for a typical field read is under 5ms.

***

## Injection: No Code Changes Required

NSP uses the Windows CLR Hosting API (`ICLRRuntimeHost`) to load `axon_clr_bridge.dll` into an already-running process. From your perspective as an operator:

* You do not deploy `axon_clr_bridge.dll` anywhere — the daemon carries it internally.
* You do not add any reference, NuGet package, or startup hook to the target project.
* You do not restart the application — injection happens into the live, running process.
* You do not need the application's source code or PDB files.

<Note>
  Injection via the CLR Hosting API requires that `axon-daemon` is running with sufficient privileges to open a handle to the target process. On Windows, this typically means running the daemon as Administrator or as the same user that launched the .NET application.
</Note>

***

## How CLR Heap Walk Works

After injection, the managed bootstrapper uses `ICorProfiler`-style reflection to walk the CLR heap:

<Steps>
  <Step title="Type enumeration">
    The bridge calls `AppDomain.CurrentDomain.GetAssemblies()` and iterates all loaded assemblies to enumerate every live type in the process.
  </Step>

  <Step title="Instance discovery">
    For each type of interest, the bridge locates live instances via GC root traversal and static field inspection.
  </Step>

  <Step title="Field extraction">
    `FieldInfo.GetValue()` reads the current value of each field on each live instance, including nested objects, collections, and JIT-compiled value types.
  </Step>

  <Step title="Normalization">
    Field values are serialized to SSF dot-notation keys (e.g. `mavlink.interface.is_armed`, `flight_data.altitude`) and written to the MMF response buffer for the daemon to consume.
  </Step>
</Steps>

***

## Supported Applications

The CLR probe supports all .NET 4.x and .NET 6+ applications, including both WinForms and WPF desktop applications. The following have validated signature entries:

| Application          | .NET Version  | Notable State                              |
| -------------------- | ------------- | ------------------------------------------ |
| Mission Planner      | .NET 4.8      | MAVLink telemetry, waypoints, arm state    |
| Visual Studio 2022   | .NET 4.8      | Project, solution, open files, diagnostics |
| WinForms apps        | .NET 4.x / 6+ | Any managed Windows Forms desktop app      |
| WPF apps             | .NET 4.x / 6+ | Any managed WPF desktop app                |
| Microsoft Excel      | .NET interop  | Workbook, sheet, cell, formula state       |
| Microsoft Word       | .NET interop  | Document, selection, track-changes state   |
| Microsoft PowerPoint | .NET interop  | Presentation, slide, shape state           |

<Info>
  For Microsoft Office applications, state is read via the .NET COM Interop layer (VSTO / Office Interop). The CLR probe connects to the managed interop process — Office itself remains a native COM server, but its object model is fully accessible through the managed wrapper.
</Info>

***

## Confidence Baseline

The CLR probe achieves high confidence because .NET retains rich reflection metadata at runtime — assembly names, type names, field names, and method signatures are available even in release builds.

| Scenario                                       | Confidence Range |
| ---------------------------------------------- | ---------------- |
| Cold probe (first attachment)                  | 0.93–0.97        |
| After schema cache populated                   | 0.95–0.98        |
| Obfuscated assembly (Dotfuscator / ConfuserEx) | 0.65–0.82        |
| Native AOT compiled (.NET 8+)                  | 0.50–0.70        |

| Confidence                | Action Gating                               |
| ------------------------- | ------------------------------------------- |
| 0.95–0.98 (cached schema) | All actions, including `irreversible_write` |
| 0.93–0.97 (cold probe)    | `reversible_write` and `read` actions       |
| 0.65–0.82 (obfuscated)    | `read` actions only                         |

***

## Reading State via the Python SDK

<Tabs>
  <Tab title="Standard State API">
    ```python theme={null}
    import asyncio
    from nelieo_nsp import NSPClient

    async def main():
        async with NSPClient() as client:
            session = await client.session("mission-planner-12847")
            state = await session.state()

            armed = state.get_bool("mavlink.interface.is_armed")
            alt   = state.get_float("flight_data.altitude")
            mode  = state.get_str("flight_data.flight_mode")

            print(f"Armed: {armed} | Alt: {alt:.1f}m | Mode: {mode}")

    asyncio.run(main())
    ```
  </Tab>

  <Tab title="CLR Session Proxy">
    ```python theme={null}
    async def main():
        async with NSPClient() as client:
            session = await client.session("mission-planner-12847")

            # Read a single field directly
            armed = await session.clr.read_field(
                "MAVLink.MAVLinkInterface", "IsArmed"
            )
            alt = await session.clr.read_field(
                "MissionPlanner.FlightData", "alt"
            )
            print(f"Armed: {armed} | Alt: {alt:.1f}m")
    ```
  </Tab>

  <Tab title="Multiple Fields">
    ```python theme={null}
    async def main():
        async with NSPClient() as client:
            session = await client.session("mission-planner-12847")

            vals = await session.clr.read_fields(
                "MissionPlanner.FlightData",
                "alt", "groundspeed", "battery_voltage", "heading"
            )
            print(
                f"Alt={vals['alt']:.1f}m  "
                f"GS={vals['groundspeed']:.1f}m/s  "
                f"Bat={vals['battery_voltage']:.2f}V"
            )
    ```
  </Tab>
</Tabs>

***

## Invoking .NET Methods

The managed bridge supports full method invocation with automatic Python-to-.NET type coercion:

```python theme={null}
# Parameterless method
status = await session.clr.invoke("MissionPlanner.MainV2", "GetStatusText")

# Set a waypoint — Python floats auto-coerced to .NET double
await session.clr.invoke(
    "MissionPlanner.NavController",
    "SetWaypoint",
    [34.052, -118.243, 150.0],  # lat, lon, alt
)

# String argument — auto-coerced to System.String
await session.clr.invoke(
    "MissionPlanner.GCS",
    "SetMode",
    ["GUIDED"],
)
```

### Type Coercion Reference

| Python Type              | .NET Target Types                                               |
| ------------------------ | --------------------------------------------------------------- |
| `bool`                   | `bool`, `Boolean`                                               |
| `int`                    | `int`, `long`, `short`, `byte`, `uint`, and other integer types |
| `float`                  | `double`, `float`, `decimal`                                    |
| `str`                    | `string`, `String`                                              |
| `str` (GUID format)      | `Guid`                                                          |
| `str` (ISO 8601)         | `DateTime`, `DateTimeOffset`                                    |
| `list`                   | `T[]`, `List<T>`, `IEnumerable<T>`                              |
| `dict`                   | `Dictionary<string, T>`                                         |
| `None`                   | `null`                                                          |
| `str` (enum member name) | Any `enum` type                                                 |

***

## Error Reference

| Error Code             | Description                               | Recovery                                               |
| ---------------------- | ----------------------------------------- | ------------------------------------------------------ |
| `clr_not_attached`     | Bridge not injected yet                   | Wait for `probe_status: "active"` (5–30s)              |
| `clr_type_not_found`   | `type_name` does not exist in target      | Verify the exact fully-qualified type name             |
| `clr_method_not_found` | Method name or signature mismatch         | Check method name and argument types                   |
| `clr_invoke_timeout`   | Method did not return within `timeout_ms` | Increase `timeout_ms` or check for UI-thread deadlocks |
| `clr_mmf_error`        | MMF channel is broken                     | The session will re-attach automatically               |

<AccordionGroup>
  <Accordion title="Does the CLR probe work with .NET 8 Native AOT?">
    Native AOT compilation strips most reflection metadata from the output binary. The CLR probe can still read process memory, but confidence will be lower (0.50–0.70) because type and field names may not be available. Use the probe in read-only mode for Native AOT targets until the schema is enriched.
  </Accordion>

  <Accordion title="Can I probe both 32-bit and 64-bit .NET processes?">
    Yes. NSP's CLR probe ships both x86 and x64 builds of `axon_clr_bridge.dll`. The daemon selects the correct architecture automatically based on the target process bitness.
  </Accordion>

  <Accordion title="Does this work with WPF data bindings?">
    Yes. WPF ViewModels that implement `INotifyPropertyChanged` are especially well-covered because their property names are preserved as string literals in the compiled assembly. The probe reads the backing fields directly and maps them to the declared property names.
  </Accordion>
</AccordionGroup>
