Wednesday, 18 September 2019

Tip: How To Execute C# or VB.NET Seamlessly in ABAP

Therefor I added a wrapper method in the COM library which delivers as return type a string. This method called run_str. It is very interesting to develop a component which can be used in different scenarios.

This approach uses the COM interface of the SAP GUI for Windows. Without the SAP GUI for Windows this approach can not be used. It is also not possible to use it with background processes.

Let us begin with the wrapper class for the COM library. It contains six public methods and the two most important are:

◈ add_Assembly = Adds references to other dotNET assemblies
◈ run_str = Executes VB.NET or C# code

The run_str method has six parameters

1. Language as string, allowed are CS for C# or VB for VB.NET.
2. Code as string.
3. Instance of the class as string.
4. Method to call as string.
5. Parameters as string, optional, default value empty string. Parameters are always strings, so it is necessary to convert it in the code in the right format.
6.. Separator for the parameters as string, optional, default value comma

The VB.NET or C# source code can be stored as include object inside the SAP system. With the method read_incl_as_string it can be loaded into a string variable.

"-Begin-----------------------------------------------------------------
CLASS z_cl_dotnetrunner DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    "! Loads the dotNETRunner library
    "!
    "! @parameter rv_result | 1 for success, otherwise 0
    METHODS load_lib
      RETURNING
        VALUE(rv_result) TYPE i .

    "! Frees the dotNETRunner library
    METHODS free_lib .

    "! Executes stored OLE activities
    METHODS flush .

    "! Adds an assembly
    "!
    "! @parameter iv_AssemblyName | Name of the Assembly
    METHODS add_Assembly
      IMPORTING
        VALUE(iv_AssemblyName) TYPE string.

    "! Executes C# or VB.NET code
    "!
    "! @parameter iv_Language   | CS for CSharp or VB for VB.NET
    "! @parameter iv_Code       | Code
    "! @parameter iv_Instance   | Instance
    "! @parameter iv_Method     | Method
    "! @parameter iv_Parameters | Parameters
    "! @parameter iv_Separator  | Separator of the parameters
    "!
    "! @parameter rv_result     | Value as string
    METHODS run_str
      IMPORTING
        VALUE(iv_Language) TYPE string
        VALUE(iv_Code) TYPE string
        VALUE(iv_Instance) TYPE string
        VALUE(iv_Method) TYPE string
        VALUE(iv_Parameters) TYPE string DEFAULT ''
        VALUE(iv_Separator) TYPE string DEFAULT ','
      RETURNING
        VALUE(rv_result)     TYPE string.

    "! Reads an include as string
    "!
    "! @parameter iv_incl_name   | Name of the include
    "!
    "! @parameter rv_str_incl    | Include as string
    METHODS read_incl_as_string
      IMPORTING
        VALUE(iv_incl_name) TYPE sobj_name
      RETURNING
        VALUE(rv_str_incl)  TYPE string .

  PROTECTED SECTION.

  PRIVATE SECTION.

    METHODS isactivex
      EXPORTING ev_result TYPE i.

    DATA olib TYPE ole2_object.

  ENDCLASS.

