Skip to Content
Technical Articles

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

A few days ago I published here the possibility to execute C# of VB.NET seamlessly in IRPA. On the same way is it possible 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.

Then it pops up the Win32 API dialog.

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

Last but not least all the return values.

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.

You can find the COM library dotNETRunner at my homepage.

5 Comments
You must be Logged on to comment or reply to a post.
  • Hi Stefan,

    I like your idea of calling dotnet from SAP GUI, though I have no current application.

    However I am wondering what kind of license there is behind our dotnetRunner DLL, and wether you would share its source code.

    Cheers,

    Peter

  • Hello Peter,

    thanks for your comment.

    I have not even thought about licensing issues. My libraries are all free, you can use it wherever you like. Once the development is complete I can provide the source, that’s no secret. Give me a little bit more time and you will find it in the package.

    Best regards
    Stefan

    • Hi Stefan!

      Great work! I currently don’t have a use for it either, but you’ve shown people some new options and that is always a good thing.

      Since your code has very high reuse potential, instead of hosting your code as a zip on your website, you should consider putting it inside a dedicated github repository. Your code will get more exposure and maybe even potential contributors.

      Peter is right about asking for the license. In a corporate environment, people do need to ensure their dependencies have licenses and there might be caveats in it for you as well if you don’t:

      “When you make a creative work (which includes code), the work is under exclusive copyright by default. Unless you include a license that specifies otherwise, nobody else can copy, distribute, or modify your work without being at risk of take-downs, shake-downs, or litigation. Once the work has other contributors (each a copyright holder), “nobody” starts including you.” (https://choosealicense.com/)

      It is also very easy to add a license to the repository – there are some standard license templates to choose from. Both people and tools know where to look for the license on github, so this will save you a few questions down the road.

      If you have any questions, feel free to ask!