Quantcast
Channel: SCN : Blog List - ABAP Connectivity
Viewing all articles
Browse latest Browse all 68

Enhancement: How To Call DLL Functions In ABAP

$
0
0

Hello community,

 

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 SAPIENsCOM 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:

zUseDotNet.jpg

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(StructTest As 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-------------------------------------------------------------------

 

Enjoy it.

 

Cheers

Stefan


Viewing all articles
Browse latest Browse all 68

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>