CLASS z_cl_dotnetrunner IMPLEMENTATION.

  METHOD load_lib."-----------------------------------------------------

    DATA rc TYPE i VALUE 0.

    rv_result = 0.
    CALL METHOD me->isactivex IMPORTING ev_result = rc.
    CHECK rc = 1.
    CREATE OBJECT olib 'dotNET.Runner'.
    CHECK sy-subrc = 0 AND olib-handle <> 0 AND olib-type = 'OLE2'.
    rv_result = 1.

  ENDMETHOD.

  METHOD isactivex."----------------------------------------------------

    DATA hasactivex(32) TYPE c.

    ev_result = 0.
    CALL FUNCTION 'GUI_HAS_OBJECTS'
      EXPORTING
        object_model         = 'ACTX'
      IMPORTING
        return               = hasactivex
      EXCEPTIONS
        invalid_object_model = 1
        OTHERS               = 2.
    CHECK sy-subrc = 0 AND hasactivex = 'X'.
    ev_result = 1.

  ENDMETHOD.

  METHOD free_lib."-----------------------------------------------------
    FREE OBJECT olib.
  ENDMETHOD.

  METHOD flush."--------------------------------------------------------
    CALL METHOD cl_gui_cfw=>flush.
  ENDMETHOD.

  METHOD add_assembly."-------------------------------------------------
    SET PROPERTY OF olib 'Assembly' = iv_assemblyname.
  ENDMETHOD.

  METHOD run_str."------------------------------------------------------

    CALL METHOD OF olib 'run_str' = rv_result
      EXPORTING
        #1 = iv_language
        #2 = iv_code
        #3 = iv_instance
        #4 = iv_method
        #5 = iv_parameters
        #6 = iv_separator.

  ENDMETHOD.

  METHOD read_incl_as_string."------------------------------------------

    DATA:
      lt_trdir    TYPE trdir,
      lt_incl     TYPE TABLE OF string,
      lv_inclline TYPE string,
      lv_len_line TYPE i,
      lv_retincl  TYPE string.

    SELECT SINGLE * FROM trdir INTO lt_trdir
      WHERE name = iv_incl_name AND subc = 'I' AND appl = space.
    CHECK sy-subrc = 0.
    READ REPORT iv_incl_name INTO lt_incl.
    CHECK sy-subrc = 0.
    LOOP AT lt_incl INTO lv_inclline.
      IF strlen( lv_inclline ) > 0.
        IF lv_inclline+0(1) = '*'.
          lv_len_line = strlen( lv_inclline ) - 1.
          lv_inclline = lv_inclline+1(lv_len_line).
        ENDIF.
      ENDIF.
      lv_retincl = lv_retincl && lv_inclline &&
        cl_abap_char_utilities=>cr_lf.
      CLEAR lv_inclline.
    ENDLOOP.
    rv_str_incl = lv_retincl.

  ENDMETHOD.

ENDCLASS.
"-End-------------------------------------------------------------------

Here now to include examples, the first with VB.NET and the second with C# code. The VB.NET code uses Win32 API functions and both uses Windows.Forms.

*Imports System.Windows.Forms
*Imports System.Runtime.InteropServices
*
*Namespace Foo
*
*  Public Class Bar
*
*    <DllImport("user32.dll", EntryPoint:="MessageBox", SetLastError:=True)> _
*    Public Shared Function MBox(ByVal hWnd As Integer, ByVal txt As String, _
*      ByVal caption As String, ByVal Typ As Integer) As Integer
*    End Function
*
*    Public Function SayHelloFunc() As String
*      SayHelloFunc = "Hello World from VB.NET"
*    End Function
*
*    Public Function Say42Func() As Integer
*      Say42Func = 42
*    End Function
*
*    Public Function Say166Func() As Double
*      Say166Func = 166.0
*    End Function
*
*    Public Function Add(val1 As String, val2 As String) As Integer
*      Add = CInt(val1) + CInt(val2)
*    End Function
*
*    Public Function Yepp(val1 As String, val2 As String) As String
*      Yepp = val1 & val2
*    End Function
*
*    Public Sub Yell()
*      MessageBox.Show("Hello World with native dotNET", "VB.NET")
*      MBox(0, "Hello World with native Win32 call", "user32.dll", 0)
*    End Sub
*
*  End Class
*
*End Namespace

*using System.Windows.Forms;
*
*namespace Foo {
*
*  public class Bar {
*
*    public string SayHelloFunc() {
*      return "Hello World from CSharp";
*    }
*
*    public int Say42Func() {
*      return 42;
*    }
*
*    public double Say166Func() {
*      return 166.0;
*    }
*
*    public int add(string v1, string v2) {
*      int val1 = System.Convert.ToInt32(v1);
*      int val2 = System.Convert.ToInt32(v2);
*      int res = val1 + val2;
*      return res;
*    }
*
*    public void yell() {
*      MessageBox.Show("Hello World with native dotNET", "C#");
*    }
*
*  }
*
*}

