Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Linking with a DLL

Ulysses
Beginner
4,401 Views

There might be something simple I am over looking but I am having a hard time calling a subroutine from  a Dll in an Intel Fortran console project. I have attached the console project and the dynamic library project (both in Intel Fortran).  When I include the Dll file in the console project I get an error stating: LNK1107: invalid or corrupt file: cannot read 0x20. There is also a warning: file format not recognized [dll project debug forlder path]\SamsEngineDlliF.dll.  

0 Kudos
30 Replies
Steven_L_Intel1
Employee
3,087 Views

One does not link to DLLs. Instead, you link to the .LIB that was created (the "export library") when the DLL was built. Adding a DLL to a project doesn't do anything useful. Your ZIP didn't include any of the application sources, so I'm not really sure what you're trying to do.

If you don't have the .LIB for the DLL, you'll need to use dynamic loading, as demonstrated in the provided sample DLL\Dynamic_Load.

0 Kudos
mecej4
Honored Contributor III
3,087 Views

The DLL has no exported symbols. How did you build it? How did you specify the list of exported symbols when you built the DLL? Do you have the .EXP and .LIB files that were generated along with the DLL? The linker used by Intel Fortran does not link directly with a DLL, but only through the stub library that I mentioned.

0 Kudos
Ulysses
Beginner
3,087 Views

Hey Steve,  so I have to include both the lib and the dll as additional libraries?

Hello Mecej4, the stub library is .exp? Also, I wasn't aware that I had to specify exported symbols for the dll, I will look into that. 

I have re-uploaded the projects with the source included. I didn't realize the previous attachment didn't include the source files.

It seems that the mistake I am making is treating the dll as if it where a lib. I have to read up on working with dll's, any recommendations?

Regards,

Ulysses

0 Kudos
Ulysses
Beginner
3,087 Views

If it helps: SamsExeTemplateIF is the console project that is supposed to use the library produced in the SamsEngineDllIF project. The DLL project output doesn't have the .exp or .lib files in the debug folder. Perhaps I am not building the DLL project correctly. I could definitely use some recommended reading material.

-Ulysses

0 Kudos
mecej4
Honored Contributor III
3,087 Views

Now that you have provided the source files, I can see that you are experimenting with a simple example of building and linking with a dynamic library, and that there is only one routine name that is exported by the DLL and imported into the program that uses the DLL. For this scenario, the simple solution is to add the directive

cDIR$ ATTRIBUTES DLLEXPORT :: UserSubroutines

in subroutine USERSUBMOD and to add the directive

cDIR$ ATTRIBUTES DLLIMPORT :: UserSubroutines

to your main program. Then, when you build the DLL, a library called USERSUBS.LIB will be produced as a side effect. Specify this as an additional library in your project with the Sourcefile.FOR file. At run time, make sure that the DLL is accessible along the PATH string.

If the preceding sounds a bit complex, I suggest that you put both source files into a single directory, and open a Fortran command prompt and change to that directory. After you have added the directives that I suggested, you do:

     ifort /LD UserSubs.FOR

     ifort Sourcefile.FOR UserSubs.lib

and your Sourcefile.exe will be generated.

0 Kudos
Steven_L_Intel1
Employee
3,087 Views

Please use !DIR$ as the prefix and not cDIR$.

0 Kudos
Ulysses
Beginner
3,087 Views

Thanks everyone for the help so far. I have been able to create the dll with the method outlined by meceje4 and the modification proposed by Steve. Now I am having difficulty with  dynamically loading the created dll in VB.NET.

The Fortran source I am using is:

      SUBROUTINE SAMSDLL

      !MS$ATTRIBUTES DLLEXPORT,ALIAS:'SAMSDLL' :: SAMSDLL

      !MS$ ATTRIBUTES STDCALL, REFERENCE :: SAMSDLL

c      CALL SAMS2000
      write(*,*)'Hello'

      RETURN
      END

This produces a dll that I try to call from Visual Basic .NET.

The VB.NET source is:

