Roll your own Component:

This document explains how to develop your own components. It also gives guidelines for developing client programs and deployment.  In addition to this document, you should study the sample component as well as the sample VB program and the sample ASP and HTML files.  The samples were written against the 31H version of the IDES demo system so that you can easily run them.

Last updated: 29 Sep 1999


Setting up your development environment.

On the Development machine (NT or Win9x), you will need the following:

The ADO and RDS documentation is installed via the NT Option Pack 3 using the custom install option. The ADO and RDS documentation will be installed in \program files\common files\system\ado\docs & \program files\common files\system\msadc\docs respectively.

On the client machines for client side scripting and or for VB clients, you will need IE4.0 or greater.

On the client machines for server side scripting, you will need a browser of any make.

On the production machine (the COM connector machine), you need the following software

TOP


BAPIs and/or function modules in R/3

There are two ways to view the function definitions within R/3. The first is to use the BOR (Transaction swo1) which allows you to navigates through applications areas down to the business objects and their associated methods (se37).

Or if you know the name of the function that implement the object method, or if you know a function module which exposes the functionality which you need, you can go directly via the function library transaction (se37).

The transaction se37 lists the function's parameters (grouped into imports and exports) when your select the function's Import/export parameters interface and double click the Display button.

It is important to be aware that table parameters are listed separately on the screen following the export and import parameters. All non-table parameters will be either simple ABAP datatypes or structures. In the above example the Export parameter 'RETURN' is a structure. The following information from se37 will be needed when constructing the ATL object methods:

Testing the BAPI function: In order to write either the VB or script program that will be used to test your COM implementation, you will need to know which parameter values are appropriate given the configuration and data of the target system and client as well as the authorization profile of your user account. Use the Sngl. Test functionality off of se37 to confirm these values.

TOP


Creating an ATL project

Start a new project using VC with the ATL COM wizard.

Once the project is created:

 

Add the sapmtx.h header in the stdafx.h file:

extern CComModule _Module;
#include <atlcom.h>
#include <sapmtx.h>        // addition

Add the following a the end of the stdafx.cpp file

#define SAPMTX_GLOBAL
#define SAPMTX_DEFINE_GUIDS
#include <sapmtx.h>

#define RFC_DEFINE_TYPE
#define RFC_DEFINE_STRUCTURE

 

TOP


Creating an ATL MTX Object with the object wizard

With the ATL object wizard (Insert -> New ATL Object....), create a new ATL object of type 'MS Transaction Server'. Under the MTX Tab, choose a dual interface. You do not need IObjectControl or Connection Points.

In the object .idl file, replace IDispatch with ISapConnector and import the sapconn.idl

The code generated by the ATL wizard looks like:

import "oaidl.idl";
import "ocidl.idl";
                       
    [
       
object,
       
uuid(F135B0AE-367C-11D1-94D8-00609789DA9E),
       
dual,
       
helpstring("IRFCSampObj Interface"),
       
pointer_default(unique)
    ]
   
