R/3 DCOM Connector
Follow the following steps to build a project using the DCOM Connector.
Make sure you are using either Windows NT 4.0 with Service Pack 3 or Windows 95..
At least the Visual Studio Service Pack 1 is required.
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.
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.
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.
Start a new project using Visual C++ with the ATL COM wizard. Choose building a DLL.
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.
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.
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>
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()
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 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 |
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.
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;
}
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.
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>
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.
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.
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).
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).
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.
Now you have to build the corresponding client program. Note that error messages are delivered via COM's standard GetErrorInfo function.
Single Login functionality is not yet activated.
R/2 connections work.
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.