You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
> ❌ **NEVER** use `string.Empty` — use `""`. Never use `Array.Empty<T>()` — use `[]`.
96
96
97
+
> ❌ **NEVER** use non-blittable types (`bool`, `char`) as backing fields in structs. Use `byte` (for `bool`) and `ushort`/`short` (for `char`) with property accessors. See [references/binding-patterns.md](references/binding-patterns.md) for the correct pattern.
98
+
99
+
> ❌ **NEVER** use `XAMCORE_5_0` for new code. `XAMCORE_5_0` is only for fixing breaking API changes on existing types that shipped in prior releases.
100
+
101
+
> ❌ **NEVER** use `#pragma warning disable 0169` for struct fields. Instead, wrap public methods and properties inside `#if !COREBUILD` (but NOT fields — bgen needs to know the struct size).
102
+
97
103
> ⚠️ Place a space before parentheses and brackets: `Foo ()`, `Bar (1, 2)`, `myarray [0]`.
98
104
105
+
> ⚠️ Method names should follow .NET naming conventions — use verb-based names, not direct Objective-C selector translations (e.g., `BuildMenu` not `MenuWithContents`).
106
+
99
107
> ⚠️ For in depth binding patterns and conventions See [references/binding-patterns.md](references/binding-patterns.md)
100
108
109
+
### Step 4b: Platform Exclusion Patterns for Manual Types
110
+
111
+
When a manually coded type (struct, extension, etc.) is not available on a specific platform (e.g., tvOS), you must handle compilation on that platform:
112
+
113
+
1. In the manual code file (`src/FrameworkName/MyStruct.cs`), wrap the struct body with `#if !TVOS`
114
+
2. Add `[UnsupportedOSPlatform ("tvos")]` on the struct
115
+
3. In the API definition file (`src/frameworkname.cs`), add a type alias at the top so compilation succeeds:
116
+
117
+
```csharp
118
+
#ifTVOS
119
+
usingMyStruct=Foundation.NSObject;
120
+
#endif
121
+
```
122
+
123
+
The `[NoTV]` attribute on the API definition interface ensures the type won't appear in the final tvOS assembly, while the alias prevents compilation errors from method signatures that reference the struct.
124
+
101
125
### Step 5: Build
102
126
103
127
```bash
@@ -106,6 +130,8 @@ make -C src build
106
130
107
131
Fix any compilation errors before proceeding. Builds can take up to 60 minutes — do not timeout early.
108
132
133
+
> ⚠️ **Stale build artifacts**: If you encounter unexpected test failures (false "pre-existing" failures, segfaults in unrelated types), run a full `make all && make install` to clear stale `_build/` artifacts before re-testing.
134
+
109
135
### Step 6: Validate with Tests
110
136
111
137
Run all three test suites. **Run them sequentially, not in parallel.**
@@ -132,26 +158,49 @@ make -C tests/cecil-tests run-tests
132
158
**IMPORTANT:** Clean shared obj directories before each platform to avoid NETSDK1005 errors:
133
159
134
160
```bash
135
-
# iOS
161
+
# iOS — build, then run via mlaunch directly for reliable output capture
make -C tests/introspection/dotnet clean-MacCatalyst build-MacCatalyst run-bare-MacCatalyst
193
+
make -C tests/introspection/dotnet/MacCatalyst clean build
194
+
make -C tests/introspection/dotnet/MacCatalyst run-bare
150
195
```
151
196
152
-
> ⚠️ **macOS/MacCatalyst:** Use `make run-bare` (not `make run`) — `make run` launches the app without waiting or capturing stdout. `run-bare` runs the executable directly to capture test output.
197
+
> ⚠️ **iOS/tvOS output capture:**`make run-ios`/`run-tvos` uses `dotnet build -t:Run` which does NOT reliably capture the app's stdout. The `com.apple.gamed` stderr message causes MSBuild to report failure (exit code -1) even when tests pass, and NUnit results are lost. Use **mlaunch directly** as shown above to capture test output reliably.
198
+
199
+
> ⚠️ **mlaunch device strings:** Use `xcrun simctl list runtimes` and `xcrun simctl list devicetypes` to find the correct runtime and device type identifiers for your Xcode version. The `--device` format is `:v2:runtime=<runtime-id>,devicetype=<devicetype-id>`.
200
+
201
+
> ⚠️ **`clean` and `run-bare` must be run from the platform subdirectory** (e.g., `tests/introspection/dotnet/macOS/`), not from the parent `dotnet/` directory. The parent only has `build-%` and `run-%` pattern rules — there are no `clean-%` or `run-bare-%` targets.
153
202
154
-
> ⚠️ **iOS/tvOS:** Use `make run` (not `make run-bare`) — these require simulator infrastructure that `run-bare`doesn't provide.
203
+
> ⚠️ **macOS/MacCatalyst:** Use `run-bare` (not `run`) — `run` launches the app without waiting or capturing stdout. `run-bare`runs the executable directly to capture test output.
155
204
156
205
Look for this pattern in test output to confirm results:
157
206
```
@@ -164,6 +213,8 @@ If introspection tests fail for newly bound types:
164
213
- Check if the type crashes on simulator (common for hardware-dependent APIs)
165
214
- Add exclusions in the platform-specific `ApiCtorInitTest.cs` files if needed
166
215
- Types that crash on init, dispose, or toString need specific exclusion entries
216
+
-**NEVER skip an entire namespace** — always add exclusions for specific types only
217
+
-**If a `[DesignatedInitializer]` constructor crashes (segfault) when passed null**, the correct fix is to **remove `[NullAllowed]` from that parameter** rather than adding introspection test exclusions. The null is genuinely not allowed by the native API.
167
218
168
219
If xtro still shows unresolved entries:
169
220
- Some APIs may be platform-specific (only available on device, not simulator)
// C Struct — prefer private fields + public properties for easier layout fixes later
272
+
// C Struct — use byte backing fields for bools to keep struct blittable
273
273
[StructLayout (LayoutKind.Sequential)]
274
274
publicstructMyStruct {
275
-
nfloatx;
276
-
nfloaty;
277
-
278
-
publicnfloatX { get=>x; set=>x=value; }
279
-
publicnfloatY { get=>y; set=>y=value; }
275
+
byteenabled;
276
+
nfloatx;
277
+
nfloaty;
278
+
279
+
#if!COREBUILD
280
+
publicboolEnabled {
281
+
get=>enabled!=0;
282
+
set=>enabled=value? (byte) 1: (byte) 0;
283
+
}
284
+
285
+
publicnfloatX { get=>x; set=>x=value; }
286
+
publicnfloatY { get=>y; set=>y=value; }
287
+
#endif
280
288
}
281
289
282
290
// Global constant
283
291
[Field ("kMyConstant", "MyFramework")]
284
292
publicstaticNSStringMyConstant { get; }
285
293
```
286
294
295
+
### Struct Binding Rules
296
+
297
+
-**Only use blittable types as backing fields in structs.**`bool` and `char` aren't blittable — use `byte` and `ushort`/`short` instead. This avoids `[MarshalAs]` and cecil test known failures.
298
+
-**Wrap all public methods and properties in `#if !COREBUILD`** — never use `#pragma warning disable 0169`. Do NOT wrap fields, because bgen may do different things depending on the size of a struct, so it needs to know the final size.
299
+
-**NEVER use `XAMCORE_5_0` for new code.**`XAMCORE_5_0` is only for fixing breaking API changes on existing types that shipped in prior releases.
300
+
- If a struct member is platform-specific, use `#if !TVOS` (or similar) to exclude it.
301
+
302
+
### Platform Exclusion for Manual Types
303
+
304
+
When a manual type (struct, helper class) is not available on tvOS:
305
+
306
+
```csharp
307
+
// In src/FrameworkName/MyStruct.cs:
308
+
#if!TVOS
309
+
[UnsupportedOSPlatform ("tvos")]
310
+
[StructLayout (LayoutKind.Sequential)]
311
+
publicstructMyStruct {
312
+
byteenabled;
313
+
314
+
#if!COREBUILD
315
+
publicboolEnabled {
316
+
get=>enabled!=0;
317
+
set=>enabled=value? (byte) 1: (byte) 0;
318
+
}
319
+
#endif// !COREBUILD
320
+
}
321
+
#endif// !TVOS
322
+
323
+
// In src/frameworkname.cs (at the top of the file):
324
+
#ifTVOS
325
+
usingMyStruct=Foundation.NSObject;
326
+
#endif
327
+
```
328
+
329
+
The type alias lets tvOS compilation succeed. The `[NoTV]` attribute on the API definition interface ensures the type won't appear in the final tvOS assembly.
330
+
287
331
## Strongly-Typed Dictionaries
288
332
289
333
```csharp
@@ -394,12 +438,15 @@ All `[Verify]` attributes must be resolved before submitting a PR.
394
438
395
439
## Common Pitfalls
396
440
397
-
-**Null handling**: Always use `[NullAllowed]` where Apple's docs indicate nullability. Default assumption is non-null.
441
+
-**Null handling**: Always use `[NullAllowed]` where Apple's docs indicate nullability. Default assumption is non-null. However, if a `[DesignatedInitializer]` constructor crashes (segfault) when passed null, **remove `[NullAllowed]`** — the native API genuinely doesn't accept null, and removing it is better than adding introspection test exclusions.
442
+
-**Struct backing fields**: Only use blittable types. `bool` and `char` aren't blittable — use `byte` and `ushort`/`short` instead, with typed property accessors.
398
443
-**Threading**: UI APIs require main thread. Use `[ThreadSafe]` for thread-safe APIs.
399
-
-**Naming**: Follow .NET PascalCase for methods/properties. Remove redundant ObjC prefixes (`NSString name` → `string Name`). Acronyms shouldn't be all uppercase (SIMD → Simd, ID → Id when it means "identifier", URL → Url). Methods should be verbs, properties should be nouns.
444
+
-**Naming**: Follow .NET PascalCase for methods/properties. Remove redundant ObjC prefixes (`NSString name` → `string Name`). Acronyms shouldn't be all uppercase (SIMD → Simd, ID → Id when it means "identifier", URL → Url). Methods should be verbs, properties should be nouns. Don't blindly translate ObjC selector names — use .NET-appropriate verb names (e.g., `BuildMenu` not `MenuWithContents`).
400
445
-**Selectors**: Must match exactly — a single typo causes runtime crashes.
401
446
-**Protocol conformance**: All `[Abstract]` methods in a protocol are required.
402
447
-**nint/nuint**: Use `nint`/`nuint` for Objective-C `NSInteger`/`NSUInteger`.
448
+
-**XAMCORE_5_0**: Only for fixing breaking changes on existing shipped types. Never use for new code.
449
+
-**Struct members**: Wrap public methods and properties in `#if !COREBUILD`, but NOT fields (bgen needs struct size). Never use `#pragma warning disable 0169`.
| macOS |`make -C tests/introspection/dotnet build-macOS`|`make run-bare-macOS`| Direct execution |
62
-
| MacCatalyst |`make -C tests/introspection/dotnet build-MacCatalyst`|`make run-bare-MacCatalyst`| Direct execution |
57
+
**Important:**`clean` and `run-bare` must be run from the **platform subdirectory** (e.g., `tests/introspection/dotnet/macOS/`). The parent `dotnet/` directory only has `build-%` and `run-%` pattern rules.
`make run-ios`/`run-tvos` uses `dotnet build -t:Run`, which does **NOT reliably capture** the app's stdout. The `com.apple.gamed` stderr message causes MSBuild to report failure (exit code -1) even when mlaunch returns 0, and NUnit test results are lost.
69
+
70
+
Instead, build first with `make build-ios`/`build-tvos`, then run mlaunch directly:
71
+
72
+
```bash
73
+
# Get the app path
74
+
APP_PATH=$(make -C tests/introspection/dotnet/iOS print-executable | sed 's|/introspection$||')
iOS and tvOS tests require simulator infrastructure (mlaunch, boot simulator, install app, etc.) that `run-bare` doesn't provide. Always use `make run-ios` / `make run-tvos` for these.
95
+
iOS and tvOS tests require simulator infrastructure (boot simulator, install app, etc.) that `run-bare` doesn't provide. Use **mlaunch directly** to launch the app in the simulator with output capture.
96
+
97
+
**Why not `make run-ios`/`run-tvos`?** These use `dotnet build -t:Run` which wraps mlaunch through MSBuild. The `com.apple.gamed` stderr noise from the simulator causes MSBuild to treat the run as failed (exit code -1), even though mlaunch returns 0 and the tests pass. The NUnit results are also not reliably captured to stdout through the MSBuild layer.
98
+
99
+
Running mlaunch directly with `SIMCTL_CHILD_NUNIT_AUTOSTART=true` and `SIMCTL_CHILD_NUNIT_AUTOEXIT=true` bypasses MSBuild's error detection and captures the simulator app's stdout (including NUnit results) directly to the terminal.
73
100
74
101
## Reading Test Results
75
102
@@ -97,8 +124,27 @@ Exclusion mechanisms:
97
124
98
125
### Simulator Infrastructure Errors
99
126
100
-
`com.apple.gamed` connection errors are a known simulator environment issue. If mlaunch returns exit code 0 and you see the NUnit results pattern, the tests passed despite the error.
127
+
`com.apple.gamed` connection errors are a known simulator environment issue. When running via mlaunch directly, these appear as stderr noise but don't affect the test results. When running via `make run-ios`/`run-tvos` (`dotnet build -t:Run`), these stderr messages cause MSBuild to report failure (exit code -1) even though tests pass — this is why running mlaunch directly is preferred.
101
128
102
129
## Build Timeouts
103
130
104
131
Builds can take up to 60 minutes. Do not set short timeouts on make/build commands.
132
+
133
+
## Stale Build Artifacts
134
+
135
+
If you encounter unexpected failures — types crashing in unrelated frameworks, false "pre-existing" failures, protocol conformance mismatches that shouldn't exist — the most likely cause is **stale `_build/` artifacts**.
136
+
137
+
**Fix:** Run a full `make all && make install` before re-testing. This rebuilds everything cleanly and installs fresh assemblies.
138
+
139
+
**Warning signs of stale artifacts:**
140
+
- Introspection tests report failures not seen on a clean checkout
141
+
- Types crash in `-[description]` or `-[dealloc]` in frameworks you didn't modify
When adding exclusions for types that crash on simulator:
147
+
148
+
-**NEVER skip an entire namespace.** Always add exclusions for specific types only.
149
+
-**Prefer fixing the binding over adding test exclusions.** For example, if a `[DesignatedInitializer]` constructor crashes when passed null, remove `[NullAllowed]` from the parameter rather than excluding the type from introspection tests.
150
+
- Only add exclusions for genuine simulator/beta SDK bugs that can't be fixed in managed code.
0 commit comments