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
- Language as string, allowed are CS for C# or VB for VB.NET.
- Code as string.
- Instance of the class as string.
- Method to call as string.
- 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.
- 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.
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!
Since it's using SAPGui for OLE, it won't work in background, obviously. Nonetheless, I really like it! Do you have a use case?
An idea could be to create graphics based on data that a report has read from the database.
https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-create-a-bitmap-at-run-time
Best regards,
Andre
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
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
Stefan Schnell
Hi Stefan,
This is very helpful blog. but when i register that dll using regsvr32, IIt is giving me error..
Attached screenshot of error for Ref:-
Regards,
Himanshu Kawatra
Himanshu Kawatra
Hello Himanshu,
dotNETRunner is a dotNET library, it is not possible to register it with regsvr32.
Try RegAsm instead.
Best regards
Stefan
Himanshu Kawatra
Hello Himanshu,
sounds great.
To your questions:
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
Himanshu Kawatra
Hello Himanshu,
I assume the CRM is a web UI and runs in a browser. You can't use this approach without SAP GUI for Windows. Maybe you can solve your requirement with WebAssembly.
Best regards
Stefan
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
Hello
I have exactly the same problem
i execute the reg file in admin mode multiply time
But it doesn't work
Please help
Sina Rahemi
Hello Sina,
the reg files contains path information like
You have to adjust this beforehand.
Best regards
Stefan
Sina Rahemi
Hello Sina,
as far as I know is regsvr32 a program for registering and unregistering DLLs and ActiveX controls, but not for assemblies. Use regsam instead.
Best regards
Stefan
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
Thank you
Hello Patryk,
runs your code in your development environement?
Best regards
Stefan
yes, my code work if i run it in my enviroment on Microsoft Visual Studio
Hello Patryk,
I try this C# code:
I call the C# code with this VBScript test program:
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:
and the Z_CSHARP_TEST:
If i run this code i have a compilation error. The output of the show_xml is this:
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
Hello Pat,
change
to
Best regards
Stefan
it works, thank you man, you helped me a lot!
I have to offer you a beer, at least!
Pat
Great to hear that bro. Last weekend I found a few tins of strong beer. I will drink that this evening and think about you.