> ## 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 Session Helpers for .NET Application Automation

> Use CLRSessionProxy (session.clr) to read .NET fields, invoke managed methods, and walk the CLR heap on any NSPSession attached to a .NET application.

`session.clr` is a `CLRSessionProxy` — available on every `NSPSession` that is attached to a .NET CLR application. It gives you direct access to the managed runtime: read fields by type name, invoke methods, and snapshot the heap, all without modifying the target application or writing any .NET code.

## When to Use `session.clr`

Use `session.clr` when the field or method you need is not already surfaced in `session.state`. The high-level session state (`session.get_str(...)`, `session.get_int(...)`, etc.) reflects the semantic model that the NSP probe has already extracted. `session.clr` lets you reach *below* that model — reading raw CLR fields and invoking arbitrary managed methods.

As a rule of thumb:

* **Start with `session.state`** for any value the probe already exposes.
* **Use `session.clr`** when you need runtime-specific fields, internal state, or method return values that are not in the semantic model.

<Note>
  CLR helpers require a probe confidence of at least **0.85**. If the probe is still attaching, `session.clr` calls will raise `NSPActionError` with code `clr_not_attached`. Check `session.state.runtime == RuntimeKind.CLR` and `session.confidence >= 0.85` before using CLR methods.
</Note>

## Prerequisites

```python theme={null}
from nelieo_nsp import RuntimeKind

# Guard before using CLR methods
if session.state.runtime == RuntimeKind.CLR and session.confidence >= 0.85:
    armed = await session.clr.read_field("MAVLink.MAVLinkInterface", "IsArmed")
else:
    print(f"CLR probe not ready — confidence={session.confidence:.2f}")
```

## `read_field(type_name, field_name, *, timeout_ms)`

Read the value of a single .NET field from the target process.

<ParamField path="type_name" type="str" required>
  The fully-qualified .NET type name, e.g. `"MissionPlanner.FlightData"`. Namespace-qualified names are required.
</ParamField>

<ParamField path="field_name" type="str" required>
  The field name, case-sensitive, exactly as it appears in the .NET source.
</ParamField>

<ParamField path="timeout_ms" type="int" default="10000">
  Maximum time in milliseconds to wait for the CLR bridge to respond.
</ParamField>

```python theme={null}
# Read individual fields from a .NET type
armed   = await session.clr.read_field("MAVLink.MAVLinkInterface", "IsArmed")
alt     = await session.clr.read_field("MissionPlanner.FlightData", "alt")
mode    = await session.clr.read_field("MissionPlanner.FlightData", "mode")

print(f"Armed : {armed}")   # True
print(f"Alt   : {alt:.1f}m")
print(f"Mode  : {mode}")    # "GUIDED"
```

Python types returned by `read_field()`:

| .NET Type                      | Python Type         |
| ------------------------------ | ------------------- |
| `bool`                         | `bool`              |
| `int`, `long`, `short`, `byte` | `int`               |
| `double`, `float`, `decimal`   | `float`             |
| `string`                       | `str`               |
| `DateTime`                     | `str` (ISO 8601)    |
| `Guid`                         | `str` (UUID format) |
| `null`                         | `None`              |

## `read_fields(type_name, *field_names, timeout_ms)`

Read multiple fields from the same type in a single round-trip. This is more efficient than calling `read_field()` repeatedly.

<ParamField path="type_name" type="str" required>
  Fully-qualified .NET type name.
</ParamField>

<ParamField path="*field_names" type="str" required>
  One or more field names to read.
</ParamField>

<ParamField path="timeout_ms" type="int" default="10000">
  Maximum time in milliseconds to wait for the CLR bridge.
</ParamField>

```python theme={null}
# Read six telemetry fields in one call
telemetry = await session.clr.read_fields(
    "MissionPlanner.FlightData",
    "alt", "groundspeed", "airspeed",
    "heading", "battery_voltage", "battery_remaining",
)

print(f"Alt        : {telemetry['alt']:.1f}m")
print(f"Groundspeed: {telemetry['groundspeed']:.1f}m/s")
print(f"Battery    : {telemetry['battery_voltage']:.1f}V  ({telemetry['battery_remaining']}%)")
```

## `invoke(type_name, method_name, args, *, timeout_ms)`

Invoke a .NET method on the target process and return its result. Arguments are automatically coerced to the correct .NET types based on the target method's parameter signature.

<ParamField path="type_name" type="str" required>
  Fully-qualified .NET type name.
</ParamField>

<ParamField path="method_name" type="str" required>
  Method name, case-sensitive.
</ParamField>

<ParamField path="args" type="list[Any]" default="None">
  Positional arguments. Each value is coerced to the .NET type that matches the method's parameter signature.
</ParamField>

<ParamField path="timeout_ms" type="int" default="10000">
  Maximum time to wait for the method to return.
</ParamField>

```python theme={null}
# Parameterless method
status = await session.clr.invoke("MissionPlanner.MainV2", "GetStatusText")
print(status)  # "OK: 3D GPS Fix. Satellites: 14"

# Single string argument
await session.clr.invoke("MissionPlanner.GCS", "setMode", ["GUIDED"])

# Multiple typed arguments — coerced to double, double, double
await session.clr.invoke(
    "MissionPlanner.NavController",
    "SetWaypoint",
    [34.0522, -118.2437, 50.0],
)

# Boolean argument
await session.clr.invoke("MissionPlanner.MainV2", "doArm", [True])

# Method with a return value
count = await session.clr.invoke("MissionPlanner.WaypointManager", "GetWaypointCount")
print(f"Waypoints: {count}")  # 7
```

### Type Coercion Reference

When calling `invoke()`, supply Python values and the SDK maps them to .NET types automatically:

| Python Value            | .NET Target                               |
| ----------------------- | ----------------------------------------- |
| `True` / `False`        | `bool`                                    |
| `int`                   | `int`, `long`, `short`, `byte`, `uint`, … |
| `float`                 | `double`, `float`, `decimal`              |
| `str`                   | `string`                                  |
| ISO 8601 string         | `DateTime`                                |
| UUID string             | `Guid`                                    |
| `list`                  | `T[]`, `List<T>`                          |
| `dict`                  | `Dictionary<string, object>`              |
| `None`                  | `null`                                    |
| enum member name string | Any `enum` type                           |

## `heap_snapshot(*, include_prefix, max_instances, timeout_ms)`

Walk the CLR managed heap and return instances of matching types. Each returned object includes the type name and a field-value map.

<ParamField path="include_prefix" type="str" default="&#x22;&#x22;">
  Filter objects to types whose fully-qualified name starts with this prefix.
</ParamField>

<ParamField path="max_instances" type="int" default="64">
  Maximum number of object instances to return.
</ParamField>

<ParamField path="timeout_ms" type="int" default="30000">
  Timeout for the full heap walk, in milliseconds.
</ParamField>

```python theme={null}
# Discover all MAVLink objects on the heap
objects = await session.clr.heap_snapshot(
    include_prefix="MAVLink",
    max_instances=32,
)

for obj in objects:
    print(obj["type_name"])
    for field, value in obj["fields"].items():
        print(f"  {field} = {value}")
```

<Warning>
  Heap snapshots pause the CLR garbage collector and can take 5–30 seconds depending on heap size. Use `read_field()` and `read_fields()` for real-time monitoring. Reserve `heap_snapshot()` for discovery and one-time debugging sessions.
</Warning>

## Error Reference

| Exception        | Code                   | Cause                                                      |
| ---------------- | ---------------------- | ---------------------------------------------------------- |
| `NSPActionError` | `clr_not_attached`     | CLR bridge not injected yet — probe is still attaching     |
| `NSPActionError` | `clr_type_not_found`   | `type_name` does not exist in any loaded assembly          |
| `NSPActionError` | `clr_method_not_found` | Method name or parameter signature does not match          |
| `NSPActionError` | `clr_invoke_timeout`   | Method did not return within `timeout_ms`                  |
| `NSPActionError` | `clr_mmf_error`        | Memory-mapped file channel error — session is reconnecting |

## Full Example: Mission Planner Telemetry Monitor

```python theme={null}
import asyncio
import nelieo_nsp as nsp
from nelieo_nsp import RuntimeKind

async def mission_planner_monitor():
    async with nsp.NSPClient(api_key="sk_nsp_...") as client:
        session = await client.wait_for_app("Mission Planner", timeout=30.0)
        print(f"Connected. Runtime: {session.runtime}  conf={session.confidence:.2f}")

        if session.state.runtime != RuntimeKind.CLR:
            print("Not a CLR application — exiting")
            return

        # Real-time telemetry loop
        while True:
            try:
                telem = await session.clr.read_fields(
                    "MissionPlanner.FlightData",
                    "alt", "groundspeed", "heading",
                    "battery_voltage", "battery_remaining",
                )
                armed = await session.clr.read_field(
                    "MAVLink.MAVLinkInterface", "IsArmed"
                )

                print(
                    f"Alt={telem['alt']:.1f}m  "
                    f"GS={telem['groundspeed']:.1f}m/s  "
                    f"Hdg={telem['heading']:.0f}°  "
                    f"Batt={telem['battery_remaining']}%  "
                    f"Armed={'✓' if armed else '✗'}"
                )

            except nsp.NSPActionError as e:
                print(f"CLR error [{e.code}]: {e.message}")

            await asyncio.sleep(1.0)

asyncio.run(mission_planner_monitor())
```
