Using IBM i? Need to create Excel, CSV, HTML, JSON, PDF, SPOOL reports? Learn more about the fastest and least expensive tool for the job: SQL iQuery.
Being so use to RPG IV, when you need to return a 10-position character value, you code the return for your subprocedure as "10A" and you're done. When that subprocedure is actually a C function, things are little more tricky, but not once you know the secret.
In this blog entry, I am going to illustrate how to "pass" a fixed-length character field from C to RPG IV as a return value of a subprocedure/function call.
In RPG IV, all fields occupy a fixed space. Character and VARYING both occupy the their declared size, in bytes. VARYING occupying 2 or 4 additional bytes for the hidden length.
In C a similar design exists where whatever size a field occupies is fixed. However in C you can look at fields in myriad ways, based on how you reference that variable. When it comes to parameters you have to help the compiler know how long the return value is going to be.
Let's look at an example. I'm using the GetSysName function I created last time (press the Prev link above to visit that blog entry). GetSysName returns an 8-position character value. The RPG IV Prototype looks like this:
D getSysName PR 8A extProc('getSysName')
This prototype has no parameters but has an 8-position character return value. To use in RPG IV, it would be coded as follows:
D SysName 8A /free sysName = getSysName(); /end-free
We all know how to write a subprocedure in RPG IV that returns a character field, but what about C?
Normally a character value such as a string of characters is returned by declaring a point to that value, for example:
char* getSysName()
The issue is that RPG can't deal with this. Why? Because it actually is returning a point to a character, so our example the RPG IV prototype would have had to be modified as follows:
D getSysName PR * extProc('getSysName')
D SysName s 8A D pSysName s * Inz(%Addr(sysName)) /free pSysName = getSysName(); /end-free
The C code for this would be as follows:
char* getSysName() { char sysName[9]; /* Get the system name */ strcpy(sysName,"IBMIRD"); return sysName; }
The problem is this will fail. C returns pointers to the data not the data itself when a character variable is specified. As RPG programmers we tend to avoid the use of pointers so this isn't exactly a great solution. So rather than modify the RPG to handle this let's look at how the C code can be adjust so it returns a fixed-length character variable that RPG expects.
The C Language supports structures similar to RPG. As it turns out if we return a structure as the return value from C, it is returned the same way RPG returns it.
typedef _Packed struct tagSysName8 { char sysname[8]; } SYSNAME8; SYSNAME8 getSysName() { SYSNAME8 sysName; /* Get the system name */ strcpy(sysName,"IBMIRD"); return sysName; }
This returns it as expected by our RPG IV prototype and everything works.
Since there are so many different lengths possible, I've created a C macro to build the size structure correctly. It's called CHARFIELD and accepts one parameter.
#define CHARFIELD(len) \ typedef _Packed struct { char buffer[len]; } CHARFIELD##len
For example:
CHARFIELD(8); CHARFIELD(10); CHARFIELD(20);
This defines structures (typedef's actually) named:
CHARFIELD8 CHARFIELD10 CHARFIELD20
If other sizes are needed just specify the length:
CHARFIELD(x);
And it creates CHARFIELDx for you. Then use that as the datatype of the return value.
Now, the following RPG IV code, works as expected:
D getSysName PR 8A extProc('getSysName') D SysName S 8A /free sysName = getSysName(); /end-free
But this obviously doesn't return the real System Name. To do that we need to create a real C function. Here's my attempt at it:
extern "C" CHARFIELD8 getSysName( void ); CHARFIELD8 getSysName( ) { _MMTR_Template_T mchInfo; short mAttr = _MMTR_APPN; int len = 20; CHARFIELD8 rtnSysName; // Note: System name uses first ~20 bytes. memset(&mchInfo,0x00,sizeof(mchInfo)); mchInfo.Options.Template_Size = len; _MATMATR1( &mchInfo, &mAttr); len = min(sizeof(rtnSysName.buffer,mchInfo.Options.Data.APPN.Name_Len); memset(rtnSysName.buffer,' ',sizeof(rtnSysName.buffer)); memcpy(rtnSysName.buffer,mchInfo.Options.Data.APPN.Sys_Name,len); return rtnSysName; }
Thanks to ILE and the now 21-year old RPG IV compiler, we can call C functions as if they were written in RPG. Of course for this example, we could have embedded the MI instructions right into the RPG code, but we would need to prototype the _MATMATR1 instruction first. If you have a copy of my Cozzi Tools (www.coztools.com) there's a prototype member named MIPROTOS in QCPYSRC that contains many MI prototypes.
What about *CWIDEN? Not needed for this particular prototype, but it could have been specified for those who prefer a more riged rule/standard. Simply prefix the 'getSysName' with *CWIDEN: as follows:
extproc(*CWIDEN: 'getSysName')
Works just as well.