Skip to content

Instantly share code, notes, and snippets.

@brianmed
Last active May 5, 2024 03:54
Show Gist options
  • Save brianmed/27ffcddac1f08d76e3f76e0d2be4229e to your computer and use it in GitHub Desktop.
Save brianmed/27ffcddac1f08d76e3f76e0d2be4229e to your computer and use it in GitHub Desktop.
C# Hello World gui app with slight SharpMetal modifications
using System.Runtime.Versioning;
using SharpMetal.ObjectiveCCore;
namespace SharpMetal.Examples.Common
{
[SupportedOSPlatform("macos")]
public class NSApplication
{
public IntPtr NativePtr;
public NSApplication(IntPtr ptr)
{
NativePtr = ptr;
}
public NSApplication()
{
NativePtr = ObjectiveC.IntPtr_objc_msgSend(new ObjectiveCClass("NSApplication"), "sharedApplication");
}
public void Run()
{
ObjectiveC.objc_msgSend(NativePtr, "run");
}
public void Stop()
{
ObjectiveC.objc_msgSend(NativePtr, "stop:", IntPtr.Zero);
}
public void ActivateIgnoringOtherApps(bool flag)
{
ObjectiveC.objc_msgSend(NativePtr, "activateIgnoringOtherApps:", flag);
}
public bool SetActivationPolicy(NSApplicationActivationPolicy activationPolicy)
{
return ObjectiveC.bool_objc_msgSend(NativePtr, "setActivationPolicy:", (long)activationPolicy);
}
public void SetDelegate(NSApplicationDelegate appDelegate)
{
ObjectiveC.objc_msgSend(NativePtr, "setDelegate:", appDelegate.NativePtr);
}
}
public enum NSApplicationActivationPolicy : long
{
Regular = 0,
Accessory = 1,
Prohibited = 2
}
}
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using SharpMetal.Foundation;
using SharpMetal.ObjectiveCCore;
namespace SharpMetal.Examples.Common
{
[SupportedOSPlatform("macos")]
public class NSApplicationDelegate
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void OnApplicationWillFinishLaunchingDelegate(IntPtr id, IntPtr cmd, IntPtr notification);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void OnApplicationDidFinishLaunchingDelegate(IntPtr id, IntPtr cmd, IntPtr notification);
private OnApplicationWillFinishLaunchingDelegate _onApplicationWillFinishLaunching;
private OnApplicationDidFinishLaunchingDelegate _onApplicationDidFinishLaunching;
public Action<NSNotification> OnApplicationWillFinishLaunching;
public Action<NSNotification> OnApplicationDidFinishLaunching;
public IntPtr NativePtr;
public unsafe NSApplicationDelegate(NSApplication application)
{
char[] name = "AppDelegate".ToCharArray();
char[] types = "v@:#".ToCharArray();
fixed (char* pName = name)
{
fixed (char* pTypes = types)
{
_onApplicationWillFinishLaunching = (_, _, notif) => OnApplicationWillFinishLaunching?.Invoke(new NSNotification(notif));
var onApplicationWillFinishLaunchingPtr = Marshal.GetFunctionPointerForDelegate(_onApplicationWillFinishLaunching);
_onApplicationDidFinishLaunching = (_, _, notif) => OnApplicationDidFinishLaunching?.Invoke(new NSNotification(notif));
var onDidFinishLaunchingPtr = Marshal.GetFunctionPointerForDelegate(_onApplicationDidFinishLaunching);
var appDelegateClass = ObjectiveC.objc_allocateClassPair(new ObjectiveCClass("NSObject"), pName, 0);
ObjectiveC.class_addMethod(appDelegateClass, "applicationWillFinishLaunching:", onApplicationWillFinishLaunchingPtr, pTypes);
ObjectiveC.class_addMethod(appDelegateClass, "applicationDidFinishLaunching:", onDidFinishLaunchingPtr, pTypes);
ObjectiveC.objc_registerClassPair(appDelegateClass);
NativePtr = new ObjectiveCClass(appDelegateClass).AllocInit();
}
}
}
}
}
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using SharpMetal.Foundation;
using Joy.ObjectiveCCore;
namespace SharpMetal.Examples.Common
{
[SupportedOSPlatform("macos")]
public class NSWindow
{
public IntPtr NativePtr;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void OnWindowWillCloseDelegate(IntPtr id, IntPtr cmd, IntPtr notification);
private OnWindowWillCloseDelegate _onWindowWillClose;
public Action<NSNotification> OnWindowWillClose;
public unsafe NSWindow(NSRect rect, ulong styleMask)
{
// NativePtr = new ObjectiveCClass("NSWindow").Alloc();
// ObjectiveC.objc_msgSend(NativePtr, "initWithContentRect:styleMask:backing:defer:", rect, styleMask, 2, false);
char[] name = "WindowDelegate".ToCharArray();
char[] types = "v@:@".ToCharArray();
fixed (char* pName = name)
fixed (char* pTypes = types)
{
// ObjectiveC.objc_registerClassPair(nsWindowClass);
IntPtr joy = ObjectiveC.objc_getClass("NSWindow");
NativePtr = new ObjectiveCClass(joy).Alloc();
ObjectiveC.objc_msgSend(NativePtr, "initWithContentRect:styleMask:backing:defer:", rect, styleMask, 2, false);
IntPtr windowDelegateClass = ObjectiveC.objc_allocateClassPair(new ObjectiveCClass("NSObject"), pName, 0);
IntPtr yay = ObjectiveC.objc_getProtocol("NSWindowDelegate");
ObjectiveC.class_addProtocol(windowDelegateClass, yay);
_onWindowWillClose = (_, _, notif) =>
{
OnWindowWillClose?.Invoke(new NSNotification(notif));
};
IntPtr onWindowWillClosePtr = Marshal.GetFunctionPointerForDelegate(_onWindowWillClose);
ObjectiveC.class_addMethod(windowDelegateClass, "windowWillClose:", onWindowWillClosePtr, pTypes);
ObjectiveC.objc_msgSend(NativePtr, "setDelegate:", new ObjectiveCClass(windowDelegateClass).AllocInit());
}
}
public NSString Title
{
get => new(ObjectiveC.IntPtr_objc_msgSend(NativePtr, "title"));
set => ObjectiveC.objc_msgSend(NativePtr, "setTitle:", value);
}
public void SetContentView(IntPtr ptr)
{
ObjectiveC.objc_msgSend(NativePtr, "setContentView:", ptr);
}
public void MakeKeyAndOrderFront()
{
ObjectiveC.objc_msgSend(NativePtr, "makeKeyAndOrderFront:", IntPtr.Zero);
}
}
[SupportedOSPlatform("macos")]
[Flags]
public enum NSStyleMask : ulong
{
Borderless = 0,
Titled = 1 << 0,
Closable = 1 << 1,
Miniaturizable = 1 << 2,
Resizable = 1 << 3,
FullScreen = 1 << 14,
FullSizeContentView = 1 << 15,
UtilityWindow = 1 << 4,
DocModalWindow = 1 << 6,
NonactivatingPanel = 1 << 7,
HUDWindow = 1 << 13
}
}
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Joy.ObjectiveCCore
{
[SupportedOSPlatform("macos")]
public static partial class ObjectiveC
{
public const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
public const string MetalFramework = "/System/Library/Frameworks/Metal.framework/Metal";
[LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr objc_getClass(string name);
[LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr objc_getProtocol(string name);
[LibraryImport("libdl.dylib", StringMarshalling = StringMarshalling.Utf8)]
private static partial void dlopen(string path, int mode);
[LibraryImport(ObjCRuntime)]
public static unsafe partial IntPtr objc_allocateClassPair(IntPtr superclass, char* name, int extraBytes);
[LibraryImport(ObjCRuntime)]
[return: MarshalAs(UnmanagedType.Bool)]
public static unsafe partial bool class_addMethod(IntPtr cls, Selector selector, IntPtr imp, char* types);
[LibraryImport(ObjCRuntime)]
public static unsafe partial IntPtr class_replaceMethod(IntPtr cls, Selector selector, IntPtr imp, char* types);
[LibraryImport(ObjCRuntime)]
[return: MarshalAs(UnmanagedType.Bool)]
public static unsafe partial bool class_addProtocol(IntPtr cls, IntPtr protocol);
[LibraryImport(ObjCRuntime)]
public static partial void objc_registerClassPair(IntPtr cls);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect rect);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr ptr);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
[return: MarshalAs(UnmanagedType.Bool)]
public static unsafe partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, char* buffer, ulong maxBufferCount, ulong encoding);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, long activationPolicy);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, ulong format, ulong index);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect rect, byte value);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, NSRect rect, IntPtr value);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, [MarshalAs(UnmanagedType.Bool)] bool value);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect contentRect, ulong style, ulong backingStoreType, [MarshalAs(UnmanagedType.Bool)] bool flag);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, string param);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)]
public static partial string string_objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial ulong ulong_objc_msgSend(IntPtr receiver, Selector selector);
public static void LinkMetal()
{
dlopen("/System/Library/Frameworks/Metal.framework/Metal", 0);
}
public static void LinkCoreGraphics()
{
dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", 0);
}
public static void LinkAppKit()
{
dlopen("/System/Library/Frameworks/AppKit.framework/AppKit", 0);
}
public static void LinkMetalKit()
{
dlopen("/System/Library/Frameworks/MetalKit.framework/MetalKit", 0);
}
}
public readonly struct NSPoint
{
public readonly double X;
public readonly double Y;
public NSPoint(double x, double y)
{
X = x;
Y = y;
}
}
public readonly struct NSRect
{
public readonly NSPoint Pos;
public readonly NSPoint Size;
public NSRect(double x, double y, double width, double height)
{
Pos = new NSPoint(x, y);
Size = new NSPoint(width, height);
}
}
}
using System.Runtime.Versioning;
namespace Joy.ObjectiveCCore
{
[SupportedOSPlatform("macos")]
public struct ObjectiveCClass
{
public readonly IntPtr NativePtr;
public static implicit operator IntPtr(ObjectiveCClass c) => c.NativePtr;
public ObjectiveCClass(IntPtr ptr)
{
NativePtr = ptr;
}
public ObjectiveCClass(string name)
{
var ptr = ObjectiveC.objc_getClass(name);
if (ptr == IntPtr.Zero)
{
Console.WriteLine($"Failed to get class {name}!");
}
NativePtr = ptr;
}
public ObjectiveCClass AllocInit()
{
var value = ObjectiveC.IntPtr_objc_msgSend(NativePtr, "alloc");
ObjectiveC.objc_msgSend(value, "init");
return new ObjectiveCClass(value);
}
public ObjectiveCClass Alloc()
{
var value = ObjectiveC.IntPtr_objc_msgSend(NativePtr, "alloc");
return new ObjectiveCClass(value);
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpMetal" Version="1.0.0-preview12" />
</ItemGroup>
</Project>
using System.Runtime.Versioning;
using SharpMetal.Foundation;
using Joy.ObjectiveCCore;
using SharpMetal.Examples.Common;
// OMG!!
//
// https://github.com/IsaacMarovitz/SharpMetal
namespace ObjectiveCHelloWorld;
[SupportedOSPlatform("macos")]
public class Program
{
private const int X = 100;
private const int Y = 100;
private const int Width = 800;
private const int Height = 600;
private const string WindowTitle = "Hello World";
public static void Main()
{
ObjectiveC.LinkAppKit();
// Create NSApplication
var nsApplication = new NSApplication();
var appDelegate = new NSApplicationDelegate(nsApplication);
nsApplication.SetDelegate(appDelegate);
appDelegate.OnApplicationDidFinishLaunching += OnApplicationDidFinishLaunching;
appDelegate.OnApplicationWillFinishLaunching += OnApplicationWillFinishLaunching;
nsApplication.Run();
}
private static void OnWindowWillClose(NSNotification notification)
{
Environment.Exit(0);
}
private static void OnApplicationWillFinishLaunching(NSNotification notification)
{
var app = new NSApplication(notification.Object);
app.SetActivationPolicy(NSApplicationActivationPolicy.Regular);
}
private static void OnApplicationDidFinishLaunching(NSNotification notification)
{
NSRect rect = new NSRect(X, Y, Width, Height);
NSWindow window = new NSWindow(rect, (ulong)(NSStyleMask.Titled | NSStyleMask.Resizable | NSStyleMask.Closable | NSStyleMask.Miniaturizable));
window.OnWindowWillClose += OnWindowWillClose;
IntPtr nsView = ObjectiveC.objc_getClass("NSView");
IntPtr joy = new ObjectiveCClass(nsView).Alloc();
ObjectiveC.objc_msgSend(joy, "initWithFrame:", rect);
IntPtr textFieldClass = ObjectiveC.objc_getClass("NSTextField");
IntPtr textField = new ObjectiveCClass(textFieldClass).Alloc();
ObjectiveC.objc_msgSend(textField, "initWithFrame:", new NSRect(Width/2.0f, Height/2.0f, 200, 17));
ObjectiveC.objc_msgSend(textField, "setStringValue:", StringHelper.NSString("Hello World"));
ObjectiveC.objc_msgSend(textField, "setEditable:", false);
ObjectiveC.objc_msgSend(textField, "setBezeled:", false);
ObjectiveC.objc_msgSend(textField, "setDrawsBackground:", false);
ObjectiveC.objc_msgSend(joy, "addSubview:", textField);
window.SetContentView(joy);
window.Title = StringHelper.NSString(WindowTitle);
// ObjectiveC.objc_msgSend(window.NativePtr, "becomeFirstResponder");
window.MakeKeyAndOrderFront();
NSApplication app = new NSApplication(notification.Object);
app.ActivateIgnoringOtherApps(true);
}
}
using System.Runtime.InteropServices;
namespace Joy.ObjectiveCCore
{
public partial struct Selector
{
[LibraryImport("/usr/lib/libobjc.A.dylib", StringMarshalling = StringMarshalling.Utf8)]
private static unsafe partial IntPtr sel_getUid(string name);
[LibraryImport("/usr/lib/libobjc.A.dylib")]
private static unsafe partial IntPtr sel_getName(IntPtr sel);
public readonly IntPtr SelPtr;
public Selector(string name)
{
var ptr = sel_getUid(name);
if (ptr == IntPtr.Zero)
{
Console.WriteLine($"Failed to get selector {name}!");
}
SelPtr = ptr;
}
public string Name
{
get
{
var ptr = sel_getName(SelPtr);
return Marshal.PtrToStringAnsi(ptr) ?? string.Empty;
}
}
public static implicit operator Selector(string value) => new(value);
public static implicit operator IntPtr(Selector sel) => sel.SelPtr;
}
}
using System.Runtime.Versioning;
using SharpMetal.Foundation;
using SharpMetal.ObjectiveCCore;
namespace SharpMetal.Examples.Common
{
[SupportedOSPlatform("macos")]
public static class StringHelper
{
public static NSString NSString(string source)
{
return new(ObjectiveC.IntPtr_objc_msgSend(new ObjectiveCClass("NSString"), "stringWithUTF8String:", source));
}
public static unsafe string String(NSString source)
{
char[] sourceBuffer = new char[source.Length];
fixed (char* pSourceBuffer = sourceBuffer)
{
ObjectiveC.bool_objc_msgSend(source,
"getCString:maxLength:encoding:",
pSourceBuffer,
source.MaximumLengthOfBytes(NSStringEncoding.UTF16) + 1,
(ulong)NSStringEncoding.UTF16);
}
return new string(sourceBuffer);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment