Reading the controller DLL

Dear all,
I hope I am placing the following question in the right place.
I would like to understand the module BladedDLLInterface.f90 because I want to write a similar code to use bladed controllers. In particular, I am trying to use the ROSCO controller in an in-house software.
My approach is the following:
First, I created an additional LIB file from the DLL. I have specified its path in Visual Studio as “Additional Dependency”. The additional text-file with controller parameters is also located there. It’s name is set in the input variable accINFILE .
To access the DISCON controller function, I used the following code:

subroutine DISCON(this, avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) 
    use, intrinsic :: iso_c_binding, only : c_float, c_int, c_char, c_null_char
    implicit none 
    !DEC$ ATTRIBUTES DLLEXPORT :: DISCON
    class(model_controller), intent(inout)           :: this
    REAL(C_FLOAT),  dimension(117),   INTENT(INOUT)  :: avrSWAP     
    INTEGER(C_INT),                   INTENT(INOUT)  :: aviFAIL    
    CHARACTER(KIND=C_CHAR, len = 28), INTENT(IN)     :: accINFILE  
    CHARACTER(KIND=C_CHAR, len = 20), INTENT(IN)     :: avcOUTNAME  
    CHARACTER(KIND=C_CHAR, len = 49), INTENT(INOUT)  :: avcMSG 
 end subroutine DISCON .

Then I call the controller like an ordinary subroutine

DISCON(this, avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG).

However, the controller code does not seem to work. I would be very happy if it could be explained to me whether I have forgotten important (ROSCO specific) steps or reading a DLL basically does not work like this. Do you also create a LIB file or can you only access the controller functions with the DLL? I would be very grateful for an answer.
Best regards,
David

Hi David,

Several months back, within ROSCO, I made something similar for calling external libraries.

Here is a working example, and here are the parts of BladedInterface.f90 that I added to ROSCO.

Some things that tripped me up (that I can remember):

  • different system architectures require different library loading functions
  • these lengths need to be exactly correct, otherwise there were memory leaks and seg faults

Here is another tool that might be of interest; it calls a dynamic library from python.

To actually respond to your post/questions: I didn’t have to specify any dependencies, though I use cmake to compile ROSCO. You definitely need to load the library somewhere. I don’t create a lib file called DISCON, that dynamic library should already exist somewhere.

I borrowed heavily (and happily) from the OpenFAST source, so they might be able to provide more precise answers than I can.

Best, Dan

Hi David,

When you want to link a library (DLL) to a code, there are two ways that it can be done:

  1. At build time, you add a .lib file to the dependencies so the code links with the executable. This typically means that the library is loaded immediately when the executable begins, and it must always be named the same thing.
  2. You DON’T link with a library file when building the executable, but you add an abstract interface in the code to tell it what subroutine interfaces will look like in a library you call. You can dynamically load the library, specifying a different name for the DLL, names of its subroutines (as long as they follow the types/sizes specified in the abstract interface). The routines to load the library are system dependent, so if you are going to build on different systems, you have to take that into account (see OpenFAST’s NWTC_Library Sys*.f90 routines for loading and freeing libraries). This option requires a little more complicated code, but is much more flexible.

The Bladed DLL controller interface in OpenFAST uses option #2, so you don’t specify dependencies, but it seems like you are trying to do option #1.

I have never attempted using a class passed through the DISCON-type interface. I’d be surprised if class is supported in the c bindings that we need to use in OpenFAST to make it possible to allow DLLs written in a language other than Fortran. (You can work around that a bit by pass pointers to objects instead.) But, if you want to link your DLL using option #1, I think that should work.

Anyway, I guess my answer really depends on the way you want to set up your DLL with your in-house software.

Hello together,
thank you very much for the helpful answers.
I have loaded the DLL in the way I think is the 2nd suggested solution. As a result Visual Studio tells me that libdiscon.dll was loaded.
However, the entire programme aborts when the DISCON function is called. I suspect that it must be because the input values are wrong. In the code snippet below you can see the code that calls the routine.
I have been provided with an optimised IN file (DISCON_DATA.IN), which I have placed in the working folder where the EXE is also created.
module class_controller


    implicit none
    
    type :: model_controller
        logical   :: boolean_abort = .FALSE. 
        contains
        procedure :: use_controller
      
    end type model_controller
    
        
    contains
    
    subroutine use_controller(this, swap)
      use, intrinsic :: iso_c_binding, only : c_float, c_int, c_char, c_null_char
      use DISCON_m, only : SetupDISCON, DISCON
      implicit none
     
      class(model_controller), intent(inout)        :: this
      real(kind = 8), dimension(78), intent(inout)  :: swap
      real(c_float), dimension(78)                  :: ctrl_avrSWAP(78)                    
      integer(c_int)                                :: ctrl_aviFAIL                    
      character(kind=c_char,len=13)                 :: ctrl_accINFILE
      character(kind=c_char,len=51)                 :: ctrl_avcOUTNAME           
      character(kind=c_char,len=49)                 :: ctrl_avcMSG    
    
    
      call SetupDISCON(this%boolean_abort) 
      if (this%boolean_abort .eqv. .TRUE.) then
        return
      end if
      
      ctrl_accINFILE   = c_char_"DISCON_DATA.IN" // c_null_char
      ctrl_avcOUTNAME  = c_char_"OUTNAME.txt" // c_null_char
      !ctrl_avcMSG      = c_null_char
      !ctrl_aviFAIL     = 0_c_int

      ctrl_avrSWAP     = swap               ! converting input to C++ float values
      ctrl_avrSWAP(50) = 13.0_c_float       ! length of the path "ctrl_accINFILE" / No. of characters in the INFILE argument, -1 because it starts counting at 0 ?
      ctrl_avrSWAP(51) = 10.0_c_float       ! No. of characters in the OUTNAME argument
     
      ! Calling the actual controller function from the DLL
      call DISCON(ctrl_avrSWAP, ctrl_aviFAIL, ctrl_accINFILE, ctrl_avcOUTNAME, ctrl_avcMSG )
      
      swap = ctrl_avrSWAP ! Enter the new values in the array
    
    end subroutine use_controller
    
end module class_controller

My specific questions at this point are:

  • Are there any other external files that the ROSCO controller needs?
  • Can I assume an array size for the variable ctrl_swap or must it necessarily be passed in an allocatable way?
  • Possibly the problem is that it is the first time step, so values in the swap array are missing (=0) because they could not yet be calculated. Which values must necessarily be included in the first call of DISCON so that the controller begins to work? (For now, it would be enough for me to control only the pitch).

Best regards,
David

I don’t know what’s inside your DISCON routine (and have no experience with ROSCO), so it’s hard to answer conclusively.

However, I can say that the “normal” Bladed DLL DISCON routine needs an already allocated avrSWAP array. For the Bladed v3.6 interface, it needed to be at least 85 elements long. Later versions of Bladed specify 165 elements. However, that size is really just determined by how your calling routine and DISCON use that data. Having an array too big is better than too small.

It might be helpful to read the Bladed documentation on the DISCON interface, or read the Fill_avrSWAP routine in OpenFAST source code: openfast/BladedInterface.f90 at main · OpenFAST/openfast · GitHub to see what needs to be set on the first call to the DLL (but that will really depend on how the DISCON routine in the DLL is actually written). I would definitely set anything that is a parameter (almost anything that doesn’t start with u%)

If you add print statements inside the DLL, you might be able to tell where the program is actually failing… My first guess is that you may have to send larger array sizes.