Managing Object LifecycleΒΆ
As discussed in Object Lifecycle, Q2Pix uses reference counting for managing the lifecycle of objects, and the client code must follow just a simple rule:
Q2Pix Reference Counting Rule
Every object reference returned either as the result of a method, or through
a method parameter, must be released with a call to
ImgObj_Release
when no longer needed.
Let’s see how this rule is applied to the first scenario presented in that chapter:
- A form method creates an Image Document object.
- The form method assigns the object to an Image Area.
- The form contains buttons that allow the user to rotate the image right or left in the area.
The form method would look like this:
// ============================================= // ImageViewer form method Case of : (Form event=On Load) C_TEXT($docRef) $docRef:=ImgDoc_CreateWithFile ("/path/to/image.tiff") ImgArea_SetDocument (xImgArea;$docRef) ImgObj_Release ($docRef) End case
First, the form method creates an Image Document reference from an image file on disk with a call
to the ImgDoc_CreateWithFile
method. At this point the document’s retain count is 1
.
Then the document reference is assigned to plug-in area xImgArea
with a call to
ImgArea_SetDocument
. The plug-in area retains the document reference,
because it is needed for displaying. At this point the document’s
retain count is incremented by one and becomes 2
.
Finally the form method releases the document reference because it is no longer needed.
Now the document’s retain count is decremented by one, and becomes 1
.
Next let’s have a look how the "RotateImageRight"
button in the viewer form would be coded:
// ============================================= // ImageViewer.RotateImageRight object method C_TEXT($docRef) $docRef:=ImgArea_GetDocument (xImgArea) C_LONGINT($frameIdx) $frameIdx:=ImgArea_GetFrame (xImgArea) RasterFrame_RotOrientationLeft ($docRef;$frameIdx) ImgObj_Release ($docRef)
After the execution of the On Load
form method in the previous example, the
retain count of the document assigned to the area is 1
.
The button’s method first retrieves the document reference from the plug-in area
with a call to ImgArea_GetDocument
. The plug-in area returns the
document reference with its retain count incremented by one, which now equals 2.
Then the object reference is passed to RasterFrame_RotOrientationLeft
to do the orientation transformation.
Finally, the button method releases the document reference because it is no
longer needed. The document’s retain count is decreased by one, dropping to 1
again.
When the form window is closed and the plug-in area is destroyed, or when another
document is assigned to the area, the area will release its current document.
The document’s retain count will decrease by one becoming 0
, and the document
will be destroyed.
Let’s see how the second scenario presented in that chapter would be coded:
- A project method attached to a menu item presents the standard file selection dialog allowing the user to select an image file.
- After the user selects a file, the project method verifies that the selected file is in a readable format by creating an Image Document object with the selected file.
- If the verification step succeeds, the project method passes the image document reference to a new process for processing or display.
While this seems to be straightforward to implement, just pass the document object to the new process and let it retain the reference for its own use, it is a bit tricky due to the nature of 4D’s multi-process runtime environment.
First, let’s examine the simplistic/naive approach to this problem.
The menu method would look like this:
// ============================================= // MENU_SELECT_IMAGE project method. // *** Warning: WRONG implementation*** C_TEXT($imagePath) $imagePath:=ImgDlog_PromptOpenFile ("Select an image") If ($imagePath#"") C_TEXT($imgDocRef) $imgDocRef:=ImgDoc_CreateWithFile ($imagePath) // Create the document reference. If ($imgDocRef#"") // This is a supported image C_LONGINT($handlerProcess) // Pass the document reference to the new process as a parameter. $handlerProcess:=New process("HANDLE_IMAGE";1024*1024;\ "$HANDLE_IMAGE";$imgDocRef) ImgObj_Release ($imgDocRef) // Release the reference we created. End if End if
// ============================================= // HANDLE_IMAGE project method. // *** Warning: WRONG implementation*** C_TEXT($1;$imgDocRef) $imgDocRef:=$1 // Retain the document reference to keep it around. $imgDocRef:=ImgObj_Retain ($1) // ... // Work with the image // ... ImgObj_Release ($imgDocRef) // Release the document reference.
The problem with this approach is the following: it is not guaranteed by 4D’s runtime
that between the calls to New process
and ImgObj_Release
in the
MENU_SELECT_IMAGE
method, the new process implemented by the HANDLE_IMAGE
method has got a chance to run and execute the ImgObj_Retain
call to
keep the document reference alive for its own use.
In other words, there is a good chance that the call sequence would look like this:
MENU_SELECT_IMAGE
:ImgDoc_CreateWithFile
-> the document’s retain count is1
.MENU_SELECT_IMAGE
:New process
-> the document’s retain count is still1
.MENU_SELECT_IMAGE
:ImgObj_Release
-> the document’s retain count becomes0
and it is destroyed.HANDLE_IMAGE
:ImgObj_Retain
-> failure: the document has already been destroyed.HANDLE_IMAGE
: ...failure to work with the image document.HANDLE_IMAGE
:ImgObj_Release
-> failure: the document has already been destroyed.
instead of the expected/desired sequence:
MENU_SELECT_IMAGE
:ImgDoc_CreateWithFile
-> the document’s retain count is1
.MENU_SELECT_IMAGE
:New process
-> the document’s retain count is still1
.HANDLE_IMAGE
:ImgObj_Retain
-> the document’s becomes2
.MENU_SELECT_IMAGE
:ImgObj_Release
-> the document’s retain count becomes1
.HANDLE_IMAGE
: ...work with the image document.HANDLE_IMAGE
:ImgObj_Release
-> the document’s retain count becomes0
and it is destroyed.
The right way to implement this scenario is to ensure the correct call execution order by using some sort of synchronization:
// ============================================= // MENU_SELECT_IMAGE project method. C_TEXT($imagePath) $imagePath:=ImgDlog_PromptOpenFile ("Select an image") If ($imagePath#"") C_TEXT($imgDocRef) $imgDocRef:=ImgDoc_CreateWithFile ($imagePath) // Create the document reference. If ($imgDocRef#"") // This is a supported image // Use a process variable as a signal C_BOOLEAN(gGotTheImage) gGotTheImage:=False C_LONGINT($handlerProcess) // Pass the document reference to the new process as a parameter. $handlerProcess:=New process("HANDLE_IMAGE";1024*1024;"$HANDLE_IMAGE";$imgDocRef) // Wait until $handlerProcess signals us (or dies) C_BOOLEAN($done;$handlerDead) Repeat GET PROCESS VARIABLE($handlerProcess;gGotTheImage;gGotTheImage) $handlerDead:=(Process state($handlerProcess)<0) Until (gGotTheImage | $handlerDead) ImgObj_Release ($imgDocRef) // Release the reference we created. End if End if
// ============================================= // HANDLE_IMAGE project method. C_TEXT($1;$imgDocRef) $imgDocRef:=ImgObj_Retain ($1) // Grab a reference to the image document C_BOOLEAN(gGotTheImage) gGotTheImage:=True // Signal the caller // ... // Work with the image // ... ImgObj_Release ($imgDocRef) // Release the reference to the image document
Here the two methods agree upon a protocol to coordinate the call sequence: a process variable that is used as a signal.
The MENU_SELECT_IMAGE
method waits after the New process
call
for the new process to signal before continuing execution
and calling ImgObj_Release
.
The HANDLE_IMAGE
method first retains the document reference it receives
as a parameter, and then signals the caller method via process variable. This
ensures the correct call sequence.
Finally, when HANDLE_IMAGE
is done the document reference is released and
the semaphore is destroyed before ending the process.
So, the typical pattern when working with Q2Pix objects is:
- Create an object with some plug-in or component wrapper method.
- Work with the object.
- Release the object when no longer needed.
Or, when some entity (method, module, or process) receives an existing object reference instead of creating a new one:
- Retain the object.
- Work with the object.
- Release the object when no longer needed.
When passing object references between processes, there must be some sort of synchronization between the participating processes in order to retain/release object references correctly. A simple way to implement such synchronization is with the use of a process variable as described above.