discuss-gnustep
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Swift calling into GNUStep Progress


From: Gregory Casamento
Subject: Re: Swift calling into GNUStep Progress
Date: Fri, 11 Aug 2023 01:48:21 -0400

Awesome, thanks for the update!  This is great stuff.  

One detail I have been meaning to share is that if you look in the SDKs in Xcode there are files among the headers that I think swift uses to give hints about how to transform the method names.  I am not near my computer right now so I can’t remember what they are called.   This kind of thing might be useful. 

GC

On Thu, Aug 10, 2023 at 21:35 <dr_clow@me.com> wrote:
Hello Friends, 

I just wanted to give you a little update regarding my progress. All of this can also be viewed on my GitHub at <https://github.com/austintatiousness/GNUStepSwiftBridge/

NSObjects and Subclassing
I have two classes that help bridge Swift classes to Objective-C (1) GNUStepNSObjectWrapper and (2) GNUStepNSObjectSubclassConstructor. The former is used to simply wrap Objective-C NSObjects and the latter is used to register subclasses with the GNUStep Objective-C runtime. All classes crated with GNUStepNSObjectSubclassConstructor get a special Ivar '___swiftPtr ' intended to hold a reference back the Swift. Once you use GNUStepNSObjectSubclassConstructor to create a new subclass, you can then wrap  it using GNUStepNSObjectWrapper. 

I've actually had to do this already, within the Swift AppKit API, I've subclassed NSButton as "NSButtonForSwift" but expose it as NSButton to the Swift API. This allows me to use closures instead of target/selectors. 

I don not currently like how I am using ___swiftPtr to hold a reference to the Swift object created with any class registered with GNUStepNSObjectSubclassConstructor, but it is the best solution I could come up keep track of who owns the Objective-C object. 

I understand that on Apple platforms, Swift actually emits Objective-C compatible Swift objects when @objc or inheriting from NSObject occurs, which allows them casted on either side correctly. 

NSString, NSArray and NSDictionary are all going to be problematic types because there is currently no way to share storage like in Apple's Swift implementation. I think that some work needs to be done here to figure this out. That is beyond my abilities. For now, I am just going to be copying data between them.

Currently, variables for like NSButton 'var title: String' take a String type and I just convert between NSString when getting and setting (I wish we could offer both, but Swift doesn't allow that) ; but when we have functions like "func setTitle(string: String)" I plan to also offer "func setTitle(string: NSString)" so we do not have to do any copying. 

Additionally, NSArray and NSDictionary are only going to be able to store Objective-C types. On Apple Platforms,

Regarding Sending Messages / Calling Method: 
I've to figure out how to use objc_msgSend and objc_msgSend_stret from Swift, so instead I am now using class_getMethodImplementation. Which has been incredibly successful. I created some helper swift functions called objc_smart_getIMP to aid in setting these up better. 

API Bridging Update: 

I've gotten some of the the GUI objects going: NSWindow, NSButton, NSImage, NSImageView, NSFont, NSColor. You can get and set frames. It's pretty cool. 

Image 8-10-23 at 8.09 PM.jpeg




On Aug 8, 2023, at 8:56 PM, dr_clow@me.com wrote:

UPDATE: SUCCESS!

Since I sent that last email. I had a little break through. 

Then GNUStep ObjC function class_getMethodImplementation (Apple also has function class_getMethodImplementation_stret that is evidently not used in GNUStep and is just a stub) and allows one to look up the C function that is precisely used with the function. 

So I created a nice Swift version of this lookup. 
public func objc_smart_getIMP<T>(object: GNUStepNSObjectWrapper, selector: String) -> T? {
let c = object_getClass(&object._nsobjptr!.pointee)
let v = class_getMethodImplementation(c, sel_getUid(selector))
let rt: T? = unsafeBitCast(v, to: T.self)
return rt
}

And then in the the var frame property of my NSView wrapper the following 
public var frame: CGRect {
get {
var imp:  (@convention(c) (id, SEL) -> (CGRect))? = objc_smart_getIMP(object: self, selector: "frame")
if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("frame")) {
return rtn
}
return .init(x: 0, y: 0, width: 0, height: 0)
}
set {
guard let selfPtr = self._nsobjptr else {return}
let _: Any? = objc_smart_sendMessage(object: self, selector: "setFrame:", value1: newValue)
}
}

RESULT: It worked!

When Swift Macros are working on Linux, this could be a Macro that generates everything we need. Maybe @GNUStepPropertyWrapper("frame", "getFrame") We could also have @GNUStepMethodWrapper that automatically wraps the method callers. 



On Aug 8, 2023, at 7:48 PM, dr_clow@me.com wrote:

One of the issues that I am identifying is trying to manage the functions `objc_msgSend ` and `objc_msgSend_stret `. I did a write up on my GitHub for this project, and (as of right now) am trying to come up with solutions to getting the right memory out of calls to `objc_msgSend ` and `objc_msgSend_stret `. 

Basically I need a way to make objc_msgSend_stret more generic. 

Also what datatype size does objc_msgSend_stret service vs objc_msgSend. I am assuming, that objc_msgSend is good for pointers and datives that are the size of pointers?

You can view my evolving discussion with myself at https://github.com/austintatiousness/GNUStepSwiftBridge

- Austin

objc_msgSend and objc_msgSend_stret

objc_msgSend() is a c function used by the Objective-C runtime to send messages. Unfortunately it has a variable arguments which cannot be imported into Swift. To overcome this, I have created some specialized versions of of objc_msgSend that have different number of arguments. These can be found in the ObjCSwiftInterop.c file.

Ultimately, I would like a swift version of this which more intelligently decides how to map the values. I started work on this, in the AppKit.swift file called func objc_smart_sendMessage<T>(object: NSObjectGNUStepSwiftBridge, selector: String, value1: Any?, value2: Any?, value3: Any?, value4: Any?, value5: Any?, value6: Any?, value7: Any?, value8: Any?, value9: Any?) -> T?

This solves the problem that we have to send messages of arbitrary side, but it does not solve the issues around casting, and that two separate functions objc_msgSend and objc_msgSend_stret depending on which type we are going to get back from.

CASTING

Just some thoughts and notes: Please correct me where I am wrong or misunderstanding.

objc_msgSend and objc_msgSend_stret are very difficult functions to properly import into Swift. objc_msgSend is used for messages that return Objective-C classes, and simple data values that have the same size as id . From what I understand, both require the the function to be properly cast to work and because they are implemented in assembly and they are doing some magic with the registers to properly get the result. objc_msgSend returns void* but objc_msgSend_stret on GNUStep's runtime returns void. Before you can use objc_msgSend_stret, you have to cast it to a function that returns the value you are expecting.

UIView *view = [[NSView alloc] initWithFrame:NSRectZero];

NSRect (*sendRectFn)(id receiver, SEL operation);
sendRectFn = (NSRect(*)(id, SEL))objc_msgSend_stret;
NSRect frame = sendRectFn(view, @selector(frame));

Idea 1: THIS DOES NOT WORK. I really have no idea what I am doing.

void* forSwift_objcMsgSend_stret(id ID, SEL cmd, int64_t returnSize) {
	void* itemArr = malloc(returnSize);
	void* (*sendRectFn)(id receiver, SEL operation);
	sendRectFn = (void(*)(id, SEL))objc_msgSend_stret;
	itemArr = sendRectFn(ID, cmd);
	return itemArr;
}

What we need to be able to do is express this in Swift using

I do not know how the objc_msgSend_stret knows what the returned data type should be.

On Aug 8, 2023, at 4:56 AM, Gregory Casamento <greg.casamento@gmail.com> wrote:

M A,

I guess you could consider the issues and such on github as a todo list.  You are welcome to take on any tasks on there if you like.  Each repo has it's own "Issues" tab where you can see what issues are outstanding.   Or if you can think of features that you think might be useful, please discuss it here and we can all work together to make it happen if it sounds reasonable.

Wrapping an ObjC class around swift sounds interesting, though most people go the other way around these days... Swift->ObjC.

Yours, GC

On Mon, Aug 7, 2023 at 1:58 PM M A <teammember0x01@gmail.com> wrote:
A to do list would be a great edition to this project's website.

One thing I would add it making a program that can wrap an Objective-c class around Swift code. There are just too many classes and methods to do it all by hand.