Public Class Form1

    Declare Sub SamsDll Lib "SamsPS.dll" Alias "SAMSDLL" ()

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        SamsDll()
    End Sub
End Class

The vb project is able to call SamsDll() but in the output window I don't get the "Hello" text that we expect to be printed. I have tried changing !MS$ to !DIR$ and I get the same error. Any ideas?

 

0 Kudos
Anthony_Richards
New Contributor I
3,087 Views

I suggest that you change the WRITE statement to use

USE IFWIN
integer iret

iret=MessageBox(0,'Hello - it works!'C,'SAMSDLL Test'C,MB_OK)

 

because the VBNET source has no console to WRITE to.
 

0 Kudos
Ulysses
Beginner
3,087 Views

Thanks  Anthony. The console write statements in fortran are directed to the project output window in the debugger. I don't actually plan on having a write statement in the fortran subroutines; I'm just using it as a way to tell if everything is working ok. Sadly, the write statement is not sent to the console when I build the dll with Intel fortran but it does work when I use a different compiler.

0 Kudos
Steven_L_Intel1
Employee
3,087 Views

If there is no console window you'll need to create one with a call to AllocConsole, a Windows API routine.

0 Kudos
Ulysses
Beginner
3,087 Views

I added the console to the VB.NET source as follows:

Public Class Form1

    Declare Function AllocConsole Lib "kernel32" () As Boolean

    Declare Function FreeConsole Lib "kernel32" () As Boolean 

    Declare Sub SamsDll Lib "SamsPS.dll" Alias "SAMSDLL" ()

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        AllocConsole()
        SamsDll()
       FreeConsole()
    End Sub

End Class

And I am not able to get the write statement to show even though the same source in a different compiler does work. Any other ideas guys?

 

 

 

0 Kudos
Anthony_Richards
New Contributor I
3,087 Views

Since the WRITE  statement is in the DLL, then I suspect that the console must be allocated there also.
If all you want is a message that the DLL routine has been entered, then MessageBox is the way to go.

0 Kudos
Steven_L_Intel1
Employee
3,087 Views

It shouldn't be necessary to allocate the console in the DLL, I don't think. You do need to do the AllocConsole before ANY Fortran I/O is done.

0 Kudos
Anthony_Richards
New Contributor I
3,087 Views

Forum quirk made me post twice. how do I delete a post?

0 Kudos
Steven_L_Intel1
Employee
3,087 Views

You can't - I deleted it for you.

0 Kudos
Ulysses
Beginner
3,087 Views

Ok, so I've been able to put together some sample projects that outline the approach I am using. The goal is to get a VB.NET winforms application(DllLauncher) to call a subroutine(ENGINE) in an Intel Fortran dll (SAMSIFCONSOLE) when Button1 is pressed. This needs to be done in a manner that would allow for loading and unloading the Intel Fortran dll during program execution. This is accomplished through the use of a child Application Domain in VB.Net. We load a managed dll (SamsManaged.dll) into the child AppDomain. The managed dll in turn P/Invokes the unmanaged dll's for the ENGINE Fortran subroutine and the functions responsible for allocation/de-allocation of the console. 

