Converting C Prototypes to RPG

VERSION 1 Published

Created on: Nov 13, 2008 12:42 PM by barbara_morris - Last Modified:  Nov 13, 2008 12:44 PM by barbara_morris

For RPG programmers who have access to C prototypes for functions, and want to call those functions from RPG.

Average User Rating
(1 rating)




Aug 24, 2009 2:03 PM Click to view barbara_morris's profile barbara_morris says:

Raymond, for a callback, you use a normal prototype except that you code a procedure pointer for the EXTPROC keyword. Then before you call using the prototype, you set the EXTPROC procedure pointer.

Assuming you got passed the callback function pointer as subfield "callbackFn" of data structure "parm":

In C you would code something like this:

   typedef float (*myfn_t)(int i);  
   myfn_t *myfn;
   
   myfn = parm->callbackFn;
   x = myfn(3);

In RPG you would code like this:
D myfn     pr      8f   extproc(myfnProcptr)
D   i             10i 0 value                  

   myfnProcptr = parm.callbackFn;
   x = myfn(3);

Aug 24, 2009 10:00 PM Click to view Raymond_Everhart's profile Raymond_Everhart says: in response to: barbara_morris

I have a working sample in C, but I can't seem to pass the right RPG structure to the C program that will execute the callback. Could I send you the sample code?

Thanks!

Aug 25, 2009 8:56 AM Click to view barbara_morris's profile barbara_morris says: in response to: Raymond_Everhart

