Working with blocks (NSBlock)

Mac OSX specific forum
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Working with blocks (NSBlock)

Post by wilbert »

Quite a few new Cocoa methods require blocks.
http://clang.llvm.org/docs/Block-ABI-Apple.html

I tried to implement them using PureBasic.
It's important that the block is passed to CocoaMessage by reference using @ otherwise it crashes.

Edit:
Tried to create a bit cleaner implementation as a module
http://www.purebasic.fr/english/viewtop ... 18#p467818

Alternative module
http://www.purebasic.fr/english/viewtop ... 75#p473875


Code: Select all

; *** Global Block code ***

!extern __NSConcreteGlobalBlock
Macro GlobalBlock(BlockName, FunctionName, Sig32, Sig64)
  DataSection
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      FunctionName#_bl_sig:
      !db Sig64, 0
      FunctionName#_bl_desc:
      Data.i 0, 32, ?FunctionName#_bl_sig, 0
      FunctionName#_bl_lit:
      !dq __NSConcreteGlobalBlock
    CompilerElse
      FunctionName#_bl_sig:
      !db Sig32, 0
      FunctionName#_bl_desc:
      Data.i 0, 20, ?FunctionName#_bl_sig, 0
      FunctionName#_bl_lit:
      !dd __NSConcreteGlobalBlock
    CompilerEndIf
    Data.l $50000000, 0
    Data.i @FunctionName#(), ?FunctionName#_bl_desc
  EndDataSection
  BlockName = CocoaMessage(0, ?FunctionName#_bl_lit, "copy")  
EndMacro

; *** End of Global Block code ***



; *** Procedure to wrap inside a block ***

ProcedureC EventMonitor(*Block, Event)
  AddGadgetItem(0, -1, "test")
  ProcedureReturn Event
EndProcedure

; *** Wrap the procedure ***

GlobalBlock(*Block, EventMonitor, '@"NSEvent"8@?0@"NSEvent"4', '@"NSEvent"16@?0@"NSEvent"8')

; *** Use the block (use @ to pass the block!) ***

CocoaMessage(0, 0, "NSEvent addLocalMonitorForEventsMatchingMask:", 2, "handler:@", @*Block); *Block is executed when left mouse button is pressed

; *** Main code ***

If OpenWindow(0, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
  EditorGadget(0, 10, 10, 175, 240)
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf
Last edited by wilbert on Fri Oct 09, 2015 9:52 am, edited 3 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Working with blocks

Post by wilbert »

I updated my first post. It seems to be working now.
It would be much more convenient if a signature string could be a simple null pointer but I don't understand exactly from the documentation I found if that is allowed or not.

With a null pointer signature it could simply be like this (llvm-gcc-4.2 seems to do it like this) ...

Code: Select all

; *** Global Block code ***

DataSection
  !extern __NSConcreteGlobalBlock
  block_descriptor:
  Data.i 0, SizeOf(Integer)*3+8, 0, 0
EndDataSection

Macro GlobalBlock(BlockName, FunctionName)
  DataSection
    FunctionName#_bl_lit:
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !dq __NSConcreteGlobalBlock
    CompilerElse
      !dd __NSConcreteGlobalBlock
    CompilerEndIf
    Data.l $30000000, 0
    Data.i @FunctionName#(), ?block_descriptor
  EndDataSection
  Global BlockName = ?FunctionName#_bl_lit  
EndMacro

; *** End of Global Block code ***



; *** Procedure to wrap inside a block ***

ProcedureC EventMonitor(*Block, Event)
  AddGadgetItem(0, -1, "test")
  ProcedureReturn Event
EndProcedure

; *** Wrap the procedure ***

GlobalBlock(*Block, EventMonitor)

; *** Use the block (use @ to pass the block!) ***

CocoaMessage(0, 0, "NSEvent addLocalMonitorForEventsMatchingMask:", 2, "handler:@", @*Block); *Block is executed when left mouse button is pressed

; *** Main code ***

If OpenWindow(0, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
  EditorGadget(0, 10, 10, 175, 240)
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf
Last edited by wilbert on Sun Jul 19, 2015 1:10 pm, edited 1 time in total.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Working with blocks

Post by luis »

Too much advanced for me now but bookmarked for future reference, thanks for sharing your experiments :wink:
"Have you tried turning it off and on again ?"
A little PureBasic review
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Working with blocks

Post by wilbert »

Reworked my NSBlock code a bit into a module.
Hopefully it works :)
Once a NSBlockWithPtr is created, make sure it stays at the same memory location !

Module code

Code: Select all

; NSBlock module by Wilbert

; last update July 17, 2015

DeclareModule NSBlock
  
  Structure NSBlock
    *isa
    flags.l
    reserved.l
    *invoke
    *descriptor
  EndStructure
  
  Structure NSBlockWithPtr
    *block.NSBlock
    _block.NSBlock
  EndStructure
  
  Declare InitNSBlockWithPtr(*NSBlockWithPtr.NSBlockWithPtr, *Invoke, IsGlobal = #True)
  
EndDeclareModule

Module NSBlock
  
  Global.i NSConcreteGlobalBlock, NSConcreteStackBlock, DyLib
  
  DyLib = OpenLibrary(#PB_Any, "libSystem.dylib")
  If DyLib
    NSConcreteGlobalBlock = GetFunction(DyLib, "_NSConcreteGlobalBlock")
    NSConcreteStackBlock = GetFunction(DyLib, "_NSConcreteStackBlock")    
    CloseLibrary(DyLib)
  EndIf
  
  If Not NSConcreteGlobalBlock
    MessageRequester("Error", "Unable to access _NSConcreteGlobalBlock symbol")
    End
  EndIf
  
  DataSection
    NSBlockDescriptor:
    Data.i 0, SizeOf(NSBlock), 0, 0
  EndDataSection
  
  Procedure InitNSBlockWithPtr(*NSBlockWithPtr.NSBlockWithPtr, *Invoke, IsGlobal = #True)
    *NSBlockWithPtr\block = @*NSBlockWithPtr\_block
    With *NSBlockWithPtr\_block
      If IsGlobal
        \isa = NSConcreteGlobalBlock
        \flags = $30000000
      Else
        \isa = NSConcreteStackBlock
        \flags = $20000000
      EndIf
      \invoke = *Invoke
      \descriptor = ?NSBlockDescriptor
    EndWith
  EndProcedure
  
EndModule

; *** End of NSBlock module ***
Example 1

Code: Select all

UseModule NSBlock

ProcedureC EventMonitor(*Block.NSBlock, Event)
  AddGadgetItem(0, -1, "test")
  ProcedureReturn Event
EndProcedure

InitNSBlockWithPtr(@MyBlockWithPtr.NSBlockWithPtr, @EventMonitor())

CocoaMessage(0, 0, "NSEvent addLocalMonitorForEventsMatchingMask:", 2, "handler:@", @MyBlockWithPtr)

; *** Main code ***

If OpenWindow(0, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
  EditorGadget(0, 10, 10, 175, 240)
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf
Example 2

Code: Select all

UseModule NSBlock

ProcedureC BlockEnumeration(*Block.NSBlock, id, idx, *stop.Byte)
  Debug PeekS(CocoaMessage(0, id, "UTF8String"), -1, #PB_UTF8)
  If idx = 2
    *stop\b = #True; stop after index 2 (string 3)
  EndIf
EndProcedure

InitNSBlockWithPtr(@MyBlockWithPtr.NSBlockWithPtr, @BlockEnumeration())


NSMutableArray = CocoaMessage(0, 0, "NSMutableArray arrayWithCapacity:", 3)
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 1")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 2")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 3")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 4")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 5")

CocoaMessage(0, NSMutableArray, "enumerateObjectsUsingBlock:@", @MyBlockWithPtr)
Previous 2 examples combined with a Map.
This is to show it's possible to use a Map but it might be better to use global variables since it are supposed to be global blocks :wink:

Code: Select all

UseModule NSBlock

NewMap Block.NSBlockWithPtr()


ProcedureC EventMonitor(*Block.NSBlock, Event)
  AddGadgetItem(0, -1, "test")
  ProcedureReturn Event
EndProcedure

ProcedureC BlockEnumeration(*Block.NSBlock, id, idx, *stop.Byte)
  Debug PeekS(CocoaMessage(0, id, "UTF8String"), -1, #PB_UTF8)
  If idx = 2
    *stop\b = #True; stop after index 2 (string 3)
  EndIf
EndProcedure


InitNSBlockWithPtr(@Block("EventMonitor"), @EventMonitor())
InitNSBlockWithPtr(@Block("BlockEnumeration"), @BlockEnumeration())

NSMutableArray = CocoaMessage(0, 0, "NSMutableArray arrayWithCapacity:", 3)
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 1")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 2")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 3")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 4")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 5")
CocoaMessage(0, NSMutableArray, "enumerateObjectsUsingBlock:@", @Block("BlockEnumeration"))

CocoaMessage(0, 0, "NSEvent addLocalMonitorForEventsMatchingMask:", 2, "handler:@", @Block("EventMonitor"))

; *** Main code ***

If OpenWindow(0, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
  EditorGadget(0, 10, 10, 175, 240)
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf
Great example from Shardik for a modal sheet :)
http://www.purebasic.fr/english/viewtop ... 43#p471843
Windows (x64)
Raspberry Pi OS (Arm64)
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Working with blocks (NSBlock)

Post by wilbert »

I created an alternative to my previous NSBlock module.
I'll leave the previous code also posted but this one might be a bit easier to use if you need to work with multiple blocks.
It works by creating a temporary block instead of a global one.

Module code

Code: Select all

; *** NSBlock alternative module ***

; last update Okt 7, 2015

DeclareModule NSBlock
  
  Structure NSBlock
    *isa
    flags.l
    reserved.l
    *invoke
    *descriptor
  EndStructure
  
  Declare Block(*Invoke)
  
EndDeclareModule

Module NSBlock
  
  Structure NSBlockWithPtr
    *block.NSBlock
    _block.NSBlock
  EndStructure
  
  DataSection
    NSBlockPtr:
    !extern __NSConcreteStackBlock
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !dq 0, __NSConcreteStackBlock, 0x20000000, 0
    CompilerElse
      !dd 0, __NSConcreteStackBlock, 0x20000000, 0, 0
    CompilerEndIf
    Data.i ?NSBlockDescriptor
    NSBlockDescriptor:
    Data.i 0, SizeOf(NSBlock), 0, 0
  EndDataSection
  
  Procedure Block(*Invoke)
    Protected *Block.NSBlockWithPtr = CocoaMessage(0, CocoaMessage(0, 0, "NSData dataWithBytes:", ?NSBlockPtr, "length:", SizeOf(NSBlockWithPtr)), "bytes")
    *Block\block = @*Block\_block
    *Block\_block\invoke = *Invoke
    ProcedureReturn *Block
  EndProcedure
  
EndModule

; *** End of NSBlock alternative module ***
Example

Code: Select all

UseModule NSBlock

ProcedureC EventMonitor(*Block.NSBlock, Event)
  AddGadgetItem(0, -1, "test")
  ProcedureReturn Event
EndProcedure

ProcedureC BlockEnumeration(*Block.NSBlock, id, idx, *stop.Byte)
  Debug PeekS(CocoaMessage(0, id, "UTF8String"), -1, #PB_UTF8)
  If idx = 2
    *stop\b = #True; stop after index 2 (string 3)
  EndIf
EndProcedure

NSMutableArray = CocoaMessage(0, 0, "NSMutableArray arrayWithCapacity:", 3)
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 1")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 2")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 3")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 4")
CocoaMessage(0, NSMutableArray, "addObject:$", @"String 5")
CocoaMessage(0, NSMutableArray, "enumerateObjectsUsingBlock:@", Block(@BlockEnumeration()))

CocoaMessage(0, 0, "NSEvent addLocalMonitorForEventsMatchingMask:", 2, "handler:@", Block(@EventMonitor()))

; *** Main code ***

If OpenWindow(0, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
  EditorGadget(0, 10, 10, 175, 240)
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf
Another example (PB 5.40+)

Code: Select all

UseModule NSBlock

ProcedureC EventMonitor(*Block.NSBlock, Event)
  Protected Flags = CocoaMessage(0, Event, "modifierFlags")
  If Flags & #NSShiftKeyMask
    AddGadgetItem(0, -1, "Shift")
  EndIf
  ProcedureReturn Event
EndProcedure

CocoaMessage(0, 0, "NSEvent addLocalMonitorForEventsMatchingMask:", #NSFlagsChangedMask, "handler:@", Block(@EventMonitor()))

; *** Main code ***

If OpenWindow(0, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
  EditorGadget(0, 10, 10, 175, 240)
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf
Windows (x64)
Raspberry Pi OS (Arm64)
Post Reply