Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

Overview

As a continuation of the documents on using PowerBuilder to connect up with SAP (ERP), this document will contain the basic information on calling an RFC (Remote Function Call) inside SAP and how to consume the results.  The example here will use an incredibly useful resource for the development process when dealing with SAP data:  RFC_READ_TABLE.  At a later date I'll publish some actual demo PBL's.

Before delving off into the action, it should be noted that RFC_READ_TABLE, while useful, is something you will want to use VERY sparingly...  It's SLOW!  It's something I use in the development process to gain live data from SAP in something like a SQL format and/or for a few data extraction processes.  Being that it's slow, it's also going to be a resource hog...  Using it on a production server is quite often frowned upon.  Rather than using RFC_READ_TABLE in a compiled and rolled-out application, it's suggested you do one of the following:

  1. Use a standard SAP RFC to access the data
  2. Bust out some ABAP and develop your own RFC (either from scratch or by extending a current RFC)
  3. Contact your ABAP developer and have them develop an RFC for you

I will cover some other useful RFC's down the road in a later document which will help with the selection process of the RFC's in SAP.  For now, we need to take the second step after connecting to SAP (this example will build off it):  Creating an RFC to use in the PowerBuilder world.  In the first part, we added an ActiveX control to a visual object which handles and holds our connection to SAP.  There are actually several different ActiveX controls you can use which will handle/hold the connection to SAP, but the one we chose is specifically designed for handling RFC's...  Programming with a purpose!

Inside the Machine

Let's take a look at what the function looks like within SAP!  The transaction to do that is SE37...  If you're familiar with SAP and ABAP, you already knew this.  If not, there's a good chance you won't have access to it.  Fortunately the internet has a wealth of knowledge on the standard SAP RFC's.  A simple Google search of something like "SAP RFC_READ_TABLE" will provide you with a plethora of knowledge on this one single function inside SAP.  Anyway, below is the SE37 entry screen...  Simply type in your function module name and hit the display button.

This will bring you into the view-only area of the function module where you can start to get an idea of how complex/easy a function module is to work with.  Let's look at the "Attributes" tab first.  The key item to pay attention to here is listed under "Processing Type" as "Remote-Enabled Module".  If this option is not checked, you cannot use the function module outside of the SAP system (also referenced as "RFC-enabled").  To use a non-RFC-enabled function, you will have to create a wrapper for it...  It's not as simple as going into change mode and selecting it, as there are very specific rules on what is / is not allowed for RFC-enabled function modules.  You also never want to change a standard function module, as there are likely 10's/100's/1,000's of other function modules referencing it.  Below is a screen shot of what that first tab should look like for RFC_READ_TABLE.  (hint:  many of the standard RFC's inside SAP are prefixed with "RFC_")

Onward we go to the next tab:  "Import".  These are the arguments (inputs) for the function module.  From the image below, we see a table of arguments.  Most of it is fairly straightforward...  name, type, default, optional, by value, and description.  Some of the typing and by value data will never change for RFC-enabled function modules, as it's part of the requirements.  We will reference this tab later.

The next tab is the Export tab.  This shows us the output of the function module.  But wait...  There are no outputs!  That's because anything on the Export tab is a single object...  If there are multiple objects returned (or inputted), they will be returned in table format (and you can guess which tab that's on).

Think of it this way: If you have a function to return material details, you're going to return a single material object, which would be located on the "Export" tab.  If you're using a function that lists materials based on criteria, you're going to return a list of material objects, which would be located on the "Tables" tab.

The next tab we're going to delve into for this document is the "Tables" tab.  The name "Tables" is not referencing tables in the database sense, but more like an array of objects.  For some reason this was a concept which took me a few minutes to realize.  We'll go deeper into this later.

The next two screenshots are of the remaining two tabs, which I'm not going to explain much on in this part.  For all intents and purposes, the "Exceptions" tab is the pre-defined errors you can incur calling the function module and "Source Code" is just that:  The Source Code (ABAP) (often commented in German).

Hi Ho, It's Off to Work We Go!

Now that we have seen some of the internals of the function module we will use in this example, let's get to work using it!  Our first step will be to add another function to the visual object which is storing the ActiveX RFC control for SAP (referenced as oRFC).  Let's call the function of_SAP_GetRFC( ), with a single string argument of as_rfc_name which returns an OLEObject.  The objective is simple:  Return a reference to an RFC inside SAP in OLE form.  And fortunately, the coding is pretty simple and straightforward as well!

