Skip to Content
Technical Articles
Author's profile photo Stefan Schnell

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:

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

Assigned Tags

      14 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo ako bale
      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.

      Author's profile photo Stefan Schnell
      Stefan Schnell
      Blog 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

      Author's profile photo Tashi Norbu
      Tashi Norbu

      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 ?

       

      Author's profile photo Stefan Schnell
      Stefan Schnell
      Blog Post Author

      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

      Author's profile photo Tashi Norbu
      Tashi Norbu

      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

       

      Author's profile photo Stefan Schnell
      Stefan Schnell
      Blog Post Author

      Hello Tashi,

      I tried it and it worked like expected.

      Best regards
      Stefan

       

      Author's profile photo Tashi Norbu
      Tashi Norbu

      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.

      Author's profile photo David Szatmari
      David Szatmari

      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.

      Author's profile photo Stefan Schnell
      Stefan Schnell
      Blog Post Author

      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

      Author's profile photo Anderson Jesus
      Anderson Jesus

      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

      Author's profile photo Stefan Schnell
      Stefan Schnell
      Blog Post Author

      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

      Author's profile photo James Young
      James Young

      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

      Author's profile photo Stefan Schnell
      Stefan Schnell
      Blog Post Author

      Hello James Young,

      the link works now, maybe a temporary indisposition.

      You can find DynamicWrapperX here.

      Best regards
      Stefan

      Author's profile photo James Young
      James Young

      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