> On Aug 7, 2023, at 1:51 PM, Gregory Casamento <greg.casamento@gmail.com> wrote:
>
> I am extremely impressed!!!  This is great!  Please let me know if I can help in any way.
>
> GC
>
> On Mon, Aug 7, 2023 at 10:35 AM <dr_clow@me.com> wrote:
> Gregory,
>
> Thank you. I over last night, I was able to solve almost all the issues with calling into GNUStep's AppKit and Foundation. I've been able to set up buttons that respond to selectors, create objects at runtime and register them with the runtime. Right now, I am working on generalizing a sort of "Smart" version of obj_msgSend that allows me to not have to write a separate version of that handles each type parameters. As it is now, the only way I am getting it to work is to make a version of objc_msgSend that explicitly takes, for example, an NSRect, or id.
>
> This is probably because I just don't fully understand how pointers work in Swift. If anyone has any idea of how we can generalize the function, I would greatly appreciate it. Thanks!
>
> Below is a screen shot of a working app written in Swift. The button does work and does open the other window. It's pretty cool. You can see the code on my GitHub. It's messy still. 
>
>
> <Image 8-7-23 at 9.33 AM.jpeg>
>
>> On Aug 6, 2023, at 7:59 PM, Gregory Casamento <greg.casamento@gmail.com> wrote:
>>
>> Hey, I just want you to know that this is VERY VERY cool!!!  Yours, GC
>>
>> On Sun, Aug 6, 2023 at 12:05 PM <dr_clow@me.com> wrote:
>> I have solved the NSWindow initializer issue. I didn't realize I was passing Swift's Foundation.NSRect and not the C version. Sill haven't solved the issues regarding adding new ObjC classes to the runtime at runtime through Swift. Any ideas here would be appreciated. 
>>
>> The image below is an GNUStep app written in Swift. The Menu is from the GORM file from the Terminal (I had to start somewhere!)
>>
>> <Screenshot 2023-08-06 at 10.54.06 AM.png>
>>
>>
>>> On Aug 5, 2023, at 9:03 PM, dr_clow@me.com wrote:
>>>
>>> I just wanted to update everyone on my progress and solicit some help if possible.
>>>
>>> State of my progress:
>>> I've had a lot of success patching into GNUStep's libobjc2 C runtime from within Swift. I've been able to create NSWindows through Swift, call methods, et cetera. You can see my progress here https://github.com/austintatiousness/GNUStepSwiftBridge . This assumes that you're running this from within OnFlapp's GNUStep Desktop.
>>>
>>> Solution to objcSendMessage:
>>> Because Swift doesn't allow variable argument parameters, I had to create various versions of objcSendMessage (e.g forSwift_objcSendMessage1, forSwift_objcSendMessage2, forSwift_objcSendMessage3) to accommodate various number of arguments.
>>>
>>> Problem 1: NSWindow initWithContentRect:styleMask:backing:defer
>>>
>>> 1) I am having trouble with the NSWindow.initWith… functions. I am sure that it is because of the way that I am casting all the values from Swift into to the C implementation. Either I just don't understand how the casting between Swift and C works OR I am just using the wrong variables.  I include a C version of the NSRect struct in my project.
>>>
>>> let  nsWindowClass =  objc_getClass("NSWindow")
>>> var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))
>>>
>>> var styleMask: UInt64 = 1 + 2 + 4
>>> var backingStoreType: UInt64 = 0
>>> var deferr: UInt8 = 0
>>> var rect = NSRect(x: 200, y: 200, width: 300, height: 300)
>>>
>>> allocatedObject = forSwift_objcSendMessage4(&allocatedObject!.pointee, sel_registerName("initWithContentRect:styleMask:backing:defer:"), &rect, &styleMask, &backingStoreType, &deferr)
>>>
>>> I've tried several times to change the various integer types from UInt64 to UInt8 to no avail.
>>>
>>> Problem 2: Registering new classes  with the runtime.
>>> This is the current state of the HelloWorld target:
>>>
>>> For reasons I cannot explain, I am able to allocate a new obj-c object with objc_allocateClassPair and then register it using objc_registerClassPair but when objc_getClass using the same class name that I registered, it returns nil.
>>>
>>> Any help would be appreciated. I am currently unable to make progress on adding delegates with out being able to register new ObjC classes with the runtime.
>>>
>>> Thanks!
>>
>>
>>
>> --
>> Gregory Casamento
>> GNUstep Lead Developer / OLC, Principal Consultant
>> http://www.gnustep.org - http://heronsperch.blogspot.com
>> https://www.patreon.com/bePatron?u=352392 - Become a Patron
>> https://www.openhub.net/languages/objective_c - OpenHub standings
>
>
>
> --
> Gregory Casamento
> GNUstep Lead Developer / OLC, Principal Consultant
> http://www.gnustep.org - http://heronsperch.blogspot.com
> https://www.patreon.com/bePatron?u=352392 - Become a Patron
> https://www.openhub.net/languages/objective_c - OpenHub standings




--
Gregory Casamento
GNUstep Lead Developer / OLC, Principal Consultant
http://www.gnustep.org - http://heronsperch.blogspot.com
https://www.patreon.com/bePatron?u=352392 - Become a Patron



--
Gregory Casamento
GNUstep Lead Developer / OLC, Principal Consultant
http://www.gnustep.org - http://heronsperch.blogspot.com
https://www.patreon.com/bePatron?u=352392 - Become a Patron
https://www.openhub.net/languages/objective_c - OpenHub standings

reply via email to

[Prev in Thread] Current Thread [Next in Thread]