Navigation:  Appendix B: Dolphin Pattern Book > External Interfacing Patterns >

External Callback

Previous pageReturn to chapter overviewNext page

Context

Interfacing with external software systems involves not only calling externally implemented functions, but also implementing functions to be used by external systems (callbacks). We need a generic mechanism for implementing callbacks from other languages in Smalltalk.

Solution

Implement an ExternalCallback block which can be passed like a function pointer to external systems to enable them to call back into Dolphin. The steps are given below but see the section on External Callbacks for further information.

1.Define any new External Structure classes for any structure or pointer parameters you are going to use in the callback block which are not already defined.
2.Define a block with the correct number of arguments for the callback procedure, inside which you implement your callback functionality. The block can contain whatever code is necessary to implement the callback. For many callbacks, the return value is important, e.g. to continue or terminate enumerations. The return value is the result of the last expression in the block, and should be an object with a conversion to 32-bit integer when sent #asLRESULT.
3.You may need to add an External Method Selector to the appropriate External Library to enable you to register your callback function. Use an lpvoid parameter type for the callback argument (the function pointer), and send the external callback the #asParameter message to persuade it to answer its machine code thunk.
4.Define an argument descriptor string describing the callbacks parameter types mapped to the appropriate validation. The type string does not include the return type (the result answered by the block will be sent the #asLRESULT message for conversion to a 32-bit return value, the only possible return type at present).
5.Create an ExternalCallback instance using the #block:argumentTypes: message, passing (respectively) the callback block and the argument descriptor string. You must retain a reference to this external callback object to prevent it being garbage collected, as although the ExternalCallback class maintains a register of its instances, the register is a Weak Collection.
If the callback is for the use of a control (or some other type of window) which has a SendMessage() interface, then you may need to define an appropriate External Structure for a parameter block, or you may have to pass the external callback directly as the lParam.
If a particular callback is frequently created, then consider using a pre-created ExternalDescriptor (which holds a "compiled" representation of the argument type string) in conjunction with the ExternalCallback>>block:descriptor: instantiator. This technique is illustrated in the example below.
6.Pass your external callback object to the library method you defined, and wait for the callbacks to come pouring in!
7.When you've finished with the callback, you can explicitly #free it. If you're not sure when you'll have finished with it, then simply leave it to be finalized when Dolphin garbage collects it.

Examples

An important callback example in the development system is that implemented for streaming text out of a rich edit control in RichTextEdit. RichTextEdit actually defines two callbacks (one for streaming in, and one for streaming out), but that for streaming out is as follows:

streamIn: aStream format: streamFormat

  "Private - Read text from the stream aStream"

  | answer callback text size |

  callback :=

      ExternalCallback

          block: [ :dwCookie :pbBuff :cb :pcb |

                  text := aStream nextAvailable: cb.

                  size := text size.

                  pbBuff replaceFrom: 1 to: size with: text startingAt: 1.

                  pcb value: size.

                  0

          descriptor: ##(ExternalDescriptor argumentTypes: 'dword lpvoid sdword DWORD*').

 

  winStruct

      pfnCallback: callback asParameter yourAddress.

  answer := self sendMessage: EM_STREAMIN wParam: streamFormat lpParam: winStruct.

  self setModify: false.

  "It seems we have to increase the limit again after streaming in."

  self setMaxTextLimit.

  streamIn isNil ifFalse: [streamIn free].

  streamIn := callback.

  ^answer

 

Notice how the arguments to the block are already objects of suitable types, and can be used directly, even the DWORD* parameter.

Like any other block, the callback block captures the environment in which it was created, so we can directly reference all the closure information we need (e.g. the argument to the method, aStream)

The interface to the rich edit control is SendMessage() based, and so uses a parameter block to hold the function pointer and cookie ("extra data" or closure information). We don't need the cookie, so we just pass 0, and ignore the argument in the callback block.

Known Uses

RichTextEdit control RTF streaming (as in the previous example).
Enumeration of Locale specific time and date formats.
Enumeration of Font characteristics.
Enumerating top-level Views