Help with Declare in MacOS

I want to safely remove an external drive in MacOS. (Not looking to use MBS, btw)

Can someone help me format the proper Declare function?

Here’s what I tried, and it doesn’t seem to work.

Declare function unmountAndEjectDeviceAtPath lib "AppKit" selector "unmountAndEjectDeviceAtPath:" (path as CFString) as Boolean Dim Resut as Boolean = unmountAndEjectDeviceAtPath("/Volumes/MyUSBKey")

The Xojo docs say:

I assume that’s the part I’m missing, but I don’t know what the “reference to the class containing the method” means…

Hopefully someone can help a newbie out here!

You’ll find here a great introduction into declares by Jim: https://forum.xojo.com/13244-nssearchfield/2014/7

Basically: unmountAndEjectDeviceAtPath: is an instance method of NSWorkspace as you can see in Apple’s documentation. This means it’s not a framework method that can be called like you defined it, but it must be cast on an instance of NSWorkspace.
This class has a shared instance, so the way would be like:

[code]Declare Function NSClassFromString lib “Foundation” (classname As CFStringRef) As Ptr
// This is a Framework method: No instance to call it on, no Selector

Declare Function sharedWorkspace lib “AppKit” Selector “sharedWorkspace” (classptr as ptr) as ptr
// A class method/property so it must be cast on a Ptr to a class

Declare Function unmountAndEjectDeviceAtPath lib “AppKit” selector “unmountAndEjectDeviceAtPath:” (id as ptr, path as CFStringRef) as Boolean
// Takes two arguments. You can spot that easily by the ObjC name: A colon separates input parameters. If this method would take three parameters like an additional ptr to the device, it would be called “unmountAndEject:deviceAtPath:”

Dim NSWClass As Ptr = NSClassFromString(“NSWorkspace”)
Dim sharedInstance As Ptr = sharedWorkspace(NSWClass)
Dim success as Boolean = unmountAndEjectDeviceAtPath(sharedInstance, “/Volumes/MyUSBKey”)[/code]

EDIT: Only modification to Jim’s introduction would be that CGFloat does not equal Single like he said. It is a floating point data type sized according to the OS, so it’s a Single on 32 Bit systems and a Double on 64 Bit. At the time of his answer, July 2014, Xojo was not 64 Bit capable so it was correct at that time.
No need to worry: Xojo has a GGFloat data type too.

You might also just try using a shell to call “umount”

Thank you, Ulrich, for your detailed reply. I understand a little more now about the crazy way the Apple API is formatted (or objective-C is, I guess…)
Still don’t quite have it, though, even though I tried what you suggested. Any thoughts on what I’ve done wrong?
This routine is an event handler from a window with a menu item that when clicked, runs this routine.
It fails each time. Ejecting the external drive manually from the desktop works fine.

Declare function NSClassFromString lib "Foundation" (ClassName as CFStringRef) As Ptr Declare function sharedWorkspace lib "AppKit" selector "sharedWorkspace" (ClassPtr as Ptr) As Ptr Declare function unmountAndEjectDeviceAtPath lib "AppKit" selector "unmountAndEjectDeviceAtPath:" (ws as Ptr, path as CFStringRef) as Boolean Dim WorkSpace as Ptr = sharedWorkspace(NSClassFromString("NSWorkspace")) Dim dlg As New OpenDialog Dim f As FolderItem = dlg.ShowModal Dim cf as CFStringRef = f.Parent.ShellPath Label1.Text = "Volume to UnMount: " + cf MsgBox("Unmounting...") Dim r as Boolean = unmountAndEjectDeviceAtPath(WorkSpace, cf) if r then MsgBox("Success!") else MsgBox("Failed.") Return True

If there aren’t any syntax errors in the above code, then maybe I should try the other version of this unmount command which returns an error code that might tell me more.

This one’s syntax is even more confusing. I’ve read Jim’s explanation, but I don’t understand what the “at” means or how to use the “throws” parameter or how to re-format this function into Xojo.

[quote]unmountAndEjectDevice(at:)
Attempts to eject the volume mounted at the given path.

Declaration
func unmountAndEjectDevice(at url: URL) throws
Parameters
url
The URL of the volume to eject.
error
If the operation fails, this error contains more information about the failure.[/quote]

Thank you so much for your help!

Ok, I figured out the error. You had it right, but my unfamiliarity with MacOS got me into trouble.

I had to change:

Dim cf as CFStringRef = f.Parent.ShellPath

to:

Dim cf as CFStringRef = f.Parent.NativePath

And then it worked.
Now, I’m still very interested in creating a version of this function that returns an error code. If I had used that, it would have told me that the path was invalid or didn’t exist or something…

Oh, is it true that all libs can be “Cocoa” instead of using “Foundation” and “AppKit”, etc.?

With NSWorkspace you’d have to use unmountAndEjectDeviceAtURL:error: then.
You’d need a few more declares to build a NSURL and an NSError instance first and then two more to read ErrorCode and LocalizedReason. But it’s not that complicated.

If you want to try it, I’d encourage you to read the documentation of both classes and try to figure out the declares for their constructors. These Init methods must always be called on a ptr to their class like above.
I’ll assist you with the code then.

Yes. I developed the habit of naming them “fully qualified” because a lot of code can be easily shared between macOS and iOS projects then. All the core frameworks are equal or very similar, but the GUI code differs (and UIKit will be included in macOS later), and as I am using declares rather often, I can simply import the framework modules I need.
But if your project will be Desktop only, it’s completely fine to use “Cocoa” instead.

You have read the Swift documentation. Better change the documentation to show the ObjC code as we need that naming.
“Throws” in Swift means that the method throws an error if it fails and you have to pass an NSError instance as a byref’ed container for the error details.

[quote=393864:@Ulrich Bogun]With NSWorkspace you’d have to use unmountAndEjectDeviceAtURL:error: then.
You’d need a few more declares to build a NSURL and an NSError instance first and then two more to read ErrorCode and LocalizedReason. But it’s not that complicated.

If you want to try it, I’d encourage you to read the documentation of both classes and try to figure out the declares for their constructors. These Init methods must always be called on a ptr to their class like above.
I’ll assist you with the code then.[/quote]
You’re right, it’s better to learn by doing. Thank you for the tips - I’ll give it a try and see how far I get.

[quote]Yes. I developed the habit of naming them “fully qualified” because a lot of code can be easily shared between macOS and iOS projects then. All the core frameworks are equal or very similar, but the GUI code differs (and UIKit will be included in macOS later), and as I am using declares rather often, I can simply import the framework modules I need.
But if your project will be Desktop only, it’s completely fine to use “Cocoa” instead.[/quote]Thanks for that - I’ll probably keep it separate as I’ve been doing, easier to follow I think.

[quote]You have read the Swift documentation. Better change the documentation to show the ObjC code as we need that naming.
“Throws” in Swift means that the method throws an error if it fails and you have to pass an NSError instance as a byref’ed container for the error details.[/quote]
Aha! That makes a huge difference. :slight_smile: