R/3 DCOM Connector

 

Roll your own business object ...

Steps for building a DCOM Connector project

Follow the following steps to build a project using the DCOM Connector.

Requirements

Make sure you are using either Windows NT 4.0 with Service Pack 3 or Windows 95..

Install Visual Studio with Visual C++ 5.0

At least the Visual Studio Service Pack 1 is required.

Install RFCSDK (Version 40C)

Install Internet Explorer 4.0

Internet Explorer 4.0 brings with it RDS 1.5 which we need for marshaling ABAP tables over the network. Installing IE4 is the easiest way to make sure we have all the necessary components. IE4 must be installed on the client as well as on the server.

Install NT Option Pack

NT Option Pack contains MTS (Microsoft Transaction Server) and IIS (Internet Information Server) and others. Using NT Option Pack you'll get a complete environment for using the DCOM Connector.

There is no need to install MSMQ (Message Queue) if you want to use the DCOM Connector.

NT Option Pack requires installing at least Internet Explorer 4.01.

Install and register librfc32.dll

Take the librfc32.dll out of the RFCSDK and put it into a place, which is in the search path for executables. Since MTS typically is running in the system32 directory, we recommend to put the librfc32.dll also there. Register the DLL with regsvr32. Do NOT import it into the MTS explorer.

Create a new ATL project

Start a new project using Visual C++ with the ATL COM wizard. Choose building a DLL.

ATL Wizard

With the ATL object wizard create a new ATL object of type 'MS Transaction Server'. Choose a dual interface. Do not turn on IObjectControl or Connection Points.

Set Paths

In the project settings add the include and lib directories of the SDK into the corresponding fields (you specify the include path at C/C++, Preprocessor and the lib path at Link, Input). At C/C++, C++ Language turn on C++ exception handling.

Include SAP header files

All the functionality is imported via a header file named sapmtx.h. It includes some template classes and template functions, which are again using some COM interfaces being exported from librfc32.dll. No additional bindings to a DLL or library is needed.

Add the following line to stdafx.h:

#include <atlcom.h>
#include <sapmtx.h> // <<--- add this

Since sapmtx.h is a template library you must have exactly one place within your project where some code is created. Add these lines at the bottom of stdafx.cpp

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

Modify the class declaration

Almost everything is imported into the project by inheriting from the class CSapBaseProxy, which is defined in sapmtx.h. Because CSapBaseProxy also implemts a IDispatch interface, you must use the COM_INTERFACE_ENTRY2 macro in the 'COM_MAP' to determine the standard IDispatch interface of the class.

Add the following lines to the class declaration (assuming the class is called CTest) and remove the lines refering to CComObjectRootEx and CComCoClass.

class ATL_NO_VTABLE CTest : 

      public CSapConnector<CTest, &CLSID_Test,                    // <<--- add this

                           ITest, &IID_ITest, &LIBID_TESTLib>

                                                                  // <<--- remove all other base classes

