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.

31 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!

  • Hi Stefan,

     

    This is a very useful code for me. However, I did execute the dotNetRunner command as suggested but even after doing it, the code

    CREATE OBJECT olib 'dotNET.Runner'.

    throws an exception SY-SUBRC = 2.

    Please help in rectifying the error.

     

    Regards,

    Saurabh.

    • Saurabh Banerjee

       

      Hello Saurabh,
      it is necessary to register the dotNET Runner library first via regasm. When you have done that, please take a look at your security configuration to see if instantiation is allowed.
      Best regards
      Stefan

      • /
        • Himanshu Kawatra

          Hello Himanshu,

          dotNETRunner is a dotNET library, it is not possible to register it with regsvr32.

          Try RegAsm instead.

          C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe dotNETRunner_x86.dll /codebase
          C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe dotNETRunner_x64.dll /codebase

          Best regards
          Stefan

          • Hi Stefan,
            Thanks Again, your dll is working fine in ABAP. I am able to get the output...
            But when i execute my dll. i am not able to do.
            Some Qs are in my mind..
            1. Do i need to add assembly ??
            If Yes, then where i will find the assembly name???
            2. What i need to give here instead of dotnet.runner
            Let me provide you my dll code..
            I just need to fetch the token in abap using that TokenGen.dll
            Dll Name:-  TokenGen.dll
            I know i am disturbing you a lot...
            Thanks all for your help!!
          • Himanshu Kawatra

            Hello Himanshu,
            sounds great.

            To your questions:

            1. You must add the assemblies that your code needs. You see the assembly names in VB.NET at Imports respectively in C# at using.
            2. Nothing, dotNET.Runner is the correct name. This command creates an object of dotNET.Runner class.
            3. As far as I can see is your C# code not complete. To call an DLL function you must use DLLImport. Look at the example above.

            Best regards
            Stefan

          • Stefan Schnell

            Hello Stefan,

             

            It is working for GUI successfully....

            Now i got another requirement is that... Client want to execute that using UI(CRM) ....

            We have made transaction launcher.. but i this as per your blog.. it is only available by GUI....

            So can you help me to execute that using CRM....

             

            Regards,

            Himanshu Kawatra

  • Hi Stefan, your idea is great and helpfull in my scenario. I need to run a c# class that i've already wrote in sap, but your example doesn't work in my case.
    From your dotnetrunner.com package i copied the class, the includes and the program report.

    i copied the files from dotnetrunner.com into C:\Projects\SAP\IRPA\dotNETRunner\1.3.1 and i run the dotNETRunner_Register.reg

    when i'm launch the program z_dotnetrunner_test the gui ask me to use the file on my pc, i click on "consent" and then nothing, i don't have any output. Only "C# - return:" without any value.

    Am i doing something wrong?

    Thank you

    • Hello Patryk,

      thank you for your reply.

      Do you execute the reg file in admin mode? This is necessary to set registry entries.

      Do you try the VBScript programs from the example folder? On this way you can see whether it works at all.

      Let me know your results.

      Best regards
      Stefan

      • Thx Stefan, you helped me! i think that my problem was executing the reg files not as administrator. Now your example works great, but i have a new problem now. I would like to use C# class with udp client so i load the assembly in this way:

        Next i wrote the C# class with the using "*using System.Net.Sockets;" and if i run the program everything is correct. If i add the variable udpClient (like you can see in the image)

        everything stop working. i check and the System.Net.Sockets.dll exists, maybe i forgot something?

        if i debug lv_result in compile_code i can see the error  that can't find the udpclient type

        I'm very appreciate your help,
        Patryk

          • Hi Stefan,
            Thanks Again, your dll is working. I am able to get the output.
            but i have a new problem now

            i have dll that can not register in Regsvr32
            how i can add this dll in assembly ?
            I’m very appreciate your help.
          • Thank you for your consideration

            I faced a problem when registering in regsam

            “RegAsm : warning RA0000 : No types were registered”

            what can i do ? 🙁

            Is there a way use dll without register in regsam ?

            /
            🙁
          • Sina Rahemi

            Hello Sina,

            I am not sure that I understand your problem correctly.

            You want to use an additional dotNET DLL in the code which is executed in the context of dotNETRunner? If yes, use the method AddAssembly.

            Best regards
            Stefan

          • Hello Stefan,
            I’m sorry, but my English is not very good
            if I am correct before we use the method AddAssembly we must register dll in regsam
            also in method AddAssembly we only write dll name and not direction of dll
            but how can i use dll located in "D:\MarkerDll.dll" ? can i set direction in method AddAssembly ?

            Thank you

      • Hello Patryk,

        I try this C# code:

        //-Begin----------------------------------------------------------------
        
        using System;
        using System.Net.Sockets;
        
        public class UdpClientSample {
        
          public string Init() {
            try {
              UdpClient client = new UdpClient();
              return "Success";
            } catch (Exception ex) {
              return ex.Message;
            }
          }
        
        }
        
        //-End------------------------------------------------------------------

        I call the C# code with this VBScript test program:

        '-Begin-----------------------------------------------------------------
        
        Dim dotNETRunner, Result
        
        On Error Resume Next
        Set dotNETRunner = CreateObject("dotNET.Runner")
        If Not IsObject(dotNETRunner) Then
          MsgBox "Can't create dotNET.Runner", vbOkOnly, "Important hint"
          WScript.Quit
        End If
        On Error Goto 0
        
        dotNETRunner.AddAssembly = "System.dll"
        
        Result = dotNETRunner.CompileFile("CS", "Test.cs")
        If Result <> "" Then
          MsgBox Result, vbOKOnly, "Error"
          WScript.Quit
        End If
        
        Result = dotNETRunner.Run("UdpClientSample", "", "Init")
        MsgBox  "Return " & TypeName(Result) & ": " &Result, vbOkOnly, "Init"
        
        Set dotNETRunner = Nothing
        
        '-End-------------------------------------------------------------------

        And it works, it delivers success.

        Add only the assembly System.dll, the UdpClient type is only in this library.

        Best regards
        Stefan

         

        • Hi Stefan, you're great! Your VBS is working well, but i have problem when i'm running the ABAP code.  Let's check my code:

          REPORT z_csharp_report.
          
            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.Net.dll' ).
          
            lv_cscode = lo_dotnetrunner->read_incl_as_string('Z_CSHARP_TEST').
          
            lv_result = lo_dotnetrunner->compile_code(
              iv_language = 'CS'
              iv_code = lv_cscode
            ).
          
            cl_abap_browser=>show_html(  html_string = lv_result ).
          
            CHECK lv_result IS INITIAL.
          
            lv_result = lo_dotnetrunner->run_string(
              iv_instance = 'UdpClientSample'
              iv_method = 'Init'
            ).
            WRITE: / `C# - Return: ` && lv_result.

           

          and the Z_CSHARP_TEST:

          *using System;
          *using System.Net.Sockets;
          *
          *public class UdpClientSample {
          *
          *  public string Init() {
          *    try {
          *      UdpClient client = new UdpClient();
          *      return "Success";
          *    } catch (Exception ex) {
          *      return ex.Message;
          *    }
          *  }
          *
          *}

           

          If i run this code i have a compilation error. The output of the show_xml is this:

          "C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe" /t:library /utf8output /R:"System.Net.dll" /out:"C:\Users\pat\AppData\Local\Temp\3hny32e2.dll" /D:DEBUG /debug+ /optimize- /w:3  "C:\Users\pat\AppData\Local\Temp\3hny32e2.0.cs"

          if i try to run the command with my prompt i don't have the last 3hny32e2.0.cs file.

          if i run the report with the line commented: * //UdpClient client = new UdpClient(); everything is ok and the C# code run fine.

          Do you have some idea? i really don't know what to do.

           

          Thank you very much and have a nice weekend!

           

          Pat