Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
stefan_schnell
Active Contributor


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:

  1. TestMethod is only a sub (function with result void), without any parameters.

  2. TestMethod2 is also a sub, with a string parameter.

  3. TestMethod3 is a function, with a string, an integer parameter and a string result.

  4. TestMethod4 is a function, with a float parameter and result.

  5. TestMethod5 is a function, with no parameters and an integer result.

  6. TestMethod6 is a sub, with a structure parameter.

  7. 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-------------------------------------------------------------------

15 Comments