Hi Raymond, could you post it here? (Enclose the various bits of code inside two tags "{ code }" (but with no spaces between the curly braces and "code").

If it's the C program that's executing the callback, you don't need a special prototype for RPG. Your RPG code that passes the callback pointer to C would look something like this:

D myfn       pr      8f   extproc('myfn') 
D   i               10i 0 value           
D mystruct   ds           qualified align 
D   callback          *   procptr         
D cfn        pr           extproc('cfn')  
D   info                  likeds(mystruct)
 /free                                    
    mystruct.callback = %paddr(myfn);     
    cfn (mystruct); // calls back to myfn 
    *inlr = '1';                          
 /end-free                                
...
    mystruct.callback = %paddr(myfn);
    cfn (mystruct); // calls back to myfn
...
P myfn       b                 
D myfn       pi      8f        
D   i               10i 0 value
 ...

And the C code like this:
typedef float (*callback_t)(int i); 
typedef struct                      
{                                   
   callback_t callback;             
} MYSTRUCT_T;                       
void cfn(MYSTRUCT_T *mystruct)      
{                                   
   double x = mystruct->callback(3);
}                                   

Aug 25, 2009 1:46 PM Click to view Raymond_Everhart's profile Raymond_Everhart says: in response to: barbara_morris

Here are some lines fom the header file:

typedef RFC_RC (SAP_API* RFC_SERVER_FUNCTION)(RFC_CONNECTION_HANDLE rfcHandle, RFC_FUNCTION_HANDLE funcHandle, RFC_ERROR_INFO* errorInfo);

    /* ***********************************************************************/
    /*                                                                       */
    /*  Installation of Callback Functions for RFC Servers                   */
    /*                                                                       */
    /* ***********************************************************************/

        /**
         * \brief  Installs a callback function of type RFC_SERVER_FUNCTION, which will be triggered when a request for
         * the function module corresponding to funcDescHandle comes in from the R/3 system corresponding to sysId.
         * \ingroup installer
         *
         * If you pass NULL as "sysId", the serverFunction will be used for calls from any backend system.
         *
         * The main inputs of RFC_SERVER_FUNCTION are as follows:
         * - RFC_CONNECTION_HANDLE\n            A connection handle, which can be used to query logon information about
         *                                              the current (backend) user or to make callbacks into the backend.
         * - RFC_FUNCTION_HANDLE\n                      A data container that represents the current function call. Read the importing
         *                                              parameters, which came from the backend, from this container via the RfcGetX functions and
         *                                              write the exporting parameters, which are to be returned to the backend, into this container
         *                                              using the RfcSetX functions.\n
         *                                              The memory of that container is automatically released by the RFC Runtime after the
         *                                              RFC_SERVER_FUNCTION returns.
         * - RFC_ERROR_INFO*\n                  If you want to return an ABAP Exception or ABAP Message to the backend, fill the
         *                                              parameters of that container and return RFC_ABAP_EXCEPTION or RFC_ABAP_MESSAGE from
         *                                              your RFC_SERVER_FUNCTION implementation.\n
         *                                              If you want to return a SYSTEM_FAILURE to the backend, fill the message parameter of
         *                                              this container and return RFC_EXTERNAL_FAILURE from your RFC_SERVER_FUNCTION implementation.
         * If your RFC_SERVER_FUNCTION implementation processed the function call successfully, you should return RFC_OK.
         *
         *
         * \inout *sysId System ID of the R/3 system, for which this function module implementation shall be used.
         * If you set this to NULL, this server function will be used for calls from all backends, for whose SysID no
         * explicit server function has been installed.
         *
         *
         * \in *sysId The System ID of the backend for which this server function is to be used, or NULL in case the
         * function can be used for calls from all systems.
         * \in funcDescHandle A function description giving the name of the function module and its parameters.
         * \in serverFunction Pointer to a C function of type RFC_SERVER_FUNCTION.
         * \out *errorInfo Not much that can go wrong here.
         * \return RFC_RC
         */
        DECL_EXP RFC_RC SAP_API RfcInstallServerFunction(SAP_UC const *sysId, RFC_FUNCTION_DESC_HANDLE funcDescHandle, RFC_SERVER_FUNCTION serverFunction, RFC_ERROR_INFO* errorInfo);


Here is the C code that works....
#include <stdlib.h>
#include <stdio.h>
#include "sapnwrfc.h"

static int listening = 1;

#if defined(SAPonOS400) || defined(SAPwithPASE400)
        /* o4fprintfU, o4fgetsU
         * calling o4xxxU instead of xxxU produces much smaller code,
         * because it directly expands to xxxU16, while xxxU expands to
         * as4_xxx  which links against the whole pipe check and handling for ILE.
     * This creates an executable containing almost the whole platform
         * specific kernel and needs the ILE O4PRTLIB in a special library.
         * Because we have no pipe usage of fxxxU here I use o4xxxU.
         * ATTENTION:
         * In any case the above mentioned functions are efectively used for
         * pipes, the redefinition below corrupts this functionality
         * because than the pipe handling is not called for our platform.
         */
        #undef fprintfU
        #define fprintfU o4fprintfU
        #undef fgetsU
        #define fgetsU o4fgetsU
#endif


void errorHandling(RFC_RC rc, SAP_UC description[], RFC_ERROR_INFO* errorInfo, RFC_CONNECTION_HANDLE connection){
        printfU(cU("%s: %d\n"), description, rc);
        printfU(cU("%s: %s\n"), errorInfo->key, errorInfo->message);
        // It's better to close the TCP/IP connection cleanly, than to just let the
        // backend get a "Connection reset by peer" error...
        if (connection != NULL) RfcCloseConnection(connection, errorInfo);

        exit(1);
}

RFC_RC SAP_API stfcDeepTableImplementation(RFC_CONNECTION_HANDLE rfcHandle, RFC_FUNCTION_HANDLE funcHandle, RFC_ERROR_INFO* errorInfoP){
        RFC_ATTRIBUTES attributes;
        RFC_TABLE_HANDLE exportTab = 0;
        RFC_STRUCTURE_HANDLE tabLine = 0;
        RFC_TABLE_HANDLE importTab = 0;
        RFC_ERROR_INFO errorInfo ;
        RFC_CHAR buffer[257]; //One for the terminating zero
        RFC_INT intValue;
        RFC_RC rc;
        RFC_CHAR matnr[19]; //One for the terminating zero
        RFC_CHAR valid[2]; //One for the terminating zero
        unsigned tabLen = 0, strLen;
        unsigned  i = 0;
        buffer[256] = 0;
        matnr[18] = 0;
        valid[1] = 0;

        printfU(cU("\n*** Got request for STFC_DEEP_TABLE from the following system: ***\n"));

        RfcGetConnectionAttributes(rfcHandle, &attributes, &errorInfo);
        printfU(cU("System ID: %s\n"), attributes.sysId);
        printfU(cU("System No: %s\n"), attributes.sysNumber);
        printfU(cU("Mandant  : %s\n"), attributes.client);
        printfU(cU("Host     : %s\n"), attributes.partnerHost);
        printfU(cU("User     : %s\n"), attributes.user);

        //Print the Importing Parameter
        printfU(cU("\nImporting Parameter:\n"));
        RfcGetChars(funcHandle, cU("MATNR"), matnr, 18, 0);
        printfU(cU("Item Number : %s\n"), matnr);

        //Print the Importing Parameter
        RfcSetChars(funcHandle, cU("VALID"), cU("Y"), 1, 0);
        printfU(cU("Valid : %s\n"), valid);

        if (strncmpU(cU("STOP"), matnr , 4) == 0) listening = 0;
        return RFC_OK;

        printfU(cU("\nImporting Parameter:\n"));
        RfcGetTable(funcHandle, cU("IM_Record"), &exportTab, &errorInfo);

        RfcGetRowCount(importTab, &tabLen, &errorInfo);
        printfU(cU("EXPORT_TAB (%d lines)\n"), tabLen);
        for (i=0; i<tabLen; i++){
                RfcMoveTo(importTab, i, &errorInfo);
                printfU(cU("\t\t-line %d\n"), i);

                RfcGetInt(importTab, cU("I"), &intValue, &errorInfo);
                printfU(cU("\t\t\t-I:\t%d\n"), intValue);
                RfcGetString(importTab, cU("C"), buffer, 11, &strLen, &errorInfo);
                printfU(cU("\t\t\t-C:\t%s\n"), buffer);
                // Check for the stop flag:
                if (i==0 && strncmpU(cU("STOP"), buffer, 4) == 0) listening = 0;
                RfcGetStringLength(importTab, cU("STR"), &strLen, &errorInfo);
                if (strLen > 256) printfU(cU("UTF8_STRING length bigger than 256: %d. Omitting the STR field...\n"), strLen);
                else{
                        RfcGetString(importTab, cU("STR"), buffer, 257, &strLen, &errorInfo);
                        printfU(cU("\t\t\t-STR:\t%s\n"), buffer);
                }
                RfcGetStringLength(importTab, cU("XSTR"), &strLen, &errorInfo);
                if (strLen > 128) printfU(cU("XSTRING length bigger than 128: %d. Omitting the XSTR field...\n"), strLen);
                else{
                        RfcGetString(importTab, cU("XSTR"), buffer, 257, &strLen, &errorInfo);
                        printfU(cU("\t\t\t-XSTR:\t%s\n"), buffer);
                }
        }

        //Now set the Exporting Parameters
        printfU(cU("\nSetting values for Exporting Parameters:\n"));
        printfU(cU("Please enter a value for RESPTEXT:\n> "));
    /*CCQ_SECURE_LIB_OK*/
        getsU(buffer);
    /*CCQ_SECURE_LIB_OK*/
        RfcSetChars(funcHandle, cU("RESPTEXT"), buffer, strlenU(buffer), &errorInfo);
        printfU(cU("\nPlease enter the number of lines in EXPORT_TAB:\n> "));
    /*CCQ_SECURE_LIB_OK*/
        getsU(buffer);
        tabLen = atoiU(buffer);
        RfcGetTable(funcHandle, cU("EXPORT_TAB"), &exportTab, &errorInfo);
        for (i=0; i<tabLen; i++){
                tabLine = RfcAppendNewRow(exportTab, &errorInfo);
                printfU(cU("Line %d\n"), i);
                printfU(cU("\tPlease enter a value for C [CHAR10]:> "));
        /*CCQ_SECURE_LIB_OK*/
                getsU(buffer);
        /*CCQ_SECURE_LIB_OK*/
                RfcSetChars(tabLine, cU("C"), buffer, strlenU(buffer), &errorInfo);
                printfU(cU("\tPlease enter a value for I [INT4]:> "));
        /*CCQ_SECURE_LIB_OK*/
                getsU(buffer);
                RfcSetInt(tabLine, cU("I"), atoiU(buffer), &errorInfo);
                printfU(cU("\tPlease enter a value for STR [UTF8_STRING]:> "));
        /*CCQ_SECURE_LIB_OK*/
                fgetsU(buffer, 257, stdin); // For these fields better make sure, the user doesn't bust our buffer...
        /*CCQ_SECURE_LIB_OK*/
                strLen = strlenU(buffer) - 1;
                // In contrast to gets, fgets includes the linebreak... Very consistent...
                RfcSetString(tabLine, cU("STR"), buffer, strLen, &errorInfo);
                mark: printfU(cU("\tPlease enter a value for XSTR [XSTRING]:> "));
        /*CCQ_SECURE_LIB_OK*/
                fgetsU(buffer, 257, stdin);
        /*CCQ_SECURE_LIB_OK*/
                strLen = strlenU(buffer) - 1;
                // In contrast to gets, fgets includes the linebreak... Very consistent...
                rc = RfcSetString(tabLine, cU("XSTR"), buffer, strLen, &errorInfo);
                if (rc != RFC_OK){
                        printfU(cU("\tInvalid value for XSTR. Please only use hex digits 00 - FF.\n"));
                        goto mark;
                }
        }
        printfU(cU("**** Processing of STFC_DEEP_TABLE finished ***\n\n"));

        return RFC_OK;
}

int mainU(int argc, SAP_UC** argv){
        RFC_RC rc;
        RFC_FUNCTION_DESC_HANDLE stfcDeepTableDesc;
        RFC_CONNECTION_PARAMETER repoCon[8], serverCon[3];
        RFC_CONNECTION_HANDLE repoHandle, serverHandle;
        RFC_ERROR_INFO errorInfo;

        serverCon[0].name = cU("program_id");   serverCon[0].value = cU("Z_READ_MAPICS_GP");
        serverCon[1].name = cU("gwhost");               serverCon[1].value = cU("sapd01ci");
        serverCon[2].name = cU("gwserv");               serverCon[2].value = cU("sapgw68");

        repoCon[0].name = cU("client"); repoCon[0].value = cU("050");
        repoCon[1].name = cU("user");           repoCon[1].value = cU("userid");
        repoCon[2].name = cU("passwd"); repoCon[2].value = cU("buggywhip");
        repoCon[3].name = cU("lang");           repoCon[3].value = cU("EN");
        repoCon[4].name = cU("ashost"); repoCon[4].value = cU("sapd01ci");
        repoCon[5].name = cU("sysnr");  repoCon[5].value = cU("68");

        printfU(cU("Logging in..."));
        repoHandle = RfcOpenConnection (repoCon, 6, &errorInfo);
        if (repoHandle == NULL) errorHandling(errorInfo.code, cU("Error in RfcOpenConnection()"), &errorInfo, NULL);
        printfU(cU(" ...done\n"));

        printfU(cU("Fetching metadata..."));
        stfcDeepTableDesc = RfcGetFunctionDesc(repoHandle, cU("Z_READ_MAPICS_IM"), &errorInfo);
        // Note: STFC_DEEP_TABLE exists only from SAP_BASIS release 6.20 on
        if (stfcDeepTableDesc == NULL) errorHandling(errorInfo.code, cU("Error in Repository Lookup"), &errorInfo, repoHandle);
        printfU(cU(" ...done\n"));

        printfU(cU("Logging out..."));
        RfcCloseConnection(repoHandle, &errorInfo);
        printfU(cU(" ...done\n"));

        rc = RfcInstallServerFunction(NULL, stfcDeepTableDesc, stfcDeepTableImplementation, &errorInfo);
        if (rc != RFC_OK) errorHandling(rc, cU("Error Setting "), &errorInfo, repoHandle);

        printfU(cU("Registering Server..."));
        serverHandle = RfcRegisterServer(serverCon, 3, &errorInfo);
        if (serverHandle == NULL) errorHandling(errorInfo.code, cU("Error Starting RFC Server"), &errorInfo, NULL);
        printfU(cU(" ...done\n"));

        printfU(cU("Starting to listen...\n\n"));
        while(RFC_OK == rc || RFC_RETRY == rc || RFC_ABAP_EXCEPTION == rc){
                rc = RfcListenAndDispatch(serverHandle, 120, &errorInfo);
                printfU(cU("RfcListenAndDispatch() returned %s\n"), RfcGetRcAsString(rc));
                switch (rc){
                        case RFC_RETRY: // This only notifies us, that no request came in within the timeout period.
                                                    // We just continue our loop.
                                printfU(cU("No request within 120s.\n"));
                                break;
                        case RFC_ABAP_EXCEPTION:        // Our function module implementation has returned RFC_ABAP_EXCEPTION.
                                                                // This is equivalent to an ABAP function module throwing an ABAP Exception.
                                                                // The Exception has been returned to R/3 and our connection is still open.
                                                                // So we just loop around.
                                printfU(cU("ABAP_EXCEPTION in implementing function: %s\n"), errorInfo.key);
                                break;
                        case RFC_NOT_FOUND:     // R/3 tried to invoke a function module, for which we did not supply
                                                            // an implementation. R/3 has been notified of this through a SYSTEM_FAILURE,
                                                            // so we need to refresh our connection.
                                printfU(cU("Unknown function module: %s\n"), errorInfo.message);
            /*FALLTHROUGH*/
            case RFC_EXTERNAL_FAILURE:  // Our function module implementation raised a SYSTEM_FAILURE. In this case
                                                                        // the connection needs to be refreshed as well.
                                printfU(cU("SYSTEM_FAILURE has been sent to backend.\n\n"));
            /*FALLTHROUGH*/
                        case RFC_COMMUNICATION_FAILURE:
                        case RFC_ABAP_MESSAGE:          // And in these cases a fresh connection is needed as well
            default:
                serverHandle = RfcRegisterServer(serverCon, 3, &errorInfo);
                                rc = errorInfo.code;
                                break;
                }

                // This allows us to shutdown the RFC Server from R/3. The implementation of STFC_DEEP_TABLE
                // will set listening to false, if IMPORT_TAB-C == STOP.
                if (!listening){
                        RfcCloseConnection(serverHandle, NULL);
                        break;
                }
        }

        return 0;
}                                                                         


The RPG version is not registering the server function correctly (RFCInstallServerFunction) because the function is not found when I make the same request from the client app......

Aug 25, 2009 2:48 PM Click to view barbara_morris's profile barbara_morris says: in response to: Raymond_Everhart

What is your RPG prototype for RFCInstallServerFunction and how are you calling it from RPG?

Aug 25, 2009 3:25 PM Click to view Raymond_Everhart's profile Raymond_Everhart says: in response to: barbara_morris

Here is the Prototype to the C Function:

D InstallFunc     PR            10u 0 ExtProc(*CWIDEN:  

D                                             'RfcInstallServerFunction')
D   SysID                         *   Value options(*string)
D   FnHandle                      *   Value options(*string)
D   ProcPtr                           LikeDS(FunctionDS)
D   ErrPtr                        *   Value options(*string)

D FunctionDS      DS                  Qualified  Align
D  FunctionPtr                    *   ProcPtr


Here is the call:
D InitServer      PI              N
D  FunctionName                 32A   Value
D  FuncDS                             LikeDS(FunctionDS) 

 * Error Parameter Structures
D ErrorDS         DS
D   Code                        10U 0
D   Code2                       10U 0
D   Key                        128A
D   Message                   1024A    


D ErrData         DS                  LikeDS(ErrorDS)
D ErrPtr          S               *   Inz(%addr(ErrData)) 
D FnHandle        S               *
D FnStrucHndle    S               *       
D Null            S              1A   Inz(x'00')  
D SvrPtr          S               *   Inz(%Addr(Server))
D Server          S             10A

/Free                            
   Server = Null;
   RC = InstallFunc(SvrPtr:FnStrucHndle:FuncDS:ErrPtr); 
/End-Free                    

Sep 4, 2009 5:07 PM Click to view Raymond_Everhart's profile Raymond_Everhart says: in response to: Raymond_Everhart

Did I post too much / too little source code?

Sep 4, 2009 6:48 PM Click to view barbara_morris's profile barbara_morris says: in response to: Raymond_Everhart

Sorry for the delay, I was away on vacation.

I can see a few differences between the C and RPG versions.

For the first parameter, in the C version, you are passing a null pointer. In the RPG version you are passing a pointer to an zero-length string.

Your C call is like this:

rc = RfcInstallServerFunction(NULL, stfcDeepTableDesc, 
     stfcDeepTableImplementation, &errorInfo);

And your RPG call is similar to this C call:
rc = RfcInstallServerFunction("", stfcDeepTableDesc, 
     stfcDeepTableImplementation, &errorInfo);


For the second parameter, did you call RfcGetFunctionDesc() to set the value of FnStrucHndle before you called
InstallFunc?

For the third parameter, I think you are supposed to pass a procedure pointer by value, and you are passing a data structure by reference. Instead of defining it as a
LIKEDS of your structure, I think it should be defined as a procedure pointer by value. And then
on the call, you should pass FuncDS.functionPtr.

The fourth parameter is an output parameter. It should be defined with LIKEDS of your ErrorDS, and then you should pass ErrorDS as the parameter. When you code the parameter as a pointer with options(*string), the compiler will copy your errorDS structure to a temporary and pass the address of the temporary. If the API has any error information to pass back, it would put it in the temporary, and you wouldn't be able to access it.

One style note, if I may ... when I'm porting code from C to RPG, I find it much easier to debug it when I use the exact same names in the RPG code as the C code. That way, it's easier to find the matching C code when there's a problem with the RPG version. So instead of using say InstallFunc for the RPG prototype name, I would use RfcInstallServerFunction as the RPG name.
In case you haven't seen the way to code long names in RPG, it's like this:
D RfcInstallServerFunction...
D                 PR            10u 0 ExtProc(*CWIDEN:  
D                                             'RfcInstallServerFunction')
D   SysID                         *   Value options(*string)

I would use the same names for all the prototypes, parameters, data structures, subfields and even temporary variables, as much as possible.

Bottom Banner