{

public:

     CTest()

     {

     }

     

     DECLARE_REGISTRY_SAP_RESOURCEID(IDR_TEST) // <<-- modify DECLARE_REGISTRY_RESOURCEID

     DECLARE_NOT_AGGREGATABLE(CTest)

     

     BEGIN_COM_MAP(CTest)

       COM_INTERFACE_ENTRY(ITest)

       COM_INTERFACE_ENTRY2(IDispatch, ITest) // <<-- change this to COM_INTERFACE_ENTRY2

       COM_INTERFACE_ALL_SAP_ENTRIES // <<-- add this

     END_COM_MAP()

Define the Registry Map

On top of the .cpp file of your class you must define the Registry Map for this class. This map may contain several attributes describing the class. At least there must be defined an empty map.


BEGIN_SAP_REGISTRY_MAP( CTest )

END_SAP_REGISTRY_MAP()


Here is an example containing some values:


BEGIN_SAP_REGISTRY_MAP( CTest )

   SAP_REGISTRY_ENTRY( PROGID,  R3Test.R3Test.1 )

   SAP_REGISTRY_ENTRY( PACKAGE, ConnectorTest )

END_SAP_REGISTRY_MAP()


All possible values are defined by the enum SapConnectorRegistryEntryKey .

You must define this map, if you have exchanged the DECLARE_REGISTRY_RESOURCEID macro by DECLARE_REGISTRY_SAP_RESOURCEID. Having defined this map, the object will register itself automatically into the DCOM connector infrastructure.

 

Create your function skeletons

Create your functions with the ATL wizard by selecting 'Add new Method' at the interface definition (not at the class itself!). For the parameters you should follow the following rules:

You can choose input [in] or output [out] parameters. Use automation data types for input parameters and VARIANT * for output parameters. For tables or structures always choose variants (VARIANT for input, VARIANT * for output parameters). Map TYPE C (CHAR) fields to BSTR, TYPE I (INT4) fields to long and TYPE F (FLTP) fields to double. Some ABAP data types (D, T, P) need special handling (see section below).

Structures and tables are always represented as objects (VARIANTs with VT_DISPATCH)

 

COM/Automation

ABAP/RFC

Type indicator

Type

Type indicator

Type

VT_I2

VT_I4

VT_R4

VT_R8

VT_CY

VT_DATE

VT_BSTR

VT_BOOL

VT_VARIANT

VT_DECIMAL

VT_UI1

DBTYPE_BYTES

DBTYPE_STR

DBTYPE_WSTR

DBTYPE_DBDATE

DBTYPE_DBTIME

SHORT

LONG

float

double

LARGE_INTEGER

DATE

BSTR

VARIANT_BOOL

VARIANT

DECIMAL

BYTE

BYTE[length]

char[length]

wchar_t[length]

DBDATE

DBTIME

RFCTYPE_INT2

RFCTYPE_INT

RFCTYPE_FLOAT

RFCTYPE_FLOAT

RFCTYPE_BCD

RFCTYPE_DATE

RFCTYPE_TIME

RFCTYPE_CHAR

RFCTYPE_CHAR

<recursion, 1 level>

RFCTYPE_BCD

RFCTYPE_INT1

RFCTYPE_BYTE

RFCTYPE_CHAR

RFCTYPE_CHAR

RFCTYPE_DATE

RFCTYPE_TIME

RFC_INT2

RFC_INT

RFC_FLOAT

RFC_FLOAT

RFC_BCD[precision]

RFC_DATE

RFC_TIME

RFC_CHAR[length]

RFC_CHAR

RFC_CHAR

RFC_BCD[precision]

RFC_INT1

RFC_BYTE[length]

RFC_CHAR[length]

RFC_CHAR[length]

RFC_DATE

RFC_TIME

Fill up the function body

To map the COM method call to a call to an R/3 function module, you must fill up the body of the generated method.

Assume we have generated a skeleton for the function: HRESULT Test([in] BSTR inString, [out] VARIANT * pOutString); Then fill up the function body with the following code:

STDMETHODIMP CTest::Test(BSTR inString, VARIANT * pOutString)

{

    try

    {

        CallFunction( L"TEST" ); // TEST = function module name in R/3

        Exporting( L"INSTRING", inString ); // INSTRING = Input parameter name in R/3

        Importing( L"OUTSTRING", pOutString, VT_BSTR ); // OUTSTRING = Output parameter name in R/3

        EndCallFunction();

    }

    catch( HRESULT hr )

    {

       return hr;

    }

    return S_OK;

}

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

All the functions you use here where inherited from CSapBaseProxy. CSapBaseProxy::Exporting is a template function which accepts any automation data type. CSapBaseProxy::Importing only works with VARIANT * fields, so it needs the variant type of the parameter to choose as an additional field. All these functions throw HRESULTs on errors and set an IErrorInfo object. They also abort the actual transaction internally on errors.

Special ABAP data types

For some ABAP data types there exists no unique C++ data type. So the ABAP data type must be given explicitly. The data type to be given would be one of the following: RFCTYPE_DATE, RFCTYPE_TIME, RFCTYPE_BCD or RFCTYPE_NUM (RFCTYPE).

A function with an date and a currency input parameter. You can use BSTR or a VARIANT containing a VT_DATE value in the function declaration for the date field and a CY or VARIANT field for the currency.

STDMETHODIMP CTest::Test(BSTR inDate, CY inCurrency )

{

    try

    {

        CAbapScalarType DateType( RFCTYPE_DATE );         // define a DATE type

        CAbapScalarType BCDType8_2( RFCTYPE_DATE, 8, 2 ); // define a BCD Type length 8 byte , 2 decimals

        

        CallFunction( L"TEST1" ); 

            Exporting( L"INDATE", &DateType, inDate );    // INDATE = Input parameter name in R/3

            Exporting( L"INCURRENCY", &BCDType8_2, inCurrency ); // INCURRENCY = Input parameter name in R/3

        EndCallFunction();

    }

    catch( HRESULT hr )

    {

        return hr;

    }

    return S_OK;

}
 

Using structues and tables

If your function is using tables or structures within the function interface, you should use the genh utility in the SDK to generate a C++ header file for the corresponding structure from the R/3 repository.

Assume you want to access a table parameter COMPANY_CODE in a function module. This parameter has the reference structure T001. The first step we need to do is to generate a header file for this table. Use genh with the following command line

genh ashost=<R3 host> sysnr=<system number> user=.. passwd=.. client=.. t001 > t001.h

For structures follow the same procedure.

Define the table or structure object in the code

Attaching the structure meta data to the project you simply have to include the generated header together with defining some macros.

After having generated t001.h, put the following lines at the bottom of stdafx.cpp


#define RFC_DEFINE_TYPE

#define RFC_DEFINE_STRUCTURE

#include <t001.h>

 

Refer to the structure object

To use the structure description data in other files you must refer to them.

In the cpp file, which contains the function definition, insert at the top


#include "stdafx.h"

#include "test.h"

#include "Test.h"

#define RFC_DEFINE_TYPE // <-- add this

#include <t001.h> // <-- add this

Note the naming convention for the type object, which is the reference structure name + _Type. You must not change it.

Using tables or structures as parameters

Since all tables and structures have the same type (IDispatch * or VARIANT ) from the point of view of C++, you must give the structure description object as an additional parameter, when exporting or importing a table or structure.

A function using tables as input and output parameters might look like this, where the input table parameter name is INTABLE with a reference structure of RFCTEST_IN and the output table parameter name is OUTTABLE with a reference structure RFCTEST_OUT

STDMETHODIMP CTest::TestTable(IDispatch * tabIn, VARIANT * pTabOut)

{

    try

    {

        CallFunction( L"TEST_TABLE" ); 

            ExportingTable( L"INTABLE", &RFCTEST_IN_Type, tabIn ); 

            ImportingTable( L"OUTTABLE", &RFCTEST_OUT_Type, pTabOut );

        EndCallFunction();

    }

    catch( HRESULT hr )

    {

        return hr;

    }

    return S_OK;

}

Note that in R/3 table parameters can be designated for both input and output. You must map these to different parameters, if you want to use them for both. We do not yet support [in,out] parameters for structures and tables.

A function using structures as input and output parameters might look like this. The input structure parameter name is INSTRUCT with a reference structure of RFCTEST_IN and the output structure parameter name is OUTSTRUCT with a reference structure RFCTEST_OUT


STDMETHODIMP CTest::TestStruct(IDispatch * structIn, VARIANT * pstructOut)

{

    try

    {

        CallFunction( L"TEST_STRUCTURE" ); 

            ExportingTable( L"INSTRUCT", &RFCTEST_IN_Type, structIn ); 

            ImportingTable( L"OUTSTRUCT", &RFCTEST_OUT_Type, pStructOut );

        EndCallFunction();

    }

    catch( HRESULT hr )

    {

        return hr;

    }

    return S_OK;

}

Tables and structures are represented ADO Recordsets.

Build everything

Compile the project and register the dll in a newly created package within the MTS explorer. If you recompile your project you always have to use the MTS explorer function "Refresh all Components" to reactivate the MTS settings before you can use your DLL again. (Or simply turn off the custom build step, which registers the DLL and destroys the MTS registry settings by that).

Configure the R/3 connection

At last you must configure the R/3 connection for the Component Connector. You can do that using 'regedit' at the registry key

HKEY_LOCAL_MACHINE\SOFTWARE\SAP\mts\Destination.

(This key is automatically created when you try to use your object the first time, if it's not present, So simply call some method - this will fail - but then have a registry entry, you can edit). You must enter/find a string value named "NONE". The string must contain the connection data as you would use it with the RfcOpenEx function (or try the genh utility without arguments).

Typically it looks like that

ashost=<hostname> sysnr=<system number> client=<client> user=<userid> passwd=<password> lang=<language>

You can set the option 'nocleanup=1' to speed up resource pooling against an 3.x or older system. However, you must make sure that the functions you are calling don't have an internal state.

By setting 'maxlivetime=nnn' you can set the max. live time in seconds of an R/3 connection in the resource pool.

You also can set 'role=xxxx', where xxxx is some string. This means that the user must be assigned to the corresponding MTS role to use this destination.

Turn on the trace with the option 'trace=1'

You can also turn on the tracing by entering a string value

HKEY_LOCAL_MACHINE\SOFTWARE\SAP\mts\Options\trace = 1

to the registry. Usually the trace files show up in the SYSTEM32 directory (or in the actual directory, if you are running within MTS).

Configuring the DCOM Connector using ccadmin.dll

Within the 'ccwww' directory of the rfcsdk you'll find the file 'ccadmin.dll'. Create a package 'ccadmin' in MTS and add 'ccadmin.dll' as a component to this package. Then you can use the DHTML file ccadmin0.htm in the 'web' directory using IE4 to configure the R/3 connection.

Have a look at ccadmin0.htm. Is is an nice example of how to use RDS and ADO Recordsets together with DHTML data binding.

Build the client

Now you have to build the corresponding client program. Note that error messages are delivered via COM's standard GetErrorInfo function.

What's missing up to now?

Single Login functionality is not yet activated.

R/2 connections work.

What changed since the last version?

Tables and structures are now fully supported.

All class ids are changed. You need to recompile everything.

Security via MTS Roles is activated. You can restrict the access to a destination to one role.

Interfaces to select several destinations and to add explicit logon data.

Resource pooling against 3.x and older systems is activated.

R/2 connections work.

The original base class CSapBaseProxy was replaced by CSapConnector. CSapBaseProxy is present, however its interface also changed.
It is recommended to move to CSapConnector.