interface IRFCSampObj : IDispatch
    {

Change it to:

import "oaidl.idl";
import "ocidl.idl";
import "sapconn.idl"; //<-- add this line
                       
    [
       
object,
       
uuid(F135B0AE-367C-11D1-94D8-00609789DA9E),
       
dual,
       
helpstring("IRFCSampObj Interface"),
       
pointer_default(unique)
    ]
   
interface IRFCSampObj : ISAPConnector     // NOT IDispatch
    {

In the objects header, replace the ATL base classes and template with CSapConnector.

The code generated by the ATL wizard looks like:

class ATL_NO_VTABLE CRFCSampObj :
     public
CComObjectRootEx<CComSingleThreadModel>,
     public
CComCoClass<CRFCSampObj, &CLSID_RFCSampObj>,
     public IDispatchImpl<IRFCSampObj, &IID_IRFCSampObj,
                          &LIBID_RFCSAMPLib>
{

Change it to:

class ATL_NO_VTABLE CRFCSampObj :
    
public CSapConnector<CRFCSampObj, &CLSID_RFCSampObj,
                          IRFCSampObj, &IID_IRFCSampObj,
                          &LIBID_RFCSAMPLib>

{

In the ATL interface map do the following changes:

BEGIN_COM_MAP(CRFCSampObj)
     COM_INTERFACE_ENTRY(IRFCSampObj)
     COM_INTERFACE_ENTRY
2(IDispatch, IRFCSampObj)   // change
     COM_INTERFACE_ALL_SAP_ENTRIES
                 // addition
END_COM_MAP()

Change the code generated by the wizard to control the registration of the object:

//DECLARE_REGISTRY_RESOURCEID(IDR_RFCSAMPOBJ)       // remove
DECLARE_REGISTRY_SAP_RESOURCEID(IDR_RFCSAMPOBJ)
    // addition

Add the registration map to the object cpp file. The BOR_CLASSNAME will be the name of the class as it appears in the R/3 Business Object Repository -- the name can also be a user defined name as in our example where we chose the name 'Foo'. The map should be inserted prior to any method definitions.

BEGIN_SAP_REGISTRY_MAP( CRFCSampObj )
     SAP_REGISTRY_ENTRY( BOR_CLASSNAME, Foo )
END_SAP_REGISTRY_MAP()

TOP


Creating methods

You will define a method for each BAPI function that you wish to expose on the object's interface. In declaring the method you will need to follow these conventions:

ABAP DataType

Method DataType

Variant Type

C  (Character) BSTR VT_BTSR
I    (Integer) LONG VT_I4
F   (Float) DOUBLE VT_R8
D   (Date) DATE VT_DATE
T   (Time) DATE VT_DATE
P   (BCD Packed, Currency, Decimal,Quantity) DECIMAL
CY
VT_DECIMAL
VT_CY
N   (Numc) LONG,
DECIMAL or
BSTR
VT_I4
VT_DECIMAL
VT_BTSR
X   (Binary and Raw) BSTR VT_BTSR

Example:

Consider the BAPI function, BAPI_SALESORDER_GETLIST. This function module has five import parameters, one export parameters and a table that exports data. The parameters, their ABAP data type and method data types are listed below (from se37):

Parameter Name

Import/Export

ABAP DataType

Method Data Type
CUSTOMER_NUMBER Import C (char) BTSR
SALES_ORGANIZATION Import C (char) BSTR
MATERIAL Import (optional) C (char) BSTR
DOCUMENT_DATE Import (optional) D (date) DATE
DOCUMENT_DATE_TO Import (optional) D (date) DATE
PURCHASE_NUMBER Import (optional) C (char) BSTR
RETURN Export Structure VARIANT *
SALES_ORDERS Export Table VARIANT *

A couple of notes: 

To add a method to an interface go to the Class View in the VS project and right click on the Interface node of your class. Select Add Method. In the dialog box choose a user friendly name for the method name (in our example we use GetCustomerOrders). Then add the parameter declarations using friendly parameter names. The following is the parameter declarations for BAPI_SALESORDER_GETLIST:  
            [in] BSTR CustNum,
            [in] BSTR SalesOrg,
            [in,optional] VARIANT Material,
            [in,optional] VARIANT DocDate,
            [in,optional] VARIANT DocDateTo,
            [in,optional] VARIANT PONum,
            [in,out] VARIANT* pBapiReturn,
            [in,out] VARIANT* pSalesOrders


***After the wizard has done its magic AND if you change your mind about the method signature (how you declare the parameters), you must update the IDL file, the object header file and the object implementation file by hand.

Important Additional Note:  For each input table type it is recommended that an additional method be declared that simply returns an empty table crafted as an ADO Recordset. This Recordset can then be easily used to add items. The alternative is to use the RDS Advanced Data factory to define a disconnected Recordset. An example of using this latter approach can be found in the VB sample client for crafting up the OrderPartners input table on the OrderCreate method invocation.

TOP


Tables, structures and special types

For each table and structure a header file must be generated using the genh utility provided with the RFCSDK. If tables or structures share the same Reference Structure, only one instance is required. These headers should be included in the project directory. The following syntax is used when calling genh:

genh  ASHOST=<R/3 application server>
          SYSNR=<R/3 application server system number>
          CLIENT=<R/3 client>
          USER=<R/3 user id>
          PASSWD=<R/3 user password>
          LANG=<one of E, D, F ...>
          <Reference Name of the Table or Structure (NOT the parameter name)>
         >    <header file name>.h

Example to create header for Sales_Orders (BAPI_SALESORDER_GETLIST):

         genh ASHOST=myserver SYSNR=00 CLIENT=800 USER=Gilbert
                  PASSWD=heike LANG=E  BAPIORDERS > bapiorders.h

In the StdAfx.cpp file include the header files after the following line:

             #define RFC_DEFINE_STRUCTURE

The header files also need to be included in the object cpp file. The includes should be preceded by the following line:

            #define RFC_DEFINE_TYPE

 

 

Considerations for special ABAP scalar types

Certain ABAP data types, for which there exists no unique C++ data type, must be declared explicitly using CAbapScalarType.  This type is used in the method implementation. The following data types are covered by this:

RFCTYPE_DATE
RFCTYPE_TIME
RFCTYPE_FLOAT
RFCTYPE_BCD
RFCTYPE_NUM

For ABAP date, time and float at the C level these are all represented by double; consequently, it is necessary to explicitly tell the template the RFCTYPE of the value. For RFCTYPE_BCD and RFCTYPE_NUM, it is necessary to give the field length (and in the case of BCD the precision)

The BCD type (Binary Coded Decimal) is often used for accouting values. In the SAP data dictionary, this types is sometime called DEC, CURR or QUAN. For BCD fields, the length in bytes of the field and the number to the right of the decimal point are required. The length in bytes is ("total number of digits" + 1) / 2. where "total number of digits" contains the digits on the right of the decimal point. An easy way to obtain the proper length and decimal values is to run the genh.exe tool against the structure or the table which is referenced by the parameter in se37.

Syntax:

                    CAbapScalarType <user defined type name> (RFCTYPE_xxx ...);

Examples:

CAbapScalarType DateType( RFCTYPE_DATE );
CAbapScalarType Numc5Type( RFCTYPE_NUM, 5 );
CAbapScalarType BCDType8_2( RFCTYPE_BCD,8, 2 );

TOP


Method implementation

We employ the following programming framework for the method implementation:

Example using the BAPI_GL_ACC_GETLIST function:

try
    {
        CallFunction
( L"BAPI_GL_ACC_GETLIST" );
            Exporting( L"COMPANYCODE", CompanyCode );
            Exporting( L"LANGUAGE", Language );
            ImportingStructure( L"RETURN", &BAPIRETURN_Type, pBapiReturn);
            ImportingTable( L"ACCOUNT_LIST", &BAPI3006_1_Type, pAccountList);
        EndCallFunction();

       
// Enable pooling at the method invocation level for 3.x systems
        // This presumes that the RFC method is stateless
        SetComplete();
    }
    catch( HRESULT hr )
    {
        return hr;
    }

return S_OK;

 

Catching the HRESULT and returning it is the only error handling that needs to be done on the C++ side. See Handling Errors in Client Program for the client side handling of runtime error.

The class CSapConnector template class provides the following methods for the method implementation:

CallFunction(...)
EndCallFunction()
SetComplete();
Exporting(...)
Importing(...)
ExportingStructure(...)
ImportingStructure(...)
ExportingTable(...)
ImportingTable(...)
Data(...)

The syntax for each is described below:

CallFunction(…)

Function: associates the R/3 function with the method

Syntax:

CallFunction(L<R/3 function name>)

Note that all strings must be passed as UNICODE strings ( L"..." ).

Example:

CallFunction(L"Z_RFC_SALES_LIST");

 

EndCallFunction()

Function: executes the associated R/3 function

Syntax:

EndCallFunction()

Example:

EndCallFunction();

 

 

SetComplete()

Function:  Notifies the RFC resource dispenser to release the RFC connection. This is useful for 3.x systems in that it enables RFC pooling at the method invocation level. This should not be called if other method invocations on the object instance require a persistent (i.e., the same) RFC connection.

Syntax:

SetComplete()

Example:

SetComplete();

 

Exporting(...)

Function: sets up method input parameters that are ABAP scalar types (not tables or structures).

Syntax (for non-special ABAP scalar types):

Exporting(L<R/3 parameter name>, <method parameter name>):

Example:

Exporting(L"SORTL", SearchTerm);

Syntax (for special ABAP scalar types):

Exporting(L<R/3 parameter name>, &<ABAP scalar type name>,<method parameter name>):

Example:

                   Exporting( L"INDATE", &DateType, inDate );

 

Importing(...)

Function: sets up method output parameters that are ABAP scalar types (not tables or structures).

Syntax:

Importing(L<R/3 parameter name>, <method parameter name>, <parameter variant type>):

See above for parameter variant type mapping from ABAP datatypes.

Example:

Importing(L"ERRORTEXT", pErrTxt, VT_BSTR);

 

ExportingStructure(...)

Function: sets up method input parameters that are ABAP structures

Syntax:

ExportingStructure(L<R/3 structure name>, &<structure CAbapType>, <method parameter name>)

Example:

ExportingStructure(L"ORDER_HEADER_IN",                                 &VBAKKOM_Type,
                               OrderHeader);

 

ImportingStructure(...)

Function: sets up method output parameters that are ABAP structures

Syntax:

ImportingStructure(L<R/3 structure name>, &<structure CAbapType>, <method parameter name>)

Example:

ImportingStructure(L"RETURN",   
                                 &BAPIRETURN_Type,
                                 pBapiReturn);

 

ExportingTable(...)

Function: sets up method input parameters that are ABAP tables

Syntax:

ExportingTable(L<R/3 table name>, &<table CAbapType>, <method parameter name>)

Example:

ExportingTable(L"ORDER_ITEMS_IN", 
                          &VBAPKOM_WW_Type,
                          OrderItems);

 

ImportingTable(...)

Function: sets up method output parameters that are ABAP tables

Syntax:

ImportingTable(L<R/3 table name>, &<table CAbapType>, <method parameter name>)

Example:

ImportingTable(L"CUSTOMER",
                           &ZRFCCUST01_Type, pCustList);

 

 

Data(...)

Function: crafts up an empty R/3 table or structure as a ADO recordset.

Syntax:

Data(L<R/3 table name>, &<table CAbapType>, <method parameter name>)

Note:  Unlike the Importing.. and Exporting... calls, this function is not bounded by
            CallFunction() and EndCallFunction().

 

Example:

Data( L"ORDER_HEADER_IN",
          &VBAKKOM_Type, pHeader);

 

TOP


Build the object

Compile and register the object. If you are going to test the object locally (i.e., with both the calling and called program on the same machine), then you do not need to install the DLL into a MTS package. This is the recommended approach when you are just starting out.  Use VB as a test program. When you are confident that the object runs as expected and  you have confirmed how to drive it with an automation client, then register it in MTS and use ASP server side scripting or HTML client side scripting.

Installing the sample object,TOP


Logon and destination handling

When a method invocation comes into the object, the object requests a connection from the RFC connections which are pooled by MTS. (The connections are pooled in MTS even if the object is not installed in MTS.)

The pooled connection obtains logon information from the registry and through a dedicated session object at run time.  You may configure several destination entries  in the registry. This destination contains the SAP logon information: system name, message server, system number, client and optionally user, password, language and nocleanup.  If at the time of the method invocation no destination has been set, the default destination named NONE is used.  If the used destination is missing language, user or password then the method call will fail.

You can configure the registry with your favorite registry editor such as regedit.

\HKEY_LOCAL_MACHINE
\SOFTWARE
  \SAP
   \MTS
    \Destination: NONE="TYPE=3 ASHOST=fostcity SYSNR=00 CLIENT=800 USER=muster...
                                                                  .... PASSWD=ides LANG=E  nocleanup=1"
                        IDES="TYPE=3 ASHOST=fostcity SYSNR=00 CLIENT=800"

Alternatively you may use the DHTML pages default.htm which is included with the RFCSDK. This requires the installation of CCADMIN.dll (also included in the RFCSDK). For local administration, simply register the dll. For remote administration, install the dll into an MTS package and set the appropriate security and role.

In a production environment, you will generally not want to have a R/3 user and password in the registry.  So that, in this case, the registry will contain the system name, system number, client and pooling option.  The client program will pass the user name and password at runtime.  This make it currently difficult to use RDS on HTTP for client side scripting.

A note on the pooling option:  A destination value can be set to improve performance (pooling of RFC connections) against R/3 3.x systems. Setting nocleanup=1, assumes that the no state will be preserved between object instantiations in the R/3 system. 

TOP


Create a VB test program

You should study the included sample program in the RFCSamp.VB directory which runs against the RFCSamp object provided in source form. If you have installed and registered RFCSamp on your local machine, there is no need to install it in MTS. If you are accessing RFCSamp remotely, it must be installed as a component in an MTS package and this package must be exported. A product of exporting the package is a clients subdirectory containing an exe. Execute this on your development machine and it will do all the required DCOM configuration to remotely access RFCSamp.

Project setting:

In order to get strong type checking and editor help, Reference the following objects:

Programming steps:

            Create R/3 business object
            Set destination and/or user, password, language [optional]       
            Execute input table methods to retrieve empty input recordsets [optional]
            Set input parameter values [optional]
            Call the object method
            Use ADO to process any output recordsets [optional]

See Error Handling for information about how errors are handled by the client program.

Known issues with MTS 2.0

There is a bug in MTS  that prevents inherited interfaces from being correctly handled when dual interfaces are used such as when you create the object via a Dim bo as [new] myBusinessObject. The interface for setting destination and logon information is an inherited interface. There are two possible workarounds. The first is to use IDispatch only such as to create the object using Dim bo as Object and Set bo =  CreateObject("myBusinessObject.1"). This approach, however, eliminates the strong type checking and intelli-sense help. The other alternative is to Dim bo as myBusinessObject for regular use, and then Dim boRef As ISAPConnector for changing destination parameters. The following code illustrates the second approach.

Dim bo As New MyBusinessObject
Dim boRef As ISAPConnector
Set boRef = bo
boRef.Destination = "test"
Set boRef = Nothing

See also

Build and install the object, TOP


Create an ASP or an HTML test page

You should study the included sample asp pages in the RFCSamp.IE directory which runs against the RFCSamp object provided in source form.

Do not forget to register the object in an MTS package.

You may script in the browser, this is client side scripting. Use RDS to launch the object on the Internet Server machine. RDS will marshall input and output tables across the HTTP link.  Using client side scripting, you may bound the Recordset used to pass tables parameters directly to an active control or to the the DHTML tables. If you use this approach, then on the internet server you must make sure that the business object has been marked safe for scripting and has been registered as launchable by RDS. The client must also have the RDS libraries installed -- these are installed with IE 4.0 or greater.

The following script will mark an object as safe for scripting (example uses an object with the CLSID {F135B0AF-367C-11D1-94D8-00609789DA9E}:

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{F135B0AF-367C-11D1-94D8-00609789DA9E}\Implemented Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}]
[HKEY_CLASSES_ROOT\CLSID\{F135B0AF-367C-11D1-94D8-00609789DA9E}\Implemented Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}]

The following script will register an object as launchable by RDS (example uses the object with the ProgID RFCSampObj.RFCSampObj.1):

REGEDIT4

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters\ADCLaunch\RFCSampObj.RFCSampObj.1]

You may also script the server, this is server side scripting.  You loose the neat data bound control features from client side scripts but your application is then less dependant of the browser capabilities.

Build and install the object,Installing the sample object, Set up the component in MTS,   TOP


Handling errors in client program

If an RFC error occurs or if the function modules throws an ABAP exception, the connector will set the last error for use with ISupportErrorInfo.
In VB, you will want to check Err.Number.  If Err.Number is non zero the an RFC error or an ABAP exception has been throwed. You will want to check Err.Description to get a string describing the error.  In order to be able to do this, you need to state
         On Error Resume Next
before the first method invocation.

If a BAPI base method returns an error, you need (in addition to the prior check) to check the Type field of the BAPIRETURN structure returned by the BAPI method.

TOP


Set up the component in MTS

Setting up a package for the first time:

1)  Start the Microsoft Management Console (or the Microsoft transaction Server Explorer).
2)  Select Microsoft transaction Server
3)  Select Computers -> My Computer -> Packages Installed
4)  Right click  -> New -> Package
5)  Create an empty package (unless, of course, you have a pre-built package to install)
6)  Select <your package> -> Components
7)  Right click -> New -> Components
8)  Install new component

Recommended next steps:

9)    Select the package
10)  Right click ->  Export...  this creates the following files:
        -  a *.pak file which can be used to re-install the package as a pre-buil component on this or another MTS server
        -  a *.exe client installation program which does the DCOM configuration

 

Registry entries for logon, TOP


Sample Code

The following sample code running against a 31H IDES R/3 System is provided:

To setup the sample, you must create an MTS package by installing the pre-built package.  If this fails, just create an empty package and install the dll "...\rfcsdk\ccsamp\RFCSamp.Exp\RFCSamp.dll" in it. See Set up the component in MTS

To use the provided ASP pages, you must create a virtual root named "RFCSample" in IIS and point it at
...\rfcsdk\ccsamp\RFCSamp.IE
Allow scripting and directory browser in this directory.

In order to use the sample demonstrating RDS for the client side scripting,  you must mark the object as ADC launch and safe for scripting.  For this, you just have to double click the RFCSamp.reg file provided in ...\rfcsdk\ccsamp\RFCSamp.Exp.
Further more, you must configure a NONE destination which contains system, users and password!(Logon and destination handling).

 

TOP

 

©SAP AG 1997 - 1999