Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
Beginner
20 Views

How do I properly create a dll?

Jump to solution

Hello,

I am attempting to create dll files in Fortran.  If I am not mistaken, I am supposed to export a .dll file from my project, which is a .f90 file as opposed to a .dll file, in order for another program (written in Basic) to link to it.  To get things started, I have made a simple program in Fortran that goes as follows:
 

! TestDLL.f90 

!
!  FUNCTIONS/SUBROUTINES exported from TestDLL.dll:
!  TestDLL - subroutine 
!
subroutine TestDLL

  ! Expose subroutine TestDLL to users of this DLL
  !
  !DEC$ ATTRIBUTES DLLEXPORT::TestDLL

  ! Variables
    Integer K

 ! Body of TestDLL
    K = 1
    
    Return

end subroutine TestDLL

 

However, when I create the .dll file by running this program, I see the following when I open the .dll file in a text editor:

 

This program cannot be run in DOS mode.

Stack around the variable '' was corrupted. The variable '' is being used without being initialized.  The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

A cast to a smaller data type has caused a loss of data.  If this was intentional, you should mask the source of the cast with the appropriate bitmask.  

Changing the code in this way will not affect the quality of the resulting optimized code.

    Stack memory was corrupted

    A local variable was used before it was initialized

   Stack memory around _alloca was corrupted

 Unknown Runtime Check Error

   Runtime Check Error . 
 
Unable to display RTC Message.   Run-Time Check Failure  Unknown Filename   Unknown Module Name Run-Time Check Failure  Stack corrupted near unknown variable   user32.dll   wsprintfA   Stack area around _alloca memory reserved by this function is corrupted
    
Stack area around _alloca memory reserved by this function is corrupted A variable is being used without being initialized.  

Stack pointer corruption
Cast to smaller type causing loss of data
Stack memory corruption Local variable used before initialization
Stack around _alloca corrupted                    
                                                                                                                      
                                                                                                                        <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly> 

Where could I going wrong here?

Thanks,

Jamal

 

 

 

                                                                                                                                                                                                                         

 

 

0 Kudos

Accepted Solutions
Highlighted
Valued Contributor II
20 Views

Alternatively, you can declare the function BIND(C) and then use !DEC$ ATTRIBUTES STDCALL, DLLEXPORT and then ifort will get the names and calling convention right for you.

!  TestDLL.f90 
! Compile with
! ifort /DLL TestDLL.f90
!
!  FUNCTIONS/SUBROUTINES exported from TestDLL.dll:
!  TestDLL - subroutine 
!
Integer Function TestDLL(QuickNum) bind(C,name='TestDLL')
   use ISO_FORTRAN_ENV
   implicit none

  ! Expose subroutine TestDLL to users of this DLL
  !
  !DEC$ ATTRIBUTES STDCALL,DLLEXPORT::TestDLL

! integer in VBA corresponds to 16-bit integer
! Your VBA code specified passing Quicknum by value
    Integer(INT16), value :: Quicknum
            
    TestDLL = Quicknum

end function TestDLL

Output of dumpbin /exports TestDLL.dll:

Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file testDLL.dll

File Type: DLL

  Section contains the following exports for TestDLL.dll

    00000000 characteristics
    55D64F72 time date stamp Thu Aug 20 16:06:42 2015
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00001000 TestDLL
          2    1 00001000 _TestDLL@2

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        1000 .text

So we can see that ifort exports both the undecorated name that VBA and LoadLibrary/GetProcAddress want and also the decorated name that a program linking with the *.lib file needs when you use !DEC$ ATTRIBUTES STDCALL.

 

View solution in original post

0 Kudos
10 Replies
Highlighted
20 Views

You are trying to create a DLL from Fortran source. It looks as if you did that ok. Opening the DLL in a text editor is not useful. 

Since you want to use this DLL from Visual Basic, there are more things you need to do, however. VB requires the STDCALL calling convention, which is not the Intel Fortran default. Second, you'll get a "decorated' name from Fortran which would require you to use the same decoration in VB, To fix both of these, replace:

!DEC$ ATTRIBUTES DLLEXPORT::TestDLL

with:

!DEC$ ATTRIBUTES STDCALL,REFERENCE,ALIAS:"TestDLL",DLLEXPORT::TestDLL

Make sure you call the routine "TestDLL" in VB.

Retired 12/31/2016
0 Kudos
Highlighted
Beginner
20 Views

Thank you Steve, what you instructed me to do worked.

Now I am trying to pass variables from Basic to Fortran.  I have changed the subroutine in Fortran to a function, which goes as follows:

 

!  TestDLL.f90 
!
!  FUNCTIONS/SUBROUTINES exported from TestDLL.dll:
!  TestDLL - subroutine 
!
Integer Function TestDLL(QuickNum)

  ! Expose subroutine TestDLL to users of this DLL
  !
  !DEC$ ATTRIBUTES STDCALL,ALIAS:"TestDLL",DLLEXPORT::TestDLL
  !DEC$ ATTRIBUTES REFERENCE :: QuickNum

    Integer Quicknum
    
        
    TestDLL = Quicknum

