Progress KB - Can methods of a class be invoked dynamically?




Feedback
Did this article resolve your question/issue?

   

Article

Can methods of a class be invoked dynamically?

« Go Back

Information

 
TitleCan methods of a class be invoked dynamically?
URL NameP148058
Article Number000139441
EnvironmentProduct: OpenEdge
Version: All Supported Versions
OS: All supported platforms
Question/Problem Description
Can methods of a class be invoked dynamically?
Can the CALL object be used to invoke object-oriented methods dynamically?
Can object-oriented methods be called that are not known until run time?
What is the DYNAMIC-INVOKE function?
What is the Invoke() method of the Progress.Lang.Class class?
What are the advantages and disadvantages of DYNAMIC-INVOKE vs. Progress.Lang.Class.Invoke()?
Steps to Reproduce
Clarifying Information
Error Message
Defect/Enhancement Number
Cause
Resolution
Prior to OpenEdge 10.2B, methods of a class must be known at compile time to be invoked.

In OpenEdge 10.2B, the DYNAMIC-INVOKE function was introduced in the ABL. This function invokes a class-based method whose name is specified by a run-time expression, but whose parameters are defined at compile time.  An example of DYNAMCI-INVOKE is:
 
DEFINE VARIABLE dValue AS DECIMAL NO-UNDO.
DEFINE VARIABLE iVal32 AS INTEGER NO-UNDO.
DEFINE VARIABLE iVal64 AS INT64 NO-UNDO.
DEFINE VARIABLE iReturn AS INTEGER NO-UNDO.

/* Invoking non-overloaded FooDec (INPUT AS DECIMAL) */
iReturn = DYNAMIC-INVOKE(rObj, "FooDec", INPUT 5).
iReturn = DYNAMIC-INVOKE(rObj, "FooDec", INPUT dValue).
iReturn = DYNAMIC-INVOKE(rObj, "FooDec", INPUT iVal32).
iReturn = DYNAMIC-INVOKE(rObj, "FooDec", INPUT iVal64).
 
The Invoke( ) method of the Progress.Lang.Class class provides similar functionality to the DYNAMIC-INVOKE function, but requires parameters to be set at run time. This method was also introduced in OpenEdge 10.2B.  An example of a generic routine that uses the Invoke method to execute methods at runtime is provided below:
 
/* Program: dynMethodCall.p 
   Purpose: Reads information from a temp-table and converts it into a dynamic invocation of a method.  Saves the result of the method call back into the 
            temp-table to be read by the caller.
   Parameters: pcObject as CHARACTER - Class name to call the method in
               INPUT-OUTPUT TABLE ttParameter - Contains records describing the parameters to be passed to the constructor and a single method call per execution  
                                                The result of the method call is saved into a field of the appropriate data-type and returned with the temp-table. 
                                                Likewise the value returned in OUTPUT parameters is saved into the ttParameter record for the specific parameter 
                                                index it's associated with. 

               Supported method return and parameter data-types:
               CHARACTER, INTEGER, DECIMAL, LOGICAL, DATE, DATETIME, DATETIME-TZ, LONGCHAR, Progress.Lang.Object, TABLE-HANDLE, DATASET-HANDLE

               Parameter types like TABLE & DATASET require a static reference to a statically defined TABLE/DATASET and are therefore not supported since the 
               whole idea of this routine is Dynamic method execution.
   
    This example shows, for the most part, a generic routine for executing static and regular methods, passing parameters and receiving the return value.
 */

USING Progress.Json.ObjectModel.* FROM PROPATH.
USING Progress.Reflect.*.

{ttparam.i}

DEFINE INPUT         PARAMETER pcClass AS CHARACTER   NO-UNDO.
DEFINE INPUT-OUTPUT  PARAMETER TABLE FOR ttParameter.

DEFINE VARIABLE oClass       AS Progress.Lang.Class         NO-UNDO.
DEFINE VARIABLE oConstructor AS Constructor                 NO-UNDO.
DEFINE VARIABLE oMethod      AS Method                      NO-UNDO.
DEFINE VARIABLE ploInstance  AS CLASS Progress.Lang.Object  NO-UNDO.
DEFINE VARIABLE oClsParams   AS Progress.Lang.Parameterlist NO-UNDO.
DEFINE VARIABLE oMtdParams   AS Progress.Lang.Parameterlist NO-UNDO.
DEFINE VARIABLE giCleanupSeq AS INTEGER                     NO-UNDO.
DEFINE VARIABLE hTemp        AS HANDLE                      NO-UNDO.
DEFINE VARIABLE lcTemp       AS LONGCHAR                    NO-UNDO.
DEFINE VARIABLE oTemp        AS "Progress.Lang.Object"      NO-UNDO.
DEFINE VARIABLE oParams      AS Progress.Reflect.Parameter  NO-UNDO EXTENT.

DEFINE TEMP-TABLE ttCleanup NO-UNDO
    FIELD tiCleanupSeq AS INTEGER
    FIELD thHandle     AS HANDLE
    INDEX idxSeq tiCleanupSeq.

