Skip to content

Commit 24baa2e

Browse files
committed
Add building iOS native libraries with NativeAOT documentation
1 parent 1b7357e commit 24baa2e

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed

core/nativeaot/NativeLibrary/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ When linking the generated static library, it is important to also include addit
119119

120120
You can find a list of additional framework libraries by publishing the project as shared library (`/p:NativeLib=Shared`) with detailed verbosity (`-v d`), and looking at the output generated by the `LinkNative` target.
121121

122+
## Building iOS Native Libraries with NativeAOT
123+
124+
A separate [document](./ios-like-native-libraries-with-nativeaot.md) covers building native libraries with NativeAOT for iOS-like platforms in .NET 9.
125+
122126
## References
123127

124128
Real-world example of using CoreRT (previous incarnation of NativeAOT) and Rust is [in this article](https://medium.com/@chyyran/calling-c-natively-from-rust-1f92c506289d).
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# iOS-like Native Libraries with NativeAOT in .NET 9
2+
3+
## Building shared libraries
4+
5+
This section describes steps to create a simple .NET Class Library project with NativeAOT support.
6+
7+
1. Download .NET 9 SDK
8+
2. Create a class library project
9+
10+
```bash
11+
dotnet new classlib -n "MyNativeAOTLibrary"
12+
```
13+
14+
3. Add the following properties into the project file `MyNativeAOTLibrary.csproj`
15+
16+
```xml
17+
<PublishAot>true</PublishAot>
18+
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
19+
```
20+
21+
4. Edit the `MyNativeAOTLibrary/Class1.cs` source code to expose a managed method to the native world as `aotsample_add`. For example:
22+
23+
```cs
24+
using System.Runtime.InteropServices;
25+
namespace NaotLib;
26+
27+
public class Class1
28+
{
29+
[UnmanagedCallersOnly(EntryPoint = "aotsample_add")]
30+
public static int Add(int a, int b)
31+
{
32+
return a + b;
33+
}
34+
}
35+
```
36+
37+
### Building iOS shared libraries
38+
39+
This section covers building shared libraries for iOS physical devices (RID: `ios-arm64`).
40+
Targeting iOS simulators (RIDs: `iossimulator-arm64`, `iossimulator-x64`) is almost identical and is not covered in this guide.
41+
42+
1. Publish the class library and target desired iOS platform
43+
44+
```bash
45+
dotnet publish -r ios-arm64 MyNativeAOTLibrary/MyNativeAOTLibrary.csproj
46+
```
47+
48+
2. Shared library `MyNativeAOTLibrary.dylib` and debug symbols `MyNativeAOTLibrary.dylib.dSYM` will be located at: `MyNativeAOTLibrary/bin/Release/net9.0/ios-arm64/publish/`
49+
50+
### Building MacCatalyst shared libraries
51+
52+
This section covers building shared libraries for MacCatalyst (RIDs: `maccatalyst-arm64`, `maccatalyst-x64`).
53+
54+
1. Publish the class library and target MacCatalyst for `Arm64` architecture
55+
56+
```bash
57+
dotnet publish -r maccatalyst-arm64 MyNativeAOTLibrary/MyNativeAOTLibrary.csproj
58+
```
59+
60+
2. (and/or) Publish the class library and target MacCatalyst for `x64` architecture
61+
62+
```bash
63+
dotnet publish -r maccatalyst-x64 MyNativeAOTLibrary/MyNativeAOTLibrary.csproj
64+
```
65+
66+
3. Previous steps will produce two pairs of files: a shared library `MyNativeAOTLibrary.dylib` and its debug symbols `MyNativeAOTLibrary.dylib.dSYM` for each of the two architectures, which will be located in their respective publish folders: `MyNativeAOTLibrary/bin/Release/net9.0/<rid>/publish/`
67+
68+
> [!NOTE]
69+
> Both step 1. and step 2. are required if a universal MacCatalyst framework is being created (more info available [below](#packaging-the-shared-library-into-a-custom-maccatalyst-universal-framework))
70+
71+
## Creating and consuming a custom framework (optional)
72+
73+
Apple imposes a requirement that shared libraries (.dylibs) need to be packaged into frameworks in order to be consumed from applications.
74+
75+
This section describes all required steps to achieve this and a simple scenario of a iOS/MacCatalyst application consuming a shared NativeAOT library/framework.
76+
77+
> [!NOTE]
78+
> The described steps are just for demonstration purposes. The actual requirements may differ depending on the exact use case.
79+
80+
### Packaging the shared library into custom iOS framework
81+
82+
1. Create a framework folder
83+
84+
```bash
85+
mkdir MyNativeAOTLibrary.framework
86+
```
87+
88+
2. Adjust load commands:
89+
90+
- `LC_RPATH` load command
91+
92+
```bash
93+
install_name_tool -rpath @executable_path @executable_path/Frameworks MyNativeAOTLibrary/bin/Release/net9.0/ios-arm64/publish/MyNativeAOTLibrary.dylib
94+
```
95+
96+
- `LC_ID_DYLIB` load command
97+
98+
```bash
99+
install_name_tool -id @rpath/MyNativeAOTLibrary.framework/MyNativeAOTLibrary MyNativeAOTLibrary/bin/Release/net9.0/ios-arm64/publish/MyNativeAOTLibrary.dylib
100+
```
101+
102+
3. Manually package the binary into a universal file
103+
104+
```bash
105+
lipo -create MyNativeAOTLibrary/bin/Release/net9.0/ios-arm64/publish/MyNativeAOTLibrary.dylib -output MyNativeAOTLibrary.framework/MyNativeAOTLibrary
106+
```
107+
108+
4. Add a property list file to your framework:
109+
110+
- Create a `Info.plist` file
111+
112+
```bash
113+
touch MyNativeAOTLibrary.framework/Info.plist
114+
```
115+
116+
- Add the contents from the [appendix](#appendix-infoplist-contents) into the created `Info.plist` file
117+
118+
After the final step the framework structure should look like this:
119+
120+
```
121+
MyNativeAOTLibrary.framework
122+
|_ MyNativeAOTLibrary
123+
|_ Info.plist
124+
```
125+
126+
### Packaging the shared library into a custom MacCatalyst universal framework
127+
128+
Universal frameworks require binaries for both `Arm64` and `x64` architecture.
129+
For this reason, it is required to publish native libraries targeting both RIDs: `maccatalyst-arm64` and `maccatalyst-x64` beforehand.
130+
131+
1. Create a framework folder structure
132+
133+
```bash
134+
mkdir -p MyNativeAOTLibrary.framework/Versions/A/Resources
135+
ln -sfh Versions/Current/MyNativeAOTLibrary MyNativeAOTLibrary.framework/MyNativeAOTLibrary
136+
ln -sfh Versions/Current/Resources MyNativeAOTLibrary.framework/Resources
137+
ln -sfh A MyNativeAOTLibrary.framework/Versions/Current
138+
```
139+
140+
2. Adjust load commands:
141+
142+
- `LC_RPATH` load command
143+
144+
```bash
145+
install_name_tool -rpath @executable_path @executable_path/../Frameworks MyNativeAOTLibrary/bin/Release/net9.0/maccatalyst-arm64/publish/MyNativeAOTLibrary.dylib
146+
install_name_tool -rpath @executable_path @executable_path/../Frameworks MyNativeAOTLibrary/bin/Release/net9.0/maccatalyst-x64/publish/MyNativeAOTLibrary.dylib
147+
```
148+
149+
- `LC_ID_DYLIB` load command
150+
151+
```bash
152+
install_name_tool -id @rpath/MyNativeAOTLibrary.framework/Versions/A/MyNativeAOTLibrary MyNativeAOTLibrary/bin/Release/net9.0/maccatalyst-arm64/publish/MyNativeAOTLibrary.dylib
153+
install_name_tool -id @rpath/MyNativeAOTLibrary.framework/Versions/A/MyNativeAOTLibrary MyNativeAOTLibrary/bin/Release/net9.0/maccatalyst-x64/publish/MyNativeAOTLibrary.dylib
154+
```
155+
156+
3. Manually package the binary into a universal file
157+
158+
```bash
159+
lipo -create MyNativeAOTLibrary/bin/Release/net9.0/maccatalyst-arm64/publish/MyNativeAOTLibrary.dylib MyNativeAOTLibrary/bin/Release/net9.0/maccatalyst-x64/publish/MyNativeAOTLibrary.dylib -output MyNativeAOTLibrary.framework/Versions/A/MyNativeAOTLibrary
160+
```
161+
162+
4. Add a property list file to your framework:
163+
164+
- Create a `Info.plist` file
165+
166+
```bash
167+
touch MyNativeAOTLibrary.framework/Versions/A/Resources/Info.plist
168+
```
169+
170+
- Add the contents from the [appendix](#appendix-infoplist-contents) into the created `Info.plist` file
171+
172+
After the final step the framework structure should look like this:
173+
174+
```
175+
MyNativeAOTLibrary.framework
176+
|_ MyNativeAOTLibrary -> Versions/Current/MyNativeAOTLibrary
177+
|_ Resources -> Versions/Current/Resources
178+
|_ Versions
179+
|_ A
180+
| |_ Resources
181+
| | |_ Info.plist
182+
| |_ MyNativeAOTLibrary
183+
|_ Current -> A
184+
```
185+
186+
### Consuming custom frameworks
187+
188+
1. Open `Xcode` (in this example `Xcode 16.0` is used)
189+
2. Create a new `App` project
190+
3. Choose the name for your app (e.g., `MyiOSApp`) and choose Objective-C as the source language
191+
4. Add a reference to the `MyNativeAOTLibrary` framework
192+
- In the `MyiOSApp` targets `General` tab, under `Frameworks, Libraries and Embedded Content` click the `+` sign to add `MyNativeAOTLibrary` as the referenced framework
193+
- In the dialog choose: `Add Other` -> `Add Files` and then browse to the location of `MyNativeAOTLibrary.framework` and select it
194+
- Once selected, set `Embed and Sign` option for `MyNativeAOTLibrary` framework
195+
196+
![Drop](./xcode-add-framework-reference.png)
197+
5. Add `MyNativeAOTLibrary.framework` location to the list of `Framework Search Paths` in the `Build Settings` tab
198+
199+
![Drop](./xcode-add-framework-search-path.png)
200+
6. Edit the `main.m` by calling the exposed managed method `aotsample_add` and printing the result
201+
202+
```objc
203+
extern int aotsample_add(int a, int b);
204+
int main(int argc, char * argv[]) {
205+
...
206+
NSLog(@"2 + 5 = %d", aotsample_add(2, 5));
207+
...
208+
}
209+
```
210+
211+
7. Select your physical iOS device and build/run the app
212+
8. Inspect the logs after the app has successfully launched, the app should print out: `2 + 5 = 7`
213+
214+
> [!NOTE]
215+
> For MacCatalyst, the above-described steps are identical apart from step 7, where the Run Destination needs to be set as: `Mac (Mac Catalyst)`
216+
217+
## Building static libraries with NativeAOT for iOS-like platforms
218+
219+
As described in the [parent document](./README.md#building-static-libraries), it is preferred to build shared libraries over static ones due to several limitations.
220+
221+
However, if desired, building static libraries can be accomplished by following the steps for building a shared one, with an additional property to be included in the project file:
222+
223+
```xml
224+
<NativeLib>Static</NativeLib>
225+
```
226+
227+
After the project has been published, the static library `MyNativeAOTLibrary.a` can be found at: `MyNativeAOTLibrary/bin/Release/net9.0/<rid>/publish`.
228+
229+
Consuming the static library and configuring the consumer project are not covered in this document.
230+
231+
## Appendix Info.plist contents
232+
233+
```xml
234+
<?xml version="1.0" encoding="UTF-8"?>
235+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
236+
<plist version="1.0">
237+
<dict>
238+
<key>CFBundleName</key>
239+
<string>MyNativeAOTLibrary</string>
240+
<key>CFBundleIdentifier</key>
241+
<string>com.companyname.MyNativeAOTLibrary</string>
242+
<key>CFBundleVersion</key>
243+
<string>1.0</string>
244+
<key>CFBundleExecutable</key>
245+
<string>MyNativeAOTLibrary</string>
246+
<key>CFBundlePackageType</key>
247+
<string>FMWK</string>
248+
</dict>
249+
</plist>
250+
```
Loading
Loading

0 commit comments

Comments
 (0)