Main block references ignored in internal procedures
When the called procedure is a persistent procedure, its ProDataSet definition will naturally be in the main block of the procedure, that is, outside of any internal procedure. A ProDataSet definition is in fact not even allowed in an internal procedure. However, if its internal procedures are ever passed a ProDataSet parameter BY-REFERENCE, it is important that you not reference the ProDataSet handle in any way in the main block if you expect the effect of that reference to be visible in the internal procedures. This case is insidious enough to merit a specific example and diagram.
Here is a simple procedure that defines the dsOrder ProDataSet, runs another procedure persistent, and then runs an internal procedure to fill the ProDataSet:
/* refCaller.p */
{dsOrderTT.i}
{dsOrder.i}
DEFINE VARIABLE hProc AS HANDLE NO-UNDO.
RUN refCallee.p PERSISTENT SET hProc.
RUN fillProc IN hProc (OUTPUT DATASET dsOrder BY-REFERENCE).
Here is the procedure it runs. It defines its own instance of the ProDataSet and then uses its handle to attach three Data-Sources. Inside the internal procedure fillProc, it fills the ProDataSet and returns it as an OUTPUT parameter, as shown:
/* refCallee.p */
{dsOrderTT.i}
{dsOrder.i}
DEFINE VARIABLE hDset AS HANDLE NO-UNDO.
DEFINE DATA-SOURCE srcOrder FOR Customer.
DEFINE DATA-SOURCE srcOline FOR OrderLine.
DEFINE DATA-SOURCE srcItem FOR Item.
PROCEDURE fillProc:
DEFINE OUTPUT PARAMETER DATASET FOR dsOrder.
DATASET dsOrder:FILL().
END PROCEDURE.
If you run the main procedure refCaller.p, you get the following error:
What happened? All three Data-Sources were attached in the main block, so why can the AVM not see them?
The reason is that the instance of dsOrder defined in the main block, the one whose handle was used to attach the Data-Sources, is not the one used by the internal procedure. Because the ProDataSet is passed in by reference, fillProc is pointing to the instance of dsOrder defined in refCaller.p, which has no Data-Sources. A few messages confirm this.
Here we display the ProDataSet handle in the calling procedure:
RUN refCallee.p PERSISTENT SET hProc.
MESSAGE "In the calling proc, dsOrder is " DATASET dsOrder:HANDLE
VIEW-AS ALERT-BOX.
RUN fillProc IN hProc (OUTPUT DATASET dsOrder BY-REFERENCE).
Also, in the main block of the persistent procedure refCallee.p:
MESSAGE "In the main block, dsOrder is " DATASET dsOrder:HANDLE
VIEW-AS ALERT-BOX.
And, in the internal procedure fillProc:
PROCEDURE fillProc:
DEFINE OUTPUT PARAMETER DATASET FOR dsOrder.
MESSAGE "In the fillProc, dsOrder is " DATASET dsOrder:HANDLE
VIEW-AS ALERT-BOX.
DATASET dsOrder:FILL().
END PROCEDURE.
Run refCaller.p again and you can see the proof. When refCallee.p is first run, it gets a handle for its own ProDataSet. For example:
Next, the calling procedure displays the handle of its copy of the ProDataSet:
Now it runs fillProc:
You can see that fillProc's ProDataSet has the same handle as the one in the calling procedure refCaller.p. In fact, it is the same ProDataSet, the one with no Data-Sources.
If you change the persistent procedure to do all its work in the internal procedure, then everything works, as shown:
/* refCallee.p */
{dsOrderTT.i}
{dsOrder.i}
DEFINE DATA-SOURCE srcOrder FOR Customer.
DEFINE DATA-SOURCE srcOline FOR OrderLine.
DEFINE DATA-SOURCE srcItem FOR Item.
PROCEDURE fillProc:
DEFINE OUTPUT PARAMETER DATASET FOR dsOrder.
DEFINE VARIABLE hDset AS HANDLE NO-UNDO.
The following figure shows how the ProDataSet is filled in the called program and passed back as an OUTPUT parameter to the calling program.
Figure 9. Passing ProDataSets
Procedure refCallee.p has a definition of dsOrder, but the ProDataSet instance this represents is replaced by the one from refCaller.p when its ProDataSet is passed BY-REFERENCE. All internal references to hDset are therefore invalid because they point to a ProDataSet instance that is not being used. This teaches two important lessons, as described in the following design tips.
Do not set ProDataSet handles at the main procedure level when they will be accessed in internal procedures. Set the handle variables where they are used to capture a reference to an externally defined ProDataSet.
It is always good practice to perform operations such as attaching Data-Sources locally to where they are needed. It is essential if the ProDataSet is being passed by reference.
Note: These procedures use the standard include files for the temp-table and ProDataSet definitions. Adding the REFERENCE-ONLY option to these definitions would improve the performance of these procedures by avoiding the instantiation of the called routine's objects. It would also avoid the run-time errors by telling ABL at compile time that the called procedure's ProDataSet is not actually being used.