The DllLauncher application works correctly on the first button press but if the button is pressed again (without closing the application) Intel Fortran or the AppDomain seems to be called in an infinite loop that prints text to the console. This could be related to the bug fix referenced in (https://software.intel.com/en-us/articles/cannot-write-to-window-created-with-allocconsole) but I am not sure. 

I have tried doing the console allocation in the Fortran and that had the same behavior. I also tried not explicitly allocating/de-allocating a console and the behavior of the problem changed. Instead of getting an infinite loop of text to the console, the console itself is opened over and over again until the user restarts his computer (I couldn't get task manager to close the application running in the child AppDomain).

0 Kudos
Anthony_Richards
New Contributor I
3,087 Views

If I want to try and run your code, please can you explain in more detail what the Visual Basic code (from your Dlllauncher form's code) below is intended to do?

  Public Sub RunSamsEngine()
        'create a child AppDomain to call the engine so that we can hot load/unload the SamsUser.dll.
        ' Set up the AppDomainSetup 
        Dim setup As New AppDomainSetup()
        setup.ApplicationBase = "c:\Sams2000\"
        setup.ConfigurationFile = "AppBase.config"
        ' Set up the Evidence 
        Dim baseEvidence As Evidence = AppDomain.CurrentDomain.Evidence
        Dim evidence As New Evidence(baseEvidence)

        ' Create the AppDomain 
        Dim newDomain As AppDomain = AppDomain.CreateDomain("newDomain", evidence, setup)

        ' Add the engine assembly
        Dim a As Assembly = newDomain.Load("SamsManaged")
        Dim mytype As Type = a.GetType("SamsManaged.WrapperLibrary.SamsEngineWrapper")

        ' Get the SamsDLL method wrapper
        Dim SamsEngine As MethodInfo = mytype.GetMethod("SAMSDLLWRAPPER")
        Dim obj1 As Object = Activator.CreateInstance(mytype)

        'call the SAMS engine
        Try
            SamsEngine.Invoke(obj1, Nothing)
            MsgBox("Simulation has finished.")
        Catch
            MsgBox("Problem in calling the engine, please try again.")
        End Try

        'Unload the child AppDomain.
        AppDomain.Unload(newDomain)
    End Sub

Please can you also explain where the required DLL's are to be placed in order for them to be found by your VB code?

As far as I can tell, you refer to the following DLLs:

SAMSIFCONSOLE.dll
SamsManaged.dll

and these are required by your DllLauncher.exe. Correct?

I ran DllLauncher.exe in Visual Studio under the debugger AFTER I had copied the other two DLLs to the same folder as the .EXE.
It failed at the line

        Dim a As Assembly = newDomain.Load("SamsManaged")

because 'SamsManaged' could not be found.


 

0 Kudos
Ulysses
Beginner
3,087 Views

Hello Anthony, I re-wrote the DllLauncher application to be more concise and to get rid of the dependence on SamsManaged.dll and the project that creates it. In this project, SAMSIFCONSOLE.dll needs to be copied into the output directory of DllLauncher (which should be [ProjectRoot]\bin\x86\Release). 

The VB code in RunSamsEngine() creates an application domain into which we create an instance of the DomainAgent class. The DomainAgent class handles loading the unmanaged dll''s and making a call to the Fortran subroutine SAMSDLL. When the application domain is unloaded, it in turn releases the un-managed dll's. In this example project, the reason we create a child application domain is to allow for unloading/loading dll's without having to close the application. 

The behavior of the DllLauncher application should be that upon clicking the button a console window is opened onto which the fortran subroutine prints "hello world.". The console window will then close upon the user pressing the enter key. On each successive button click the application should do the same thing; print "hello world." to a console and then close after the user presses the enter key. Later on, I can add code to replace SAMSIFCONSOLE.dll in between button clicks and I would have no problem doing so because the dll will be released by the process when the child application domain is unloaded.

Unfortunately, the behavior of the application on a second button click is to print "hello world." and then repeatedly print out text that says " Fortran Pause [..]". I have no idea why this would happen.

 

0 Kudos
Anthony_Richards
New Contributor I
3,087 Views

I am none the wiser re: my question as to why the program failed at the point I showed.

I assumed that once all DLLs were in the same folder as the executable that they would be 'found' when the application was commanded to load them. Apparently that arrangement was not enough.

How does your 'new' version differ from the previous one and where should the required DLLs go in order to get your new case to run?

 

0 Kudos
Ulysses
Beginner
2,952 Views

Yeah sorry I didn't really answer your question. The previous version of the launcher program created a child Application Domain (AppDomain) with a specific application directory: "c:\temp". The child AppDomain looks there for dll's to load. The child AppDomain didn't know where the exe directory was located. In the new version I let the built in AppDomain function CreateDomain handle all of the configuration and creation of the child AppDomain .It automatically adds the parent AppDomain's executable directory as the base path. In this case the dll's should go in the same directory as the executable (which is better programming practice). Hope that clarifies, feel free to let me know if I should expand on anything.

0 Kudos
Reply