String ls_function

OLEObject oFUNC

// ensure we're connected to the SAP system

// if connection cannot be made, a NULL object is returned

IF NOT of_SAP_connect( ) THEN RETURN oFUNC

// handle the input argument here

ls_function = Upper( Trim( as_rfc_name ) )

// create the RFC reference as OLE

oFUNC= oRFC.Object.Add( ls_function )

// and that's pretty much it for creating the reference!

RETURN oFUNC

At this point, we have a reference to the RFC inside SAP which we can use to execute the function module!  So, let's read data from a table inside the SAP system...  A good example table to use would be the material master.  Now, there are several standard SAP material master RFC's available which are far more useful, but for this example, just roll with it.  The material master table is called MARA.  We'll pull a list of material numbers along with the material type; columns MATNR & MTART, respectively.

On our visual object, let's add a DataWindow control and name it dw_test.  Save the object and then let's create a simple external DataWindow to hold our data.  Set the external DW up as follows:

Save the DataWindow as "d_sap_test_read_table".  Go back to your visual object and assign the DataObject to the DataWindow you just created.

Now, let's code for retrieving some data!  The next snippet of code simply defines the table to pull from, declares the columns to retrieve, and contains a simple WHERE clause.  Let's call our new test function of_SAP_test_read_table( ) and have it return a boolean.

OLEObject oREAD

// get a reference to RFC_READ_TABLE using our fancy new function from above

oREAD = of_SAP_GetRFC( 'RFC_READ_TABLE' )

// test to make sure no errors occurred

IF NOT IsValid( oREAD ) THEN

     MessageBox( 'Error...', 'There was an error retrieving the RFC.' )

     RETURN FALSE

END IF

// set the table to pull from inside SAP by defining its input parameter

// see comments below for more information

oREAD.Exports( 'QUERY_TABLE' ).Value = 'MARA'

At this time, we have declared our RFC and set the property telling it what table to pull from.  The imports/exports (input/output) area can get a little confusing, because it often seems very backwards.  I wish I had a better explanation for it, but I don't.

Next, we will need to define the columns we want to return.  As a matter of personal preference, I prefer to clear out any tables I'm working with before I start in on them.  Down the road, you may find that you've used an RFC and it has remnants of data still in it which can greatly skew your return values.

// clear the field table

oREAD.Tables.Item( 'FIELDS' ).FreeTable( )

// add two rows for the column we wish to return

oREAD.Tables.Item( 'FIELDS' ).Rows.Add( )

// set column

oREAD.Tables.Item( 'FIELDS' ).Value[ 1, 1 ] = 'MATNR'  // material number (padded)

We have all the necessary properties set to make our first call to SAP!  At this point you would also define any WHERE clause by clearing the the options table, adding a new row, and setting the [ 1, 1 ] value.  But for now, let's return a big list!  If the SAP system you're hooked into has a big list of materials, this could take some time!


// make the call and get result

Boolean lb_result


lb_result= oREAD.Call( )


// test result

IF NOT lb_result THEN

     MessageBox( 'Error...', 'There was an error calling the RFC.' )

     RETURN FALSE

END IF


Well, that was pretty anticlimactic wasn't it?  You've now created a reference to an RFC, set its properties, and made a call to SAP...  Now where's the data?  The data is inside the OLEObject, located in either the export properties (not used in RFC_READ_TABLE) or in the tables.  See below for accessing the data...


String ls_mat

Long ll_idx, ll_row


// loop through the result set

FOR ll_idx = 1 TO oREAD.Tables.Item( 'DATA' ).RowCount

     // set the material number

     ls_mat = oREAD.Tables.Item( 'DATA' ).Cell( ll_idx, 'WA' )


     // format the material number

     ls_mat = Trim( ls_mat )


     // insert new row in DW

     ll_row = dw_test.InsertRow( 0 )


     // set the value(s)

     dw_test.SetItem( ll_row, 'mat_no', ls_mat )

NEXT


Add the of_sap_test_read_table( ) function to a button and run it.  Your DW should be populated with a list of all materials!

Building Better Data Sets

In the above DataWindow that was created, there were two columns defined, but only one column was returned/populated.  Why?  To get things started, I wanted to show it in its simplest form.  Working with the 'WA' object is nothing more than string manipulation, but it clutters the code a bit, so I wanted a nice, clear view of the code to show.  Below I'll show you the code to retrieve both the material number and the material type.  This will be the code for the entire function.