end function TestDLL

This dll is called by the following Visual Basic program:

Public Class Form1
    Declare Function TestDLL Lib "C:\Users\Jamal_S_\Documents\Visual Studio 2013\Projects\TestDLL\TestDLL\Debug\TestDLL.dll" (ByVal Quicknum As Integer) As Integer
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Button1.Text = TestDLL(1)
    End Sub
End Class

Basically, the text of Button1 should just say whatever number I send to the dll.  When I run it however, I get the following error:

An unhandled exception of type 'System.AccessViolationException' occurred in WindowsApplication1.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Again, where am I going wrong?

 

0 Kudos
Highlighted
New Contributor I
20 Views

Try using 'ByVal Quicknum As Integer' in the VBasic definition.

It may be that the value 1 is being taken in the fortran as an address (you specify REFERENCE for the Fortran argument. That will put it clearly out of the allowed range.

0 Kudos
Highlighted
Valued Contributor II
21 Views

Alternatively, you can declare the function BIND(C) and then use !DEC$ ATTRIBUTES STDCALL, DLLEXPORT and then ifort will get the names and calling convention right for you.

!  TestDLL.f90 
! Compile with
! ifort /DLL TestDLL.f90
!
!  FUNCTIONS/SUBROUTINES exported from TestDLL.dll:
!  TestDLL - subroutine 
!
Integer Function TestDLL(QuickNum) bind(C,name='TestDLL')
   use ISO_FORTRAN_ENV
   implicit none

  ! Expose subroutine TestDLL to users of this DLL
  !
  !DEC$ ATTRIBUTES STDCALL,DLLEXPORT::TestDLL

! integer in VBA corresponds to 16-bit integer
! Your VBA code specified passing Quicknum by value
    Integer(INT16), value :: Quicknum
            
    TestDLL = Quicknum

end function TestDLL

Output of dumpbin /exports TestDLL.dll:

Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file testDLL.dll

File Type: DLL

  Section contains the following exports for TestDLL.dll

    00000000 characteristics
    55D64F72 time date stamp Thu Aug 20 16:06:42 2015
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00001000 TestDLL
          2    1 00001000 _TestDLL@2

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        1000 .text

So we can see that ifort exports both the undecorated name that VBA and LoadLibrary/GetProcAddress want and also the decorated name that a program linking with the *.lib file needs when you use !DEC$ ATTRIBUTES STDCALL.

 

View solution in original post

0 Kudos
Highlighted
Valued Contributor II
20 Views

Noticed a couple of mistakes in my last post.

Firstly, TestDLL is typed in VBA as returning an integer, so I should have declared it as Integer(INT16) Function TestDLL

The other part is really strange. Shouldn't the caller always push multiples of the stack word size, which in 32-bit windows is 4 bytes? In that case there should be 4 bytes of arguments pushed on the stack and the name should be mangled as _TestDLL@4 but from the DUMPBIN output above it can be seen that ifort compiles it to _TestDLL@2 .  gfortran compiles to _TestDLL@4 so who is right? I don't have MSVC++; what does cl do in this case? Is it ifort or gfortran that is completely broken?

 

0 Kudos
Highlighted
Valued Contributor II
20 Views

OK, I've determined that it's ifort that is completely broken in this instance.

subroutine sub(x,y)
   use ISO_FORTRAN_ENV
   implicit none
   integer(INT16) x, y
   interface
      subroutine s(x,y) bind(C,name='s')
         import
         implicit none
!DEC$ ATTRIBUTES STDCALL :: s
         integer(INT16), value :: x, y
      end subroutine s
      subroutine t(x,y)
         import
         implicit none
!DEC$ ATTRIBUTES STDCALL, DECORATE, ALIAS: 't' :: t
!DEC$ ATTRIBUTES VALUE :: x, y
         integer(INT16) x, y
      end subroutine t
   end interface

   call s(x,y)
   call t(x,y)
end subroutine sub

Now let's check the output with ifort /c /FA std2.f90

_SUB PROC NEAR 
; parameter 1: 12 + esp
; parameter 2: 16 + esp
.B1.1:                          ; Preds .B1.0
L1::
                                                           ;1.12
        push      esi                                           ;1.12
        push      ebp                                           ;1.12
        mov       ebp, DWORD PTR [16+esp]                       ;1.12
        mov       esi, DWORD PTR [12+esp]                       ;1.12
        movsx     eax, WORD PTR [ebp]                           ;21.9
        push      eax                                           ;21.9
        movsx     edx, WORD PTR [esi]                           ;21.9
        push      edx                                           ;21.9
        call      _s@4                                          ;21.9
                                ; LOE ebx ebp esi edi
.B1.2:                          ; Preds .B1.1
        movsx     ebp, WORD PTR [ebp]                           ;22.9
        push      ebp                                           ;22.9
        movsx     eax, WORD PTR [esi]                           ;22.9
        push      eax                                           ;22.9
        call      _t@8                                          ;22.9
                                ; LOE ebx edi
.B1.3:                          ; Preds .B1.2
        pop       ebp                                           ;23.1
        pop       esi                                           ;23.1
        ret                                                     ;23.1
        ALIGN     16
                                ; LOE
; mark_end;
_SUB ENDP

Note that for both invocations, ifort is pushing 4 bytes per argument on the stack as it should, but when bind(C) was used ifort counted up the number of bytes on the stack incorrectly and mangled the name as _s@4 .

 

0 Kudos
Highlighted
Beginner
20 Views

Thank you Anthony Richards and Repeat Offender for your input.

Repeat Offender, the amendments you made to my code made it work, I highly appreciate it.  Thank you for clearing up all the confusion.

0 Kudos
Highlighted
20 Views

My colleague Kevin has created a tracking ticket for the incorrect external name in the BIND(C) case above.

If you need to reference that ticket later, its id is: DPD200375220

        thanks --

                          --Lorri

(Resolution Update on 02/27/2016): The defect DPD200375220 is fixed in the Intel® Parallel Studio XE 2016 Update 2 Release (PSXE 2016.2.055/ CnL 2016.2.180 - Windows)

0 Kudos
Highlighted
Valued Contributor II
20 Views

Thanks, Lorri & Kevin, for your attention to this matter. I suppose I have to retract the remark about ifort's behavior being 'totally broken'. I had been afraid that ifort might be generating some wrong code along with the incorrect name mangling, but further testing shows that ifort's code is fine except for the decorated names. I came up with an example where ifort must generate the names for the callee:

function f(x,y) bind(C,name='f')
   use ISO_FORTRAN_ENV
   implicit none
!DEC$ ATTRIBUTES STDCALL :: f
   integer(INT16) f
   integer(INT16), value :: x, y
   f = x+y
end function f

function g(x,y)
   use ISO_FORTRAN_ENV
   implicit none
!DEC$ ATTRIBUTES STDCALL, DECORATE, ALIAS: 'g' :: g
   integer(INT16) g
   integer(INT16), value :: x, y
   g = x+y
end function g

Output of ifort /c /FA std3.f90

       ALIGN     16
 PUBLIC _f@4
_f@4 PROC NEAR 
 PUBLIC _f
_f::
; parameter 1: 4 + esp
; parameter 2: 8 + esp
.B1.1:                          ; Preds .B1.0
L1::
                                                           ;1.10
        movsx     eax, WORD PTR [4+esp]                         ;1.10
        movsx     edx, WORD PTR [8+esp]                         ;1.10
        add       eax, edx                                      ;7.9
        ret       8                                             ;8.1
        ALIGN     16
                                ; LOE
; mark_end;
_f@4 ENDP
_TEXT ENDS
_DATA SEGMENT  DWORD PUBLIC FLAT  'DATA'
_DATA ENDS
; -- End  _f@4
_TEXT SEGMENT  PARA PUBLIC FLAT  'CODE'
; -- Begin  _g@8
_TEXT ENDS
_TEXT SEGMENT  PARA PUBLIC FLAT  'CODE'
; mark_begin;
       ALIGN     16
 PUBLIC _g@8
_g@8 PROC NEAR 
 PUBLIC _g
_g::
; parameter 1: 4 + esp
; parameter 2: 8 + esp
.B2.1:                          ; Preds .B2.0
L2::
                                                           ;10.10
        movsx     eax, WORD PTR [4+esp]                         ;10.10
        movsx     edx, WORD PTR [8+esp]                         ;10.10
        add       eax, edx                                      ;16.9
        ret       8                                             ;17.1
        ALIGN     16
                                ; LOE
; mark_end;
_g@8 ENDP

So we can see that even with BIND(C), ifort pops the right number of bytes off the stack on return. In some sense there are 6 places in my two examples where the number of bytes is needed: in the name as seen by the caller and callee and in the ret N instruction in the epilog, for both DECORATE and BIND(C). In an ideal universe perhaps the same code would get reused in all 6 instances, but it seems that code was written de novo when BIND(C) + STDCALL was introduced, and SUM(SIZES_OF_ARGS_IN_BYTES) rather than SUM(IAND(SIZES_OF_ARGS_IN_BYTES+3,INT(Z'FFFFFFFC'))) was used in the new version to create the mangled names.

 

0 Kudos
Highlighted
Employee
20 Views

The defect with BIND(C) detailed in post #7 is fixed in the recently released PSXE 2016 Update 2 release (PSXE 2016.2.055/ CnL 2016.2.180 - Windows)

0 Kudos