FUNCTION addToCleanupList RETURNS LOGICAL ( INPUT phObject AS HANDLE ):
    CREATE ttCleanup.
    ASSIGN giCleanupSeq           = giCleanupSeq + 1
           ttCleanup.tiCleanupSeq = giCleanupSeq
           ttCleanup.thHandle     = phObject.
    RETURN TRUE.
END FUNCTION.

oClass = Progress.Lang.Class:GetClass(pcClass).
RUN createParamList ( INPUT "constructor",
                      OUTPUT oClsParams ).

FIND FIRST ttParameter WHERE ttParameter.tcMethod NE "constructor".
IF AVAILABLE ttParameter AND 
   ttParameter.tlStaticMethod NE TRUE THEN DO:
    oConstructor = oClass:GetConstructor(oClsParams).
    ploInstance = oConstructor:Invoke(oClsParams).    
END.

RUN createParamList ( INPUT "",
                      OUTPUT oMtdParams ).

FIND FIRST ttParameter WHERE ttParameter.tcMethod NE "constructor" NO-ERROR.
oMethod = oClass:GetMethod(ttParameter.tcMethod, oMtdParams).
    
/* Presently this only handles the return parameter.  Output and input-output parameters must be 
   handled by using a variable for the Value in the ParameterList object and keeping that variable
   in scope.  
   
   After the method is executed, check the variable for the value of the specific parameter.  
*/
FOR EACH ttParameter WHERE ttParameter.tcMethod NE "constructor":
    IF ttParameter.tiIndex EQ 0 THEN DO:
        CASE ttParameter.tcDataType:
            WHEN "CHARACTER" THEN
                ASSIGN ttParameter.tcValue = (IF ttParameter.tlStaticMethod THEN
                                                  oClass:Invoke(ttParameter.tcMethod, oMtdParams)
                                               ELSE 
                                                  oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "INTEGER" THEN 
                ttParameter.tiValue = (IF ttParameter.tlStaticMethod THEN
                                           oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                       ELSE 
                                           oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "INT64" THEN
                ttParameter.ti64Value = (IF ttParameter.tlStaticMethod THEN
                                             oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                         ELSE 
                                             oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "DECIMAL" THEN
                ttParameter.tdeValue = (IF ttParameter.tlStaticMethod THEN
                                           oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                       ELSE 
                                           oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "LOGICAL" THEN
                ttParameter.tlValue = (IF ttParameter.tlStaticMethod THEN
                                           oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                       ELSE 
                                           oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "Progress.Lang.Object" THEN
                ttParameter.tPLOValue = CAST((IF ttParameter.tlStaticMethod THEN
                                                  oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                              ELSE 
                                                  oMethod:Invoke(ploInstance, oMtdParams)),Progress.Lang.Object).
            WHEN "HANDLE" THEN
                ttParameter.thValue = (IF ttParameter.tlStaticMethod THEN
                                           oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                       ELSE 
                                           oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "COM-HANDLE" THEN
                ttParameter.tchValue = (IF ttParameter.tlStaticMethod THEN
                                           oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                       ELSE 
                                           oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "DATE" THEN
                ttParameter.tdaValue = (IF ttParameter.tlStaticMethod THEN
                                           oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                       ELSE 
                                           oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "DATETIME" THEN
                ttParameter.tdtValue = (IF ttParameter.tlStaticMethod THEN
                                           oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                       ELSE 
                                           oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "DATETIME-TZ" THEN
                ttParameter.tdtzValue = (IF ttParameter.tlStaticMethod THEN
                                             oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                         ELSE 
                                             oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "CLOB" THEN
                ttParameter.tclbValue = (IF ttParameter.tlStaticMethod THEN
                                             oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                                         ELSE 
                                             oMethod:Invoke(ploInstance, oMtdParams)).
            WHEN "TABLE-HANDLE" THEN DO:
                hTemp = (IF ttParameter.tlStaticMethod THEN
                             oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                         ELSE 
                             oMethod:Invoke(ploInstance, oMtdParams)).
                hTemp:WRITE-JSON("LONGCHAR",lcTemp).
                COPY-LOB FROM lcTemp TO ttParameter.tclbValue.
            END.
            WHEN "DATASET-HANDLE" THEN DO:
                hTemp = (IF ttParameter.tlStaticMethod THEN
                             oClass:Invoke(ttParameter.tcMethod,oMtdParams)
                         ELSE 
                             oMethod:Invoke(ploInstance, oMtdParams)).
                hTemp:WRITE-JSON("LONGCHAR",lcTemp).
                COPY-LOB FROM lcTemp TO ttParameter.tclbValue.
            END.
        END CASE.
        
        RETURN.
    END.
    ELSE DO:
        IF ttParameter.tlStaticMethod THEN
            oClass:Invoke(ttParameter.tcMethod,oMtdParams).
        ELSE
           oMethod:Invoke(ploInstance, oMtdParams).
        
        /* Run once then return */
        RETURN.
    END.
     
END. /* Invoke block */

FINALLY:
    DELETE OBJECT hTemp NO-ERROR.
    FOR EACH ttCleanup:
        DELETE OBJECT thHandle NO-ERROR.
        DELETE ttCleanup.
    END.
END FINALLY.

PROCEDURE createParamList:
    DEFINE INPUT  PARAMETER pcType      AS CHARACTER                   NO-UNDO.
    DEFINE OUTPUT PARAMETER poParamList AS Progress.Lang.Parameterlist NO-UNDO.
    
    DEFINE VARIABLE iParams AS INTEGER                 NO-UNDO.

    DEFINE QUERY q FOR ttParameter SCROLLING.
    
    QUERY q:QUERY-PREPARE("FOR EACH ttParameter WHERE ttParameter.tiIndex GT 0 AND ttParameter.tcMethod " + (IF pcType EQ "constructor" THEN " EQ " ELSE " NE ") + "~"constructor~" BY ttParameter.tcObject BY ttParameter.tcMethod BY ttParameter.tiIndex").
    QUERY q:QUERY-OPEN().
    DO WHILE QUERY q:GET-NEXT():
        iParams = iParams + 1.
    END.
    
    QUERY q:GET-FIRST().
    poParamList = NEW Progress.Lang.Parameterlist(iParams).
    DO WHILE NOT QUERY q:QUERY-OFF-END:
                
        CASE tcDataType:
            WHEN "CHARACTER" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tcValue).
            WHEN "INTEGER" THEN 
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tiValue).
            WHEN "INT64" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.ti64Value).
            WHEN "DECIMAL" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tdeValue).
            WHEN "LOGICAL" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tlValue).
            WHEN "Progress.Lang.Object" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tPLOValue).
            WHEN "HANDLE" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.thValue).
            WHEN "COM-HANDLE" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tchValue).
            WHEN "DATE" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tdaValue).
            WHEN "DATETIME" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tdtValue).
            WHEN "DATETIME-TZ" THEN
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,ttParameter.tdtzValue).
            WHEN "CLOB" THEN DO:
                COPY-LOB FROM ttParameter.tclbValue TO lcTemp.
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,lcTemp).
            END.    
            WHEN "TABLE-HANDLE" THEN DO:
                COPY-LOB FROM ttParameter.tclbValue TO lcTemp.
            
                CREATE TEMP-TABLE hTemp.
                hTemp:READ-JSON("LONGCHAR",lcTemp).
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,hTemp).
                
                addToCleanupList(hTemp).
            END.
            WHEN "DATASET-HANDLE" THEN DO:
                COPY-LOB FROM ttParameter.tclbValue TO lcTemp.
            
                CREATE DATASET hTemp.
                hTemp:READ-JSON("LONGCHAR",lcTemp).
                poParamList:SetParameter(ttParameter.tiIndex,ttParameter.tcDataType,ttParameter.tcIOMode,hTemp).
                
                addToCleanupList(hTemp).
            END.
        END CASE.
        QUERY q:GET-NEXT().
    END.    
