For RPG programmers who have access to C prototypes for functions, and want to call those functions from RPG.
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!
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
...
typedef float (*callback_t)(int i);
typedef struct
{
callback_t callback;
} MYSTRUCT_T;
void cfn(MYSTRUCT_T *mystruct)
{
double x = mystruct->callback(3);
}
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);
#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;
}
What is your RPG prototype for RFCInstallServerFunction and how are you calling it from RPG?
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
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
Did I post too much / too little source code?
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);
rc = RfcInstallServerFunction("", stfcDeepTableDesc,
stfcDeepTableImplementation, &errorInfo);
D RfcInstallServerFunction...
D PR 10u 0 ExtProc(*CWIDEN:
D 'RfcInstallServerFunction')
D SysID * Value options(*string)

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:
In RPG you would code like this: