Skip to content

Commit 281be9e

Browse files
authored
[mbr] Add Apple sample (#50740)
* [mbr] Add an apple sample Works on iOS simulator and Mac Catalyst * delete unused DeltaHelper code Now that System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate is in the BCL officially, I don't need the reflection-based invoke code anymore
1 parent 9089931 commit 281be9e

File tree

9 files changed

+324
-41
lines changed

9 files changed

+324
-41
lines changed

src/mono/sample/mbr/DeltaHelper/DeltaHelper.cs

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,9 @@
77

88
namespace MonoDelta {
99
public class DeltaHelper {
10-
private static Action<Assembly, byte[], byte[], byte[]> _updateMethod;
11-
12-
private static Action<Assembly, byte[], byte[], byte[]> UpdateMethod => _updateMethod ?? InitUpdateMethod();
13-
14-
private static Action<Assembly, byte[], byte[], byte[]> InitUpdateMethod ()
15-
{
16-
var monoType = typeof(System.Reflection.Metadata.AssemblyExtensions);
17-
const string methodName = "ApplyUpdate";
18-
var mi = monoType.GetMethod (methodName, BindingFlags.Public | BindingFlags.Static);
19-
if (mi == null)
20-
throw new Exception ($"Couldn't get {methodName} from {monoType.FullName}");
21-
_updateMethod = MakeUpdateMethod (mi); //Delegate.CreateDelegate (typeof(Action<Assembly, byte[], byte[], byte[]>), mi) as Action<Assembly, byte[], byte[], byte[]>;
22-
return _updateMethod;
23-
}
24-
25-
private static Action<Assembly, byte[], byte[], byte[]> MakeUpdateMethod (MethodInfo applyUpdate)
26-
{
27-
// Make
28-
// void ApplyUpdateArray (Assembly a, byte[] dmeta, byte[] dil, byte[] dpdb)
29-
// {
30-
// ApplyUpdate (a, (ReadOnlySpan<byte>)dmeta, (ReadOnlySpan<byte>)dil, (ReadOnlySpan<byte>)dpdb);
31-
// }
32-
var dm = new DynamicMethod ("CallApplyUpdate", typeof(void), new Type[] { typeof(Assembly), typeof(byte[]), typeof(byte[]), typeof(byte[])}, typeof (DeltaHelper).Module);
33-
var ilg = dm.GetILGenerator ();
34-
var conv = typeof(ReadOnlySpan<byte>).GetMethod("op_Implicit", new Type[] {typeof(byte[])});
35-
36-
ilg.Emit (OpCodes.Ldarg_0);
37-
ilg.Emit (OpCodes.Ldarg_1);
38-
ilg.Emit (OpCodes.Call, conv);
39-
ilg.Emit (OpCodes.Ldarg_2);
40-
ilg.Emit (OpCodes.Call, conv);
41-
ilg.Emit (OpCodes.Ldarg_3);
42-
ilg.Emit (OpCodes.Call, conv);
43-
ilg.Emit (OpCodes.Call, applyUpdate);
44-
ilg.Emit (OpCodes.Ret);
45-
46-
return dm.CreateDelegate(typeof(Action<Assembly, byte[], byte[], byte[]>)) as Action<Assembly, byte[], byte[], byte[]>;
47-
}
48-
4910
private static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data, byte[] dpdb_data)
5011
{
51-
UpdateMethod (assm, dmeta_data, dil_data, dpdb_data);
12+
System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate (assm, dmeta_data, dil_data, dpdb_data);
5213
}
5314

5415
DeltaHelper () { }

src/mono/sample/mbr/README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,19 @@ For WebAssembly:
3030

3131
Make sure `EMSDK_PATH` is set (see [workflow](../../../../docs/workflow/building/libraries/webassembly-instructions.md))
3232
```console
33-
build.sh --os browser /p:MonoMetadataUpdate=true
33+
build.sh --os browser
34+
```
35+
36+
For Apple targets:
37+
38+
```console
39+
build.sh --os MacCatalyst -s Mono+Libs
40+
```
41+
42+
or
43+
44+
```console
45+
build.sh --os iOSSimulator -s Mono+Libs
3446
```
3547

3648
## Running
@@ -50,3 +62,16 @@ make CONFIG=Debug && make CONFIG=Debug run
5062
```
5163

5264
Then go to http://localhost:8000/ and click the button once or twice (the example has 2 updates prebuilt)
65+
66+
For Apple targets:
67+
68+
for ios simulator
69+
```
70+
make run-sim
71+
```
72+
73+
for Mac Catalyst
74+
75+
```
76+
make run-catalyst
77+
```
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<OutputPath>bin</OutputPath>
5+
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
6+
<MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.$(TargetOS.ToLower())-$(TargetArchitecture)\$(Configuration)\runtimes\$(TargetOS.ToLower())-$(TargetArchitecture)\</MicrosoftNetCoreAppRuntimePackDir>
7+
<EnableTargetingPackDownload>false</EnableTargetingPackDownload>
8+
<RuntimeIdentifier>$(TargetOS.ToLower())-$(TargetArchitecture)</RuntimeIdentifier>
9+
<DefineConstants Condition="'$(ArchiveTests)' == 'true'">$(DefineConstants);CI_TEST</DefineConstants>
10+
<MonoForceInterpreter>true</MonoForceInterpreter>
11+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<Compile Include="Program.cs" />
16+
<Compile Include="ChangeablePart.cs" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<ProjectReference Include="..\DeltaHelper\DeltaHelper.csproj" />
21+
</ItemGroup>
22+
23+
<PropertyGroup>
24+
<DeltaScript>deltascript.json</DeltaScript>
25+
<DeltaCount>1</DeltaCount>
26+
</PropertyGroup>
27+
28+
<PropertyGroup Condition="'$(TargetOS)' == 'MacCatalyst'">
29+
<DevTeamProvisioning Condition="'$(TargetOS)' == 'MacCatalyst' and '$(DevTeamProvisioning)' == ''">-</DevTeamProvisioning>
30+
</PropertyGroup>
31+
32+
<!-- Redirect 'dotnet publish' to in-tree runtime pack -->
33+
<Target Name="TrickRuntimePackLocation" AfterTargets="ProcessFrameworkReferences">
34+
<ItemGroup>
35+
<RuntimePack>
36+
<PackageDirectory>$(ArtifactsBinDir)microsoft.netcore.app.runtime.$(RuntimeIdentifier)\$(Configuration)</PackageDirectory>
37+
</RuntimePack>
38+
</ItemGroup>
39+
<Message Text="Packaged ID: %(RuntimePack.PackageDirectory)" Importance="high" />
40+
</Target>
41+
42+
<Import Project="$(RepoTasksDir)AotCompilerTask\MonoAOTCompiler.props" />
43+
<UsingTask TaskName="AppleAppBuilderTask"
44+
AssemblyFile="$(AppleAppBuilderTasksAssemblyPath)" />
45+
46+
<Target Name="BuildAppBundle" AfterTargets="CopyFilesToPublishDirectory">
47+
<PropertyGroup>
48+
<AppDir>$(MSBuildThisFileDirectory)$(PublishDir)\app</AppDir>
49+
<IosSimulator Condition="'$(TargetsiOSSimulator)' == 'true'">iPhone 11</IosSimulator>
50+
<Optimized Condition="'$(Configuration)' == 'Release'">True</Optimized>
51+
<RunAOTCompilation Condition="('$(TargetsMacCatalyst)' == 'false' and '$(IosSimulator)' == '') or '$(ForceAOT)' == 'true'">true</RunAOTCompilation>
52+
</PropertyGroup>
53+
54+
<Error Condition="'$(TargetOS)' == ''" Text="The TargetOS property must be set outside the project file" />
55+
56+
<RemoveDir Directories="$(AppDir)" />
57+
58+
<ItemGroup>
59+
<BundleAssemblies Condition="'$(RunAOTCompilation)' != 'true'" Include="$(MSBuildThisFileDirectory)$(PublishDir)\*.dll" />
60+
</ItemGroup>
61+
62+
<AppleAppBuilderTask
63+
TargetOS="$(TargetOS)"
64+
Arch="$(TargetArchitecture)"
65+
ProjectName="AppleDelta"
66+
MonoRuntimeHeaders="$(MicrosoftNetCoreAppRuntimePackDir)native\include\mono-2.0"
67+
Assemblies="@(BundleAssemblies)"
68+
NativeMainSource="$(MSBuildThisFileDirectory)\main.m"
69+
MainLibraryFileName="AppleDelta.dll"
70+
GenerateXcodeProject="True"
71+
BuildAppBundle="True"
72+
DevTeamProvisioning="$(DevTeamProvisioning)"
73+
OutputDirectory="$(AppDir)"
74+
Optimized="$(Optimized)"
75+
ForceAOT="$(ForceAOT)"
76+
ForceInterpreter="$(MonoForceInterpreter)"
77+
AppDir="$(MSBuildThisFileDirectory)$(PublishDir)">
78+
<Output TaskParameter="AppBundlePath" PropertyName="AppBundlePath" />
79+
<Output TaskParameter="XcodeProjectPath" PropertyName="XcodeProjectPath" />
80+
</AppleAppBuilderTask>
81+
82+
<Message Importance="High" Text="Xcode: $(XcodeProjectPath)"/>
83+
<Message Importance="High" Text="App: $(AppBundlePath)"/>
84+
85+
<Exec Condition="'$(TargetOS)' == 'iOSSimulator'" Command="dotnet xharness apple run --app=$(AppBundlePath) --targets=ios-simulator-64 --output-directory=/tmp/out" />
86+
87+
<!-- run on MacCatalyst -->
88+
<Exec Condition="'$(TargetOS)' == 'MacCatalyst'" Command="dotnet xharness apple run --app=$(AppBundlePath) --targets=maccatalyst --output-directory=/tmp/out" />
89+
90+
</Target>
91+
92+
<!-- Set RoslynILDiffFullPath property to the path of roslynildiff -->
93+
<Import Project="..\DeltaHelper.targets" />
94+
95+
</Project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
public class ChangeablePart
4+
{
5+
public static int UpdateCounter (ref int counter)
6+
{
7+
return ++counter;
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
public class ChangeablePart
4+
{
5+
public static int UpdateCounter (ref int counter)
6+
{
7+
return --counter;
8+
}
9+
}

src/mono/sample/mbr/apple/Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CONFIG=Debug
2+
MONO_ARCH=x64
3+
DOTNET := ../../../../../dotnet.sh
4+
5+
run-sim:
6+
$(DOTNET) publish -c $(CONFIG) /p:TargetOS=iOSSimulator /p:TargetArchitecture=$(MONO_ARCH) \
7+
/p:UseLLVM=False /p:ForceAOT=False /p:MonoForceInterpreter=true
8+
9+
run-catalyst:
10+
$(DOTNET) publish -c $(CONFIG) /p:TargetOS=MacCatalyst /p:TargetArchitecture=$(MONO_ARCH) \
11+
/p:UseLLVM=False /p:ForceAOT=False /p:MonoForceInterpreter=true
12+
13+
clean:
14+
rm -rf bin

src/mono/sample/mbr/apple/Program.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using System.Runtime.InteropServices;
8+
9+
public static class Program
10+
{
11+
// Defined in main.m
12+
[DllImport("__Internal")]
13+
private static extern void ios_set_text(string value);
14+
15+
[DllImport("__Internal")]
16+
unsafe private static extern void ios_register_button_click(delegate* unmanaged<void> callback);
17+
18+
[DllImport("__Internal")]
19+
unsafe private static extern void ios_register_applyupdate_click(delegate* unmanaged<void> callback);
20+
21+
private static int counter = 0;
22+
23+
// Called by native code, see main.m
24+
[UnmanagedCallersOnly]
25+
private static void OnButtonClick()
26+
{
27+
ios_set_text("OnButtonClick! #" + ChangeablePart.UpdateCounter (ref counter));
28+
}
29+
30+
[UnmanagedCallersOnly]
31+
private static void OnApplyUpdateClick()
32+
{
33+
deltaHelper.Update (typeof(ChangeablePart).Assembly);
34+
}
35+
36+
static MonoDelta.DeltaHelper deltaHelper;
37+
38+
public static async Task Main(string[] args)
39+
{
40+
unsafe {
41+
// Register a managed callback (will be called by UIButton, see main.m)
42+
delegate* unmanaged<void> unmanagedPtr = &OnButtonClick;
43+
ios_register_button_click(unmanagedPtr);
44+
delegate* unmanaged<void> unmanagedPtr2 = &OnApplyUpdateClick;
45+
ios_register_applyupdate_click(unmanagedPtr2);
46+
}
47+
deltaHelper = MonoDelta.DeltaHelper.Make();
48+
const string msg = "Hello World!\n.NET 5.0";
49+
for (int i = 0; i < msg.Length; i++)
50+
{
51+
// a kind of an animation
52+
ios_set_text(msg.Substring(0, i + 1));
53+
await Task.Delay(100);
54+
}
55+
56+
Console.WriteLine("Done!");
57+
await Task.Delay(-1);
58+
}
59+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"changes": [
3+
{"document": "ChangeablePart.cs", "update": "ChangeablePart_v1.cs"},
4+
]
5+
}

src/mono/sample/mbr/apple/main.m

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#include <stdlib.h>
5+
6+
#import <UIKit/UIKit.h>
7+
#import "runtime.h"
8+
9+
@interface ViewController : UIViewController
10+
@end
11+
12+
@interface AppDelegate : UIResponder <UIApplicationDelegate>
13+
@property (strong, nonatomic) UIWindow *window;
14+
@property (strong, nonatomic) ViewController *controller;
15+
@end
16+
17+
@implementation AppDelegate
18+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
19+
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
20+
self.controller = [[ViewController alloc] initWithNibName:nil bundle:nil];
21+
self.window.rootViewController = self.controller;
22+
[self.window makeKeyAndVisible];
23+
return YES;
24+
}
25+
@end
26+
27+
UILabel *label;
28+
void (*clickHandlerPtr)(void);
29+
void (*clickHandlerApplyUpdatePtr)(void);
30+
31+
@implementation ViewController
32+
33+
- (void)viewDidLoad {
34+
[super viewDidLoad];
35+
36+
label = [[UILabel alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
37+
label.textColor = [UIColor greenColor];
38+
label.font = [UIFont boldSystemFontOfSize: 30];
39+
label.numberOfLines = 2;
40+
label.textAlignment = NSTextAlignmentCenter;
41+
label.text = @"Hello, wire me up!\n(dllimport ios_set_text)";
42+
[self.view addSubview:label];
43+
44+
UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoDark];
45+
[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
46+
[button setFrame:CGRectMake(50, 250, 200, 50)];
47+
[button setTitle:@"Click me (wire me up)" forState:UIControlStateNormal];
48+
[button setExclusiveTouch:YES];
49+
[self.view addSubview:button];
50+
51+
UIButton *apply_button = [UIButton buttonWithType:UIButtonTypeInfoDark];
52+
[apply_button addTarget:self action:@selector(applyUpdateButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
53+
[apply_button setFrame:CGRectMake(50, 300, 200, 50)];
54+
[apply_button setTitle:@"ApplyUpdate" forState:UIControlStateNormal];
55+
[apply_button setExclusiveTouch:YES];
56+
[self.view addSubview:apply_button];
57+
58+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
59+
mono_ios_runtime_init ();
60+
});
61+
}
62+
-(void) buttonClicked:(UIButton*)sender
63+
{
64+
if (clickHandlerPtr)
65+
clickHandlerPtr();
66+
}
67+
68+
-(void) applyUpdateButtonClicked:(UIButton*)sender
69+
{
70+
if (clickHandlerApplyUpdatePtr)
71+
clickHandlerApplyUpdatePtr();
72+
}
73+
74+
@end
75+
76+
// called from C# sample
77+
void
78+
ios_register_button_click (void* ptr)
79+
{
80+
clickHandlerPtr = ptr;
81+
}
82+
83+
// called from C# sample
84+
void
85+
ios_register_applyupdate_click (void* ptr)
86+
{
87+
clickHandlerApplyUpdatePtr = ptr;
88+
}
89+
90+
// called from C# sample
91+
void
92+
ios_set_text (const char* value)
93+
{
94+
NSString* nsstr = [NSString stringWithUTF8String:strdup(value)];
95+
dispatch_async(dispatch_get_main_queue(), ^{
96+
label.text = nsstr;
97+
});
98+
}
99+
100+
int main(int argc, char * argv[]) {
101+
@autoreleasepool {
102+
setenv("DOTNET_MODIFIABLE_ASSEMBLIES", "Debug", 1);
103+
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
104+
}
105+
}
106+

0 commit comments

Comments
 (0)