To handle the string manipulation, we'll re-visit the 'FIELDS' table to get each fields' offset and length.  We did not define it when the call was made, but SAP populates it for us on the return.  (how nice of them)  We'll then use those two items to separate out our data.

String ls_mat, ls_type, ls_data

Long ll_idx, ll_row, ll_mat_off, ll_mat_len, ll_type_off, ll_type_len

OLEObject oREAD


// get a reference to RFC_READ_TABLE using our fancy new function from above

oREAD = of_SAP_GetRFC( 'RFC_READ_TABLE' )


// test to make sure no errors occurred

IF NOT IsValid( oREAD ) THEN

     MessageBox( 'Error...', 'There was an error retrieving the RFC.' )

     RETURN FALSE

END IF


// set the table to pull from inside SAP by defining its input parameter

oREAD.Exports( 'QUERY_TABLE' ).Value = 'MARA'


// clear the field table

oREAD.Tables.Item( 'FIELDS' ).FreeTable( )

// add two rows for the two columns we wish to return

oREAD.Tables.Item( 'FIELDS' ).Rows.Add( )

oREAD.Tables.Item( 'FIELDS' ).Rows.Add( )

// set the two columns

oREAD.Tables.Item( 'FIELDS' ).Value[ 1, 1 ] = 'MATNR'  // material number (padded)

oREAD.Tables.Item( 'FIELDS' ).Value[ 2, 1 ] = 'MTART'  // material number (padded)


// make the call and get result

Boolean lb_result


lb_result= oREAD.Call( )


// test result

IF NOT lb_result THEN

     MessageBox( 'Error...', 'There was an error calling the RFC.' )

     RETURN FALSE

END IF


// gather the offset/length properties for our two columns

ll_mat_off = Long( oREAD.Tables.Item( 'FIELDS' ).Cell( 1, 'OFFSET' ) ) + 1

ll_mat_len = Long( oREAD.Tables.Item( 'FIELDS' ).Cell( 1, 'LENGTH' ) )

ll_type_off = Long( oREAD.Tables.Item( 'FIELDS' ).Cell( 1, 'OFFSET' ) )

ll_type_len = Long( oREAD.Tables.Item( 'FIELDS' ).Cell( 1, 'LENGTH' ) ) + 1


// loop through the result set

FOR ll_idx = 1 TO oREAD.Tables.Item( 'DATA' ).RowCount

     // set the material number

     ls_data = oREAD.Tables.Item( 'DATA' ).Cell( ll_idx, 'WA' )

     // string manipulation

     ls_mat = Mid( ls_data, ll_mat_off, ll_mat_len )

     ls_type = Mid( ls_data, ll_type_off, ll_type_len )

     // format the material number

     ls_mat = Trim( ls_mat )

     ls_type = Trim( ls_type )


     // insert new row in DW

     ll_row = dw_test.InsertRow( 0 )


     // set the value(s)

     dw_test.SetItem( ll_row, 'mat_no', ls_mat )

     dw_test.SetItem( ll_row, 'mat_tp', ls_type )

NEXT


With only a few more lines, we've now returned a bigger result set!  I hope to get an example posted to the CodeExchange area soon demonstrating a clean little function I wrote a while back which allows us to input the most basic items and return a fully constructed and populated DataWindow.


Another thing to remember about this is that you have to clean up after yourself with many RFC's...  So it's a great habit to get into from the start.  Once you've handled getting the data you want, you will want to free up the data table, disconnect from the object, and destroy it so that if you call it again, you're not working with an already populated table.


oREAD.Tables.Item( 'DATA' ).FreeTable( )

oREAD.DisconnectObject( )

DESTROY oREAD



Notes / Hints / Tips


  • WHERE clause size is limited to 72 characters.  It is setup as a table, but thus far I've never been able to get more than one row to work properly. (I will do some more testing as noted in comments below)
  • Return result set size is limited to 512 characters.  If you loop through the 'FIELDS' table and tally the lengths and it's above 512, then you're losing data.  This is due to the return datatype...  If more data is needed, two calls can be made or a custom RFC should be created.
  • Typically what's shown in the Import tab will be .Export on the OLEObject, and vice versa.
2 Comments
Labels in this area