Skip to Content

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

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

Enjoy it.

Cheers
Stefan

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. ako bale

    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.

    (0) 
    1. Stefan Schnell Post author

      Hello ako bale,

      thanks for your reply. 🙂

      To your questions:

      1. Yes, you need SAPIENs ActiveXPoshV3 COM library, it is a bridge between COM and PowerShell. It is free and you can download it, after a registration, from here. All other necessary components are part of a Windows standard installation.
      2. On application server side nothing, you must only register the library via regasm on the frontend server.
      3. I think one possible approach could be to download the zip file, to encrypt them via your .NET program, to upload them and send it via e-mail. As you can see on the architecture map above, you are leaving the application server and you need a frontend server with a dialog user to do your task. If it is okay for you, you can go the way as you described.

      Let us know your results.

      Good luck.

      Cheers

      Stefan

      (0) 

Leave a Reply