Using ObjC "Block" parameters in 64 bit builds

Apple documents the structures used for Block functions here.

Basically, one creates two structures and can then pass the one’s address to an ObjC function that takes a Block parameter. Here’s the gist of the Xojo code for that:

[code]structure Block
isa_ as Ptr
flags As Integer
reserved As Integer
invoke As Ptr
descriptor As Ptr
user_id as Integer
user_data as Ptr
end

static blockCallback as Ptr = CType (AddressOf theCallbackMethod, Ptr)

static gBlockDesc as new MemoryBlock (16)
gBlockDesc.Int32Value (4) = Block.Size

declare function dlsym lib “System” (hdl as Integer, symbol as CString) as Ptr
static isaPtr as Ptr = dlsym (-2, “_NSConcreteGlobalBlock”)

mBlock.isa_ = isaPtr
mBlock.flags = &h10000000 ’ BLOCK_IS_GLOBAL = (1 << 28)
mBlock.invoke = blockCallback
mBlock.descriptor = gBlockDesc[/code]

Now, while this works in 32 bit builds, I cannot get it working in 64 bit. The Block structure should be working just fine in 64 bit, and I changed the gBlockDesc code to:

static gBlockDesc as new MemoryBlock (32) gBlockDesc.Int64Value (8) = Block.Size
Yet, this always crashes when I invoke the block.

Has anyone figured this out and gotten Blocks to work this way in 64 bit builds, i.e. without using a Plugin?

Doesn’t the declare require a consistent set of arguments?
By that I mean… it requires either 32bit all the time, or 64bit all the time…
and if it worked as a 32bit compile then INTEGER was a 32bit value, but in a 64bit compile its a 64bit value

What if you used UINT32 or INT32? to force it to use 32bit values

@Dave The structs in C are using “int” for the types, and the equivalent in Xojo is Integer. Also, the constistency in the Mac APIs is in the source, not in the binary form. So, if a C header says “int”, that means 32 bit values in 32 bit builds, and 64 bit values in 64 bit builds.

Although, what puzzles me, now that you’re pointing that out, is that the first struct uses “int” whereas the other uses “long int”…

For those who want to poke at the code, I’ve made a simple demo project:

http://files.tempel.org/RB/MacBlockTest.xojo_binary_project

The 64 bit build crashes, the 32 build works.

Gee, @Dave S - thanks for questioning my assumptions. I was indeed declaring the structure incorrectly.

Correct is:

  • “int” does translate to “Int32” in Xojo.
  • “long” does translate to “Integer” in Xojo.

So, if I change the two “Integer” types in the Block structure to Int32, then the code works.

Whew!

I have updated the test project, fixing the 64 bit issue, and also making it work better with Xojo objects (avoiding the use of global properties).

[quote=402921:@Thomas Tempelmann]“int” does translate to “Int32” in Xojo.
“long” does translate to “Integer” in Xojo.[/quote]
This is something that has puzzled me for just a short time ago too. The C definitions are sometimes really a bit unclear (at least for me), stating that long is “at least 32 Bits long” while longlong is 64 Bits …

The safest way to figure this out is to use a C compiler (or Xcode), choose your architecture, and do something like:

NSLog(@"%ld", sizeof(long));

A tip for you, make sure that the blocks you’re testing with come back in on the main thread (like a NSSavePanel), many of them come back in on a different thread and this causes a StackOverFlowException. IAP & AVFoundation are the two frameworks off the top of my head which do this.

I have a feedback request for this, but I don’t have feedback on this computer.

We have block handling for multithreading in dozens of plugin parts.
Passing parameters causes trouble with retain/release correctly.

What Sam means is that the callback function would do this, if it could be called in a thread:

[code]Private Shared Sub taskCompletionUnsafeThread(ByRef blk as Block, error_ref as Ptr)
#pragma StackOverflowChecking false

declare sub dispatch_sync_f lib SysLib (q as Ptr, ByRef context as Block, f as Ptr)
declare function dispatch_get_current_queue lib SysLib () as Ptr

declare sub retain lib CocoaLib selector “retain” (id as Ptr)
declare sub release lib CocoaLib selector “release” (id as Ptr)

blk.user_data = error_ref

if gMainQueue = dispatch_get_current_queue() then
// we’re on the main thread - must not call dispatch_sync to avoid deadlock
taskCompletionOnMainThread (blk)
else
// we’re doing a sync call so that any objects remain retained until they’re handled by the callback on the main thread
dispatch_sync_f (gMainQueue, blk, gCompletionHandler)
end if
End Sub[/code]

Some globals would need setting up before:

declare function dispatch_get_current_queue lib SysLib () as Ptr gMainQueue = dispatch_get_current_queue() gCompletionHandler = CType (AddressOf taskCompletionOnMainThread, Ptr)

taskCompletionOnMainThread is the final callback function that would get invoked safely.

I’ll update my sample code accordingly for the sake of others who might need this.

It never occurred to me you can cast a delegate to a ptr. Thanks a lot!

The new code at http://files.tempel.org/RB/MacBlockTest.xojo_binary_project also shows how to convert a Ptr into a CFStringRef, i.e. by using CFRetain / CFRelease, since you cannot cast a Ptr to a CFStringRef.

I used same trick before in my Kits.
Declare CFRetain with CFStringRef as result type. :slight_smile:

Pretty brilliant. I used to create an NSString and use its CFStringRef property …
Cancel the conference, I learned enough new stuff today!

EDIT: @Thomas, do I see this right your code contains a very robust ObjC blocks replacement without the need for a plug-in?

Convinced. :smiley:

Yes. As long as you create a structure for each different type of block, and know how to deal with the callback in case it’s called asynchronously (i.e. from a different thread) - which is discussed here, for instance.