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

Trying to reinitialize a Fortran DLL (called from C#) using FreeLibrary

jarvusluntatintel
1,341 Views

I have a large, legacy, Fortran program (~100,000 lines) that I am trying to convert from an .exe to a Fortran DLL that will be called from C#. Running the Fortran program involves passing data back and forth between C# and the DLL. I finally got this working for the first data set. However, when I go to run the second data set, I need to reinitialize the Fortran DLL. For instance, all the allocated arrays need to be in a non-allocated state, variables reinitialized to the original state, counters set to zero, etc. Since it would be a huge undertaking to add code to do this, I would like to just remove the Fortran DLL from memory and then reload it and get the DLL to behave the same way it did the first time it was loaded. I have not been able to do this (my understanding of C#, .NET, and DLLs are all rather limited).

Here is basically my C# code (for simplicity, I'm only showing the if/Throw exception for the first call). I am only trying to initialize my Fortran DLL (including allocating arrays and reading data) and then remove the DLL and then reinitialize it.

public class ModelDotNetAccess
{
IntPtr _FortranDllHandle;

public void Initialize(string dataPath, string dllPath)
{
_FortranDllHandle = Kernel32Wrapper.LoadLibrary(dllPath);

string fullDataPath = Path.GetFullPath(dataPath);

//open fortran data files, allocate arrays, and read data

if (!(ModelDllAccess.Initialize(fullDataPath, ((uint)fullDataPath.Length))))
{
CreateAndThrowException();
}


ModelDllAccess.Finish(); //close files.

Kernel32Wrapper.FreeLibrary(_FortranDllHandle);

_FortranDllHandle = Kernel32Wrapper.LoadLibrary(dllPath);

//next call causes runtime error, arrays already allocated

ModelDllAccess.Initialize(fullDataPath, ((uint)fullDataPath.Length));

}

}


public class ModelDllAccess
{

[DllImport("ProgramDll.dll", EntryPoint = "initialize")]
public static extern bool Initialize(string filePath, uint length);

[DllImport("ProgramDll.dll", EntryPoint = "finish")]
public static extern bool Finish();
}


Fortran DLL

c ----------------------------------------------------------------------
logical function Initialize (filePath_in)
c ----------------------------------------------------------------------

!dec$ attributes dllexport::initialize
implicit none

!code removed

return
end function Initi alize

c ----------------------------------------------------------------------
logical function Finish ()
c ----------------------------------------------------------------------

!dec$ attributes dllexport::finish

! code removed

return
end function Finish

Even though I have a FreeLibrary call, when I reload the DLL and go to allocate arrays, I get a runtime error, because the arrays are still allocated from the first data set. The FreeLibrary is not destroying the DLL. Is there something I am missing or some other approach that would be easier?

Any suggestions would be appreciated.

0 Kudos
6 Replies
van_der_merwe__ben
New Contributor I
1,341 Views
For the same reason we call LoadLibrary and FreeLibrary on a Fortran DLL from C++ code as well, and it does work (i.e. everything gets reset to zero).

It is likely that something in your code or .NET still hangs on to it (is _FortranDllHandle just a raw handle or a wrapper with some smarts that might need to be set to null before the next LoadLibrary call?).Surprised smiley [:-O]

In general, in theory we should be able to easily get hold of the DLL startup method which gets called to initialize everything when the DLL initialiy loads and call that again, but it may not expect to get called again and some other potential problems may creep up.

It would be nice to have a standard Reset type method exported from such DLLs that could be called, since loading and unloading the DLL is of course not very efficient or ideal.
0 Kudos
Steven_L_Intel1
Employee
1,341 Views
I don't know C#, but I'd guess that the Dllimport you have is effectively doing a LoadLibrary for you and you can't free the DLL using the system handle. If you can figure out how to call and use GetProcAddress, you can manage the calls yourself.

The alternative is to write an initialization routine in the DLL that resets everything.
0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,341 Views
MADsblionel:
I don't know C#, but I'd guess that the Dllimport you have is effectively doing a LoadLibrary for you and you can't free the DLL using the system handle. If you can figure out how to call and use GetProcAddress, you can manage the calls yourself.


I had a very similar problem with Delphi, and I assume the same mechanism goes for C#. There, a mere declaration of a DLL routine in a Delphi exe causes the DLL to be statically bound on startup (i.e. no LoadLibrary is ever involved). And FreeLibrary does not work for statically bound DLL images. Rather than tweaking all the Delphi code to use dynamic binding (LoadLibrary) we ended up by...

MADsblionel:
The alternative is to write an initialization routine in the DLL that resets everything.


...writing a cleanup routine in the dll, thus I third the advice.

Actually, a fairly foolproof method would be to do both regular cleanup (deallocate/close) and FreeLibrary (which will ensure that no locally allocated arrays persist in case of an exception within dll). But, if you don't have to guard against exceptions in Fortran code, a cleanup routine will be fine.


0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,341 Views
JugoslavDujic:
There, a mere declaration of a DLL routine in a Delphi exe causes the DLL to be statically bound on startup (i.e. no LoadLibrary is ever involved). And FreeLibrary does not work for statically bound DLL images.


Taking a more thorough look at your code, you seem to have too much LoadLibrary calls. I don't know how C# actually binds non-managed Dlls, but here's a few related facts which might help:
  • Every LoadLibrary increases the dll's reference count by one
  • Every FreeLibrary decreases the dll's reference count by one
  • The dll will be actually unloaded only when the reference count reaches zero.
  • If the dll is statically bound, you don't need LoadLibrary at all (but, like I said, FreeLibrary won't work as well, and the dll can't be unloaded)
  • For the dll to be dynamically bound, it's sufficient to LoadLibrary once. In that case, you should declare function pointers and obtain them through GetProcAddress.
0 Kudos
jarvusluntatintel
1,341 Views

Thanks for the comments and suggestions. As regards the suggestions:


IntPtr _FortranDllHandle;

As far as _FortranDllHandle, when I do a F12 on IntPtr I get,

namespace System

public struct IntPtr : ISerializable
{
public static readonly IntPtr Zero;
public IntPtr(long value);
//code removed
public static int Size { get; }
public override int GetHashCode();
//more code removed

So it looks like it has several different ways to store the handle and a few methods. Not very clear to me what I would set to null.


I did a search on GetProcAddress. It looks like I would have quite a lot to learn before I could use that, but it sounds like it has potential for me to research.

As part of my attempts at understanding how this works, I made two DLLs with the exact same name (and they exported the exact same functions) and put them in different directories. Once one of them was loaded, I was never able to get rid of the first one and load the second. However, by changing the dll path name in:

_FortranDllHandle = Kernel32Wrapper.LoadLibrary(dllPath);

I could load either one initially (they had different run time behavior so I could tell them apart). So it isn't being loaded statically, unless I don't understand correctly what statically loaded means.

So if I have one LoadLibrary call and one FreeLibrary call, I'm puzzled to what else is counting as a LoadLibrary.

The manually reinitializing the DLL is, in theory the cleanest. For the allocate statements, it would be straight forward (if tedious) to add a if (allocatted (array)) deallocate (array) in front of every allocate statement. However, the program assumes that local variables are saved (I had to change to the compilor settings to "All Variables SAVE"). The program uses data statements for initializing:

integer number
data number /0/

These would have to be changed to number = 0 andencapsulated in a if/then block and I would alsohave to figure out, for every subroutine, since the second data has been loaded, is this the first time this subroutine has been called. Worse yet, I have to set the compiler option to Initialize Local Saved Scalars to Zero, which means some variables aren't being explicitly initialized at all.

Finally, I am mulling over the possiblity of trying to create a new thread and loading the DLL in that and then killing the thread (although the documentation that came with the C# code warned about performance issues for this approach).

Thanks again for the help.

0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,341 Views
With "static binding", the dll is loaded at startup, with the .exe, and cannot be unloaded. With "dynamic binding", the dll is loaded using LoadLibrary, the pointer returned by GetProcAddress is dereferenced, function called, and dll can be unloaded at wish using FreeLibrary.

I don't think any of us is familiar with C#, but you can make a test using GetModuleHandle("YourDll.dll") from within C# code before you make any calls to the dll. If it succeeds (returns non-zero), it indicates that C# uses "static binding" behind the curtains.

As for the HANDLE/IntPtr, the handles are "opaque" types, i.e. you're not supposed to know what they mean. At most, you can assign it a zero somehow (I don't speak C# etc.)

If the said test reveal static binding, the lines like
 [DllImport("ProgramDll.dll", EntryPoint = "initialize")]
 public static extern bool Initialize(string filePath, uint length);
are a likely culprit. To make dynamic binding work, you have to declare a pointer to a function (rather than the function itself) and resolve it at run-time. In C++, that would be:
typedef void (__stdcall * FPTR_INITIALIZE) (char*, unsigned);
FPTR_INITIALIZE ptrInitialize;
...
if (ptrInitialize = GetProcAddress(hDll, "initialize"));
 ptrInitialize(szPath, 42);
Again, sorry, I don't know how to do that in C#. But see here.
0 Kudos
Reply