Here now a report to use the class and the include objects. At first it is necessary to add the assemblies we need and to read the VB.NET from the include. Then each VB.NET method will be called step by step. Then follows the same procedure with the C# code.

"-Begin-----------------------------------------------------------------
REPORT z_dotnetrunner_test.

  DATA:
    lo_dotNETRunner TYPE REF TO Z_CL_DOTNETRUNNER,
    lv_vbcode TYPE string,
    lv_cscode TYPE string,
    lv_result TYPE string
    .

  CREATE OBJECT lo_dotNETRunner.
  CHECK lo_dotnetrunner->load_lib( ) = 1.

  lo_dotnetrunner->add_assembly( iv_assemblyname = 'System.Windows.Forms.dll' ).
  lo_dotnetrunner->add_assembly( iv_assemblyname = 'System.Runtime.InteropServices.dll' ).

  "-VB.NET code---------------------------------------------------------
  lv_vbcode = lo_dotnetrunner->read_incl_as_string('Z_DOTNET_VB_TEST').

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'VB'
    iv_code = lv_vbcode
    iv_instance = 'Foo.Bar'
    iv_method = 'SayHelloFunc'
  ).
  WRITE: / `VB.NET - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'VB'
    iv_code = lv_vbcode
    iv_instance = 'Foo.Bar'
    iv_method = 'Say42Func'
  ).
  WRITE: / `VB.NET - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'VB'
    iv_code = lv_vbcode
    iv_instance = 'Foo.Bar'
    iv_method = 'Say166Func'
  ).
  WRITE: / `VB.NET - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'VB'
    iv_code = lv_vbcode
    iv_instance = 'Foo.Bar'
    iv_method = 'Add'
    iv_parameters = '20,22'
  ).
  WRITE: / `VB.NET - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'VB'
    iv_code = lv_vbcode
    iv_instance = 'Foo.Bar'
    iv_method = 'Yepp'
    iv_parameters = 'Hello, Stefan'
  ).
  WRITE: / `VB.NET - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'VB'
    iv_code = lv_vbcode
    iv_instance = 'Foo.Bar'
    iv_method = 'Yell'
  ).

  "-C# code-------------------------------------------------------------
  lv_cscode = lo_dotnetrunner->read_incl_as_string('Z_DOTNET_CSHARP_TEST').

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'CS'
    iv_code = lv_cscode
    iv_instance = 'Foo.Bar'
    iv_method = 'SayHelloFunc'
  ).
  WRITE: / `C# - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'CS'
    iv_code = lv_cscode
    iv_instance = 'Foo.Bar'
    iv_method = 'Say42Func'
  ).
  WRITE: / `C# - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'CS'
    iv_code = lv_cscode
    iv_instance = 'Foo.Bar'
    iv_method = 'Say166Func'
  ).
  WRITE: / `C# - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'CS'
    iv_code = lv_cscode
    iv_instance = 'Foo.Bar'
    iv_method = 'add'
    iv_parameters = '20,22'
  ).
  WRITE: / `C# - Return: ` && lv_result.

  lv_result = lo_dotnetrunner->run_str(
    iv_language = 'CS'
    iv_code = lv_cscode
    iv_instance = 'Foo.Bar'
    iv_method = 'yell'
  ).

  lo_dotnetrunner->free_lib( ).

"-End-------------------------------------------------------------------

And now the result of the report as a sequence of images. At first pops up the Windows.Forms dialog of the VB.NET call.

SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Certifications, SAP ABAP Online Exam

Then it pops up the Win32 API dialog.

SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Certifications, SAP ABAP Online Exam

And then the Windows.Forms dialog of the C# code comes up.

SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Certifications, SAP ABAP Online Exam

Last but not least all the return values.

SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Certifications, SAP ABAP Online Exam

The same approach as for IRPA can also be applied to ABAP. Great, VB.NET and C# seamlessly in ABAP and also the possiblity to use Win32 API calls and also the possibility to use Windows.Forms.

No comments:

Post a Comment