Technical Articles
Enhancement: How To Call DLL Functions In ABAP
Five years ago I presented here a way how to call DLL functions in ABAP via the DynamicWrapperX library, which is furthermore available here. It is a COM library which maps the DLL functions to COM calls, which are easy usable inside an ABAP program.
Nearly two years ago I presented here a way, how to call DLL functions via SAPIENs COM library ActiveXPosh.dll. With this library I use PowerShells possibilities to compile .NET languages dynamically at runtime.
Two days ago I presented here an enhancement for an easy using of .NET languages inside ABAP, based on the same method.
Here now an enhancement which combines the knowledge, to show how easy it is to use DLL function calls in ABAP.
At first a small architecture map about the using components and their interfaces:
From the ABAP program, which contains a PowerShell script, we call the ActiveX PowerShell library, via the COM interface. The PowerShell script contains the .NET language code, which is compiled at runtime. And the .NET language code stores the interface to the dynamic link library (DLL). On this way it is possible to call a specific function of the DLL from the ABAP code and to work with its results.
Here now the code. The first code is the include ZCODEINCLUDE which contains the interface to the DLL:
'-Begin-----------------------------------------------------------------
'-Imports-------------------------------------------------------------
Imports System
Imports System.Runtime.InteropServices
Imports Microsoft.VisualBasic
'-Namespaces----------------------------------------------------------
Namespace Methods
Public Structure TestStruct
Public x As Integer
Public y As Integer
End Structure
Public Class Test
Public Declare Sub TestMethod Lib "Test.dll" _
Alias "TestMethod" ()
Private Declare Sub pTestMethod2 Lib "Test.dll" _
Alias "TestMethod2" (strParam1 As IntPtr)
Public Shared Sub TestMethod2(strParam1 As String)
Dim ptrParam1 As IntPtr = Marshal.StringToHGlobalAuto(strParam1)
pTestMethod2(ptrParam1)
End Sub
Private Declare Function pTestMethod3 Lib "Test.dll" _
Alias "TestMethod3" (strParam1 As IntPtr, intParam2 As Integer) As IntPtr
Public Shared Function TestMethod3(strParam1 As String, intParam2 As Integer) As String
Dim ptrParam1 As IntPtr = Marshal.StringToHGlobalAuto(strParam1)
Dim retParam As IntPtr = pTestMethod3(ptrParam1, intParam2)
Return Marshal.PtrToStringAuto(retParam)
End Function
Public Declare Function TestMethod4 Lib "Test.dll" _
Alias "TestMethod4" (fltParam1 As Double) As Double
Public Declare Function TestMethod5 Lib "Test.dll" _
Alias "TestMethod5" () As Integer
Private Declare Sub pTestMethod6 Lib "Test.dll" _
Alias "TestMethod6" (StructTest As IntPtr)
Public Shared Sub TestMethod6(StructTestAs TestStruct)
Dim ptrStructTest As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(StructTest))
Marshal.StructureToPtr(StructTest, ptrStructTest, True)
pTestMethod6(ptrStructTest)
End Sub
Private Declare Function pTestMethod7 Lib "Test.dll" _
Alias "TestMethod7" (X As Integer, Y As Integer) As IntPtr
Public Shared Function TestMethod7(X As Integer, Y As Integer) As TestStruct
Dim ptrStructTest As IntPtr = pTestMethod7(X, Y)
Return Marshal.PtrToStructure(ptrStructTest, New TestStruct().GetType)
End Function
End Class
End Namespace
'-End-------------------------------------------------------------------
As you can see there are seven external test methods declarations with different interfaces and different parameter types. A few of them are declared as private, but they owns a public wrapper function. These wrapper functions convert the parameter types, e.g. from string to pointer. So it is easier to call the functions.
Now the PowerShell code from the include ZPSINCLUDE, to load the .NET language class:
#-Begin-----------------------------------------------------------------
If (-Not ("Methods.Test" -as [type])) {
Add-Type -TypeDefinition $MethodDefinitions -Language VisualBasic > $Null
}
#-End-------------------------------------------------------------------
Last but not least the ABAP code:
"-Begin-----------------------------------------------------------------
Program zUseDotNet.
"-TypePools---------------------------------------------------------
Type-Pools OLE2.
"-Constants--------------------------------------------------------
Constants OUTPUT_CONSOLE Type i Value 0.
Constants OUTPUT_WINDOW Type i Value 1.
Constants OUTPUT_BUFFER Type i Value 2.
Constants CrLf(2) Type c Value cl_abap_char_utilities=>cr_lf.
"-Classes-----------------------------------------------------------
Class lcl_PoSh Definition.
Public Section.
Class-Methods Init
Importing i_OutputMode Type i
Returning Value(r_oPS) Type OLE2_OBJECT.
Class-Methods Flush.
Class-Methods ReadInclAsString
Importing i_InclName Type SOBJ_NAME
Returning Value(r_strIncl) Type String.
EndClass.
Class lcl_PoSh Implementation.
Method Init.
"-Variables---------------------------------------------------
Data lv_Result Type i.
Create Object r_oPS 'SAPIEN.ActiveXPoSHV3'.
If sy-subrc = 0 And r_oPS-Handle > 0 And r_oPS-Type = 'OLE2'.
Call Method Of r_oPS 'Init' = lv_Result Exporting #1 = 0.
If lv_Result = 0.
Call Method Of r_oPS 'IsPowerShellInstalled' = lv_Result.
If lv_Result <> 0.
Set Property Of r_oPS 'OutputMode' = i_OutputMode.
Set Property Of r_oPS 'OutputWidth' = 128.
EndIf.
Else.
Free Object r_oPS.
EndIf.
Else.
Free Object r_oPS.
EndIf.
EndMethod.
Method Flush.
Call Method CL_GUI_CFW=>Flush.
EndMethod.
Method ReadInclAsstring.
"-Variables---------------------------------------------------
Data lt_TADIR Type TADIR.
Data lt_Incl Type Table Of String.
Data lv_InclLine Type String.
Data lv_retIncl Type String.
"-Main--------------------------------------------------------
Select Single * From TADIR Into lt_TADIR
Where OBJ_NAME = i_InclName.
If sy-subrc = 0.
Read Report i_InclName Into lt_Incl.
If sy-subrc = 0.
Loop At lt_Incl Into lv_InclLine.
lv_retIncl = lv_retIncl && lv_InclLine &&
cl_abap_char_utilities=>cr_lf.
lv_InclLine = ''.
EndLoop.
EndIf.
EndIf.
r_strIncl = lv_retIncl.
EndMethod.
EndClass.
"-Variables---------------------------------------------------------
Data lo_PS Type OLE2_OBJECT.
Data lv_Result Type String.
Data lt_Result Type Table Of String.
Data lv_Code Type String.
Data lv_PSCode Type String.
Data lv_PSCodeExec Type String.
Data lv_strBuf Type String.
Data lv_Path Type String.
"-Main--------------------------------------------------------------
Start-Of-Selection.
lo_PS = lcl_PoSh=>Init( OUTPUT_BUFFER ).
If lo_PS-Handle > 0.
"-Add path to the libray to environment path--------------------
lv_PSCodeExec = '$EnvPath = $env:PATH;' &&
'$env:PATH += ";C:\Dummy";$EnvPath'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
Call Method Of lo_PS 'OutputString' = lv_Result.
Call Method Of lo_PS 'ClearOutput'.
lcl_PoSh=>Flush( ).
lv_Path = lv_Result.
"-Read the dotNET language code from include--------------------
lv_Code = '$MethodDefinitions = @"' && CrLf &&
lcl_PoSh=>ReadInclAsString( 'ZCODEINCLUDE' ) &&
'"@;' && CrLf.
"-Read PowerShell code from include-----------------------------
lv_PSCode = lv_Code && lcl_PoSh=>ReadInclAsString( 'ZPSINCLUDE' ).
"-Call the different functions of the library-------------------
lv_PSCodeExec = lv_PSCode && '[Methods.Test]::TestMethod()'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
lv_PSCodeExec = lv_PSCode &&
'[Methods.Test]::TestMethod2("Hallo Welt")'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
lv_PSCodeExec = lv_PSCode &&
'$strRc = [Methods.Test]::TestMethod3("Hallo Welt", 4711)'.
lv_PSCodeExec = lv_PSCodeExec && CrLf && 'Write-Host $strRc'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
lv_PSCodeExec = lv_PSCode &&
'$fltRc = [Methods.Test]::TestMethod4(3.14159267)'.
lv_PSCodeExec = lv_PSCodeExec && CrLf && 'Write-Host $fltRc'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
lv_PSCodeExec = lv_PSCode &&
'$intRc = [Methods.Test]::TestMethod5()'.
lv_PSCodeExec = lv_PSCodeExec && CrLf && 'Write-Host $intRc'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
lv_PSCodeExec = lv_PSCode &&
'$TestStruct = New-Object Methods.TestStruct'.
lv_PSCodeExec = lv_PSCodeExec && CrLf && '$TestStruct.x = 4'.
lv_PSCodeExec = lv_PSCodeExec && CrLf && '$TestStruct.y = 2'.
lv_PSCodeExec = lv_PSCodeExec && CrLf &&
'[Methods.Test]::TestMethod6($TestStruct)'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
lv_PSCodeExec = lv_PSCode &&
'$rcStruct = [Methods.Test]::TestMethod7(1606, 1964)'.
lv_PSCodeExec = lv_PSCodeExec && CrLf &&
'Write-Host $rcStruct.x " : " $rcStruct.y'.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
lcl_PoSh=>Flush( ).
"-Catch output buffer into a variable and clear output buffer---
Call Method Of lo_PS 'OutputString' = lv_Result.
Call Method Of lo_PS 'ClearOutput'.
lcl_PoSh=>Flush( ).
"-Write the content of the output buffer------------------------
Split lv_Result At CrLf Into Table lt_Result.
Loop At lt_Result Into lv_strBuf.
Write: / lv_strBuf.
EndLoop.
"-Set environment path back-------------------------------------
lv_PSCodeExec = '$env:PATH = ' && lv_Path.
Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.
Free Object lo_PS.
EndIf.
"-End-------------------------------------------------------------------
In comparison with my example here, I added the method flush to the local class.
If you look at the section “Add path to the library to environment path” you will see the method how to call a PowerShell command, to get the result back and to work with it.
The calls of the DLL functions are also easily:
- TestMethod is only a sub (function with result void), without any parameters.
- TestMethod2 is also a sub, with a string parameter.
- TestMethod3 is a function, with a string, an integer parameter and a string result.
- TestMethod4 is a function, with a float parameter and result.
- TestMethod5 is a function, with no parameters and an integer result.
- TestMethod6 is a sub, with a structure parameter.
- TestMethod7 is a function, with a structure result.
As you can see my example uses many different types of parameters and results. All what you can do with a .NET language, you can do inside ABAP.
Here now the code of the dynamic link library (DLL), to show the receiver of the calls. You can use any program language you want to build a DLL, my example library was programmed in FreeBasic language.
'-Begin-----------------------------------------------------------------
'-Includes------------------------------------------------------------
#Include Once "windows.bi"
'-Structures----------------------------------------------------------
Type TestStruct
x As Integer
y As Integer
End Type
'-Variables-----------------------------------------------------------
Dim Shared struct As TestStruct
Extern "Windows-MS"
'-Sub TestMethod------------------------------------------------------
Sub TestMethod Alias "TestMethod" () Export
MessageBox(NULL, "Dies ist ein Test", "Test", MB_OK)
End Sub
'-Sub TestMethod2-----------------------------------------------------
Sub TestMethod2 Alias "TestMethod2" _
(ByVal strParam1 As WString Ptr) Export
MessageBox(NULL, Str(*strParam1), "Test2", MB_OK)
End Sub
'-Function TestMethod3------------------------------------------------
Function TestMethod3 Alias "TestMethod3" _
(ByVal strParam1 As WString Ptr, ByVal intParam2 As Integer) _
As WString Ptr Export
MessageBox(NULL, Str(*strParam1) & " " & Str(intParam2), _
"Test3", MB_OK)
TestMethod3 = @WStr("Hallo zusammen")
End Function
'-Function TestMethod4------------------------------------------------
Function TestMethod4 Alias "TestMethod4" _
(ByVal fltParam1 As Double) As Double Export
Dim rcValue As Double
rcValue = fltParam1 * 2
MessageBox(NULL, Str(fltParam1) & " * 2 = " & Str(rcValue), _
"Test4", MB_OK)
TestMethod4 = rcValue
End Function
'-Function TestMethod5------------------------------------------------
Function TestMethod5 Alias "TestMethod5" () As Integer Export
TestMethod5 = 16061964
End Function
'-Sub TestMethod6-----------------------------------------------------
Sub TestMethod6(ByVal structParam1 As TestStruct Ptr) Export
MessageBox(NULL, Str(structParam1->x) & " " & _
Str(structParam1->y), "Test6", MB_OK)
End Sub
'-Function TestMethod7------------------------------------------------
Function TestMethod7(ByVal x As Integer, ByVal y As Integer) _
As TestStruct Ptr Export
struct.x = x
struct.y = y
TestMethod7 = @struct
End Function
End Extern
'-End-------------------------------------------------------------------
Hi Stefan,
Fantastic Blog! I am actually searching for ways on how to send a password-protected email with ZIP attachment, until I've reached your blog.
Seemed that I can use your idea to incorporate .NET program that do the password ZIP email. I am actually new to integration of other programming languages to ABAP, so I've got some dumb question for you.
1) Do I need a 3rd party software to make this kind of solution?
2) What do I need to configure? (as I am purely an ABAP developer, less background in BASIS part)
3) I am using class " cl_abap_zip" to do the ZIPping of file before sending the email, then I have a separate .NET program that do the encryption and password (as we are removing moving all functionalities to SAP, omitting all .NET program). And my requirement is to make this Zipped file password-protected, before sending as email to the user.
Apology as this is my first time to try this kind of approach. Thank you.
Hello ako bale,
thanks for your reply. 🙂
To your questions:
Let us know your results.
Good luck.
Cheers
Stefan
Hi Stefan,
Not sure if you are still following this thread, i have a requirement to call a .NET exe program from abap. i need to pass few parameters to the application and not sure how i can accomplish this.
i am able to call the .exe application through execute method of CL_GUI_FRONTEND_SERVICES.
Do i need to use this DLL approach to accomplish this ?
Hello Tashi,
surely I follow this thread. You don't need this approach to do this. The method Execute offers with its parameter PARAMETER the possibility to set it:
Set your parameters in this string and all should worked well.
Cheers
Stefan
Thank you Stefan . i thought the same about the parameter PARAMETER, but when i checked online , many were just passing the path of the application as below:
CALL METHOD cl_gui_frontend_services=>execute
EXPORTING
* document =
application = 'CDIC.WinForm.exe'
* parameter = 'C:\CDIC.WinForm' "local path on the system
* default_directory =
* maximized =
* minimized =
* synchronous =
operation = 'OPEN' .
however, since you are certain about the usage of the paramter, i will try further with this approach.
Thanks you again,
Tashi
Hello Tashi,
I tried it and it worked like expected.
Best regards
Stefan
Perfect Stefan. Thank you so much.
i would probably need to pass with a required pattern defined in the .NET application.Thank you so much, at least now i know that i am in the right direction.
Hi Stefan!
In one of my project I had to use an encryption function from a .dll so I made an .exe program in VB that I called with SM69 and worked like a charm.
I have a project again with nearly the same requirements, but the client asked if I could achieve it without the .exe program - only calling the .dll from ABAP. ...and they pointed to your solution.
In the first blog you had a hint, that it does not work with background programs. What about this solution over here? Is it possible to use this solution let say in batch printing? Do you have any information about the performance of these calls? Can you call the .dll parallel on more threads?
Great blog and some fine programming by the way!
Thank and best regards,
David.
Hello David,
thanks for your kindful words.
This solution works also with the COM interoperability layer of the SAP GUI for Windows. This solution also don't works with background processes. The performance of this calls is slow, it comes from the backend to the SAP GUI for Windows and from SAP GUI for Windows via COM to dotNET respectively PowerShell and from PowerShell to the DLL - a long chain. I don't know if it is possible to use DLL calls parallel, maybe try it with PowerShell jobs to call the DLL functions parallel.
To communicate with ABAP background processes is one way an RFC server program. It should be possible to realize it with NCo, but I don't know if PowerShell has the ability to handle asynchronous callback processes.
Best regards
Stefan
Thank you so much Stefan!! CongratsS!!
Please, tell me if have something like it for linux system?
I mean that the dll´s need to be placed at the SAP system machine... if i´m right, this do not work when the system is under linux...
maybe have some solution?
AndersonJM
Hello Anderson,
it is not possible to use this solution with Linux, because it uses the ActiveX interface of the SAP GUI for Windows. This is not available at a Linux system.
Best regards
Stefan
Hello Stefan, I tried the link "here" at the beginning of this post http://scn.sap.com/thread/1815465 and it no longer appears to work. Is there some other way to view this? I'm actually looking for where I can download DynamicWrapperX.
Thank you.
James
Hello James Young,
the link works now, maybe a temporary indisposition.
You can find DynamicWrapperX here.
Best regards
Stefan
Thank you sir. Through some assistance I was able to utilize your blog https://blogs.sap.com/2019/09/17/tip-how-to-execute-c-or-vb.net-seamlessly-in-abap/comment-page-1/#comment-508836 to get the process to work by interacting with a dll. However, I suppose you wouldn't be surprised when I say that when I try to invoke a dll method from a CRM webclient call I do not get the expected result. I take it this is because I'm not going directly through SAP Gui? If so, any chance you have some suggestions how I can tweak your solution such that I can interact with the DLL method via webclient process. Any suggestions/guidance would be appreciated. Regards, James