END PROCEDURE. 

The advantage of DYNAMIC-INVOKE is that it has a fixed, compile-time parameter list and does not require the creation of a ParameterList object at run time. The advantage of Progress.Lang.Class.Invoke() is that it can be used when the parameters are not known until run time.

The CALL object should not be used to invoke classes or class methods.

The above dynMethodCall.p is attached in demo.zip along with a calling routine (callDynMethod.p) which creates parameter records for a few different scenarios and calls the above procedure to execute them.  The example uses the sports2000 database.
 
Workaround

In OpenEdge 10.1C and 10.2A, the DYNAMIC-NEW statement may be used to instantiate a class whose type is not known until run time. However, a variable must be defined that can accept whatever type the class might be (for example, Progress.Lang.Object will work for any class). Only methods of that known type may be used with the object.

For example, given a class MyClass containing a method myMethod, the following code is correct:

    /* this code is correct */
    DEFINE VARIABLE myObject AS Progress.Lang.Object NO-UNDO.
    myObject = DYNAMIC-NEW 'MyClass' ().
    myObject:ToString().    /* correct: can use a method of Progress.Lang.Object */

However, the following code will not compile because the class MyClass is not known at compile time. There is no alternative way to specify the methods of MyClass at run time.

    /* this code is NOT correct */
    DEFINE VARIABLE myObject AS Progress.Lang.Object NO-UNDO.
    myObject = DYNAMIC-NEW 'MyClass' ().
    myObject:myMethod().    /* will NOT compile */

Notes

References to Other Documentation:

OpenEdge Development: Object-oriented Programming, Chapter 4: "Programming with Class-based Objects"
OpenEdge Development: ABL Reference

Last Modified Date8/2/2018 11:07 PM
Attachment 
Files demo.zip
Disclaimer The origins of the information on this site may be internal or external to Progress Software Corporation (“Progress”). Progress Software Corporation makes all reasonable efforts to verify this information. However, the information provided is for your information only. Progress Software Corporation makes no explicit or implied claims to the validity of this information.

Any sample code provided on this site is not supported under any Progress support program or service. The sample code is provided on an "AS IS" basis. Progress makes no warranties, express or implied, and disclaims all implied warranties including, without limitation, the implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample code is borne by the user. In no event shall Progress, its employees, or anyone else involved in the creation, production, or delivery of the code be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample code, even if Progress has been advised of the possibility of such damages.