1. 24
    1. 5

      This was a fun article, thanks for sharing!

      The expression [pboard clearContents] sends the clearContents message to our pboard object. Clearing the pasteboard before writing is recommended in the documentation.

      This bit caused me to have to page some things back in from the back of my brain. The OpenStep pasteboard supports multiple types and you can set each type separately. If you don’t clear the contents, then the second call will replace (or set) the contents only for the type that you specify. This means that, for example, if someone copies an image + alt text, or some rich text + the plain text version, then you run your code without clearing the contents, then pasting into a plain-text field will work (it will get the text contents) but pasting into something that can take rich text or an image will get the existing contents. If you are writing more than one type, then you have to call declareTypes:owner:, which starts a transaction with the pasteboard server allowing you to (from the perspective of other programs pasting) atomically set multiple types, avoiding the case where two applications copy disjoint types to the pasteboard and pasting applications see them as disjoint.

      It would have been nice to add -declareTypes:owner: even though it isn’t needed here, because that makes the code extensible if the Lua code wants to, for example, set image data and alt text.

      Once again, here is the full code, and we will look at it closer after:

      I think this code is correct, but it’s worth noting that the Objective-C version also has the compiler insert memory management calls. The Apple docs surprised me a bit because they describe generalPasteboard as:

      @property(class, readonly, strong) NSPasteboard *generalPasteboard;

      This suggests that the caller is responsible for calling release on it. I think that’s actually fine because it’s a singleton and will have a custom release method that does nothing, but by convention the pre-ARC version was treated as autoreleased (so you didn’t call -release on it).

      The C version would probably be better if it grabbed the pasteboard object once and cached both it and the selectors. Looking up a selector like this is a fairly expensive operation but it will always return the same value and so doing it once in an initialisation phase is a good idea (mostly because it makes the code a bit more readable - here you’re doing IPC, so the perf difference doesn’t matter).

      #import <Carbon/Carbon.h>

      Not sure what happened there. Carbon went away a long time ago and the prior example used cocoa.h. The rest of this section keeps using Carbon when it means Cocoa as well. If the Carbon APIs (which were C, not Objective-C) were still around, this whole section would be somewhat redundant.

      In fact, it looks as if these APIs haven’t gone away completely (I thought they didn’t survive the 64-bit transition). You can still get a reference to a pasteboard, clear the contents, and so on via C APIs. Doing this means that you’d need only to link CoreFoundation and the Application Services Framework, not the Objective-C runtime, Foundation, and AppKit.

      We need this because this object is defined at runtime

      I don’t believe this is the case, the NSPasteboardTypeString is just a constant string. This is passed to the Pasteboard server and so nothing actually depends on object identity here (it doesn’t have to be this string object, just one with the same contents) and so you could create your own. I added the __builtin___NSStringMakeConstantString builtin to clang for this precise purpose: it creates a constant NSString object from a constant C string and works in C (and across platforms, so the same thing will also work with GNUstep). Dump the contents of the string and create your own and you can avoid that import.

      Note that, while interesting, the C version doesn’t actually have any benefits. It generates worse code than the Objective-C version and still needs to link to the same set of libraries. I don’t know how stable Apple’s pasteboard protocol is, but you might be able to do something smaller by opening the right Mach port and talking directly to it, though that would require some reverse engineering. It’s also worth noting that Objective-C++ does to C++ what Objective-C does to C, so you can expose this directly from Sol3 (and it even gets the memory management right automatically, since Objective-C types are exposed to C++ as things with non-default copy constructors and destructors that handle the reference counting).

      1. 1

        Thanks for the kind words and all the feedback! I have made some changes to the post based on your corrections.

        #import <Carbon/Carbon.h>

        Not sure what happened there.

        Wow thanks for catching that. Another project of mine is rewriting an old Macintosh game from the Carbon era, and Carbon and Cocoa look similar enough at a glance that I guess I typed it out and never questioned it because I have spent some time in Carbon recently. The code example itself does use the right header though. I hadn’t realized that Carbon is still around though, thanks for sharing those links!

        I don’t believe this is the case, the NSPasteboardTypeString is just a constant string.

        Yep you are right, thanks for the correction

        Note that, while interesting, the C version doesn’t actually have any benefits.

        Yep, it was just for fun :)

    2. 3

      I love reading articles like these where each link is a rabbit hole to follow.

      1. 1

        That was my goal, so I’m happy to hear you enjoyed it!

    3. 2

      Thanks for sharing how to call Objective-C methods with C! Since LuaJIT has an FFI module that will generate C buildings on the fly, I tried writing to the NSPasteboard with LuaJIT:

      ffi = require("ffi")
      C = ffi.C
      typedef struct objc_object *id;
      typedef struct objc_selector *SEL;
      id objc_getClass(const char*);
      SEL sel_registerName(const char*);
      id objc_msgSend(id,SEL);
      id NSPasteboardTypeString;
      input = ffi.cast("char*", arg[1] or io.read())
      pboard = C.objc_msgSend(C.objc_getClass("NSPasteboard"), C.sel_registerName("generalPasteboard"))
      C.objc_msgSend(pboard, C.sel_registerName("clearContents"))
      str = ffi.cast("id(*)(id,SEL,char*)", C.objc_msgSend)(C.objc_getClass("NSString"),
      ret = ffi.cast("bool(*)(id,SEL,id,id)", C.objc_msgSend)(pboard,