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

Exposing a Fortran dll to both COM and Fortran?

mbrengartner
Beginner
6,059 Views

Hello, I've been tasked with coming up with a way to expose some Fortran subroutines in a dll to both other Fortran projects and COM.

First of all: I do not know Fortran. You may wonder about the wisdom of choosing me to accomplish this task - I know I do ;) It is what it is!

I've spent considerable time researching this and I think it's time to ask for some direct help. Thesubroutines are:

sub1(real8a, real8b, char256c, char80d)

sub2 has 19 real(8) parameters

sub3 has 22 real(8) parameters

Here's what I've tried so far:

Put DLLExport statements in the dllto expose the subroutines, and DLLIMPORT statements in the project.

Created a generic console app that calls into the dll, using the .lib file. Result: Unresolved external.

I've used the /gen-interfaces switch to get a whole mess o' .mod and f90 files. I've linked to those individual mod files. Result: corrupt file or can't open file.

It would seem that I just don't get what needs to be done. It seems so simple...

I'm sure that I've left out crucial bits of information - I'm sorry, I just don't know what to provide. I'm happy to add anything that you need!

Thanks in advance,

Mike

0 Kudos
23 Replies
Peter_Priestley
Beginner
5,647 Views

Mike,

It sounds like you need to expose your code as a COM Server. Then it can be called from Fortran or a multitude of other clients.

From what I've heard on this Forum the COM Server wizard should be making a return in Intel Visual Fortran 10.0 which is due in the next month. It was a very useful feature in CVF 6.6, that has been sorely missed. The COM Server wizard lets you easily build COM Servers in Fortran.

Also see the book "Developing Statistical Software in Fortran 95" by Lemmon & Schafer ISBN 0-387-23817-4.(which advocates and provides techniques for wrapping Fortran code as COM Servers).

http://methodology.psu.edu/newbooks/fortranbook/index.html

Peter P.

0 Kudos
Jugoslav_Dujic
Valued Contributor II
5,647 Views
mbrengartner@pyrix.com:

Put DLLExport statements in the dllto expose the subroutines, and DLLIMPORT statements in the project.

I have a hunch that COM is a red herring here; as I get it, you want to call the Dll from a COM server written in another language, yes? If so, it shouldn't matter which language it is, as long as you correctly declare and call the dll routines.

For the start, download Dependency Walker and check if you're really exporting those routines properly (it's neat to have it associated with dll's by default). DLLIMPORT is not essential.

mbrengartner@pyrix.com:

I've used the /gen-interfaces switch to get a whole mess o' .mod and f90 files. I've linked to those individual mod files. Result: corrupt file or can't open file.

Exactly. /gen-interfaces does not do what you think (it cross-checks the validity of calls within Fortran code if explicit interfaces are not provided). In addition, .mod files are not intended for the linker, but for compiler: they're sort of equivalent to C++ .pch (precompiled headers).

mbrengartner@pyrix.com:

I'm sure that I've left out crucial bits of information - I'm sorry, I just don't know what to provide. I'm happy to add anything that you need!



Exact wording of the linker error would be helpful; you can also post the DLL's .lib file so that we can examine it. Are you sure you're linking with it?
0 Kudos
mbrengartner
Beginner
5,647 Views

Ok, this is great - thanks everybody! I'll try to address everyone's questions here. I think the COM part is working - I ran the COM export wizard, wich generated a module that I included in my dll. Now I can call out to that dll, and everything seems to work.

That function is declared as follows:

subroutine

eval(evalflag,itire,var1,Alpha,Gamma,SlipRatio,P, V,MuRoad,Fx,Fy, Fxc, Fyc, Mx, Mz, Fxbest, SRbest,Fybest,SAbest,KT,var2,Re)

!DEC$ ATTRIBUTES DLLEXPORT :: eval

!DEC$ ATTRIBUTES STDCALL, REFERENCE :: eval

!DEC$ ATTRIBUTES REFERENCE :: evalflag,itire, var1,Alpha,Gamma,SlipRatio,P,V, MuRoad, Fx, Fy, Fxc, Fyc, Mx, Mz, Fxbest,SRbest,Fybest,SAbest,KT,var2,Re

Even thoughthis is marked as STDCALL, can I still call this from Fortran? Or, what do I need to do to call it from Fortran? Here's the sample project I've been using to test:

program

dllTest

call test

contains

subroutine test

implicit

real(8) (a-z)

iTire = 1

RL = 13.5

!in

Alpha = 2

!deg

Gamma = -1

!deg

SlipRatio = 0.08

!unitless

P = 0

!psi

V = 150

!mph

MuRoad = 1

!unitless

FZ = -2000

!lb

dblFlag = 1

dblSlipAngle = 2.0

call eval (dblFlag, iTire, FZ, dblSlipAngle, Gamma, SlipRatio, P, V, MuRoad, Fx, Fy, Fxc, Fyc, Mx, Mz, Fxbest, SRbest, Fybest, SAbest, KT, RL, Re)

end subroutine

end

program

In VS2005, I've set Project Properties->Linker->Input->Additional Dependencies to the dll I'm trying to call (the one that has eval). I've copied the dll to the project path locally.

Thanks!

0 Kudos
Jugoslav_Dujic
Valued Contributor II
5,647 Views
Yes, STDCALL is the core of the problem: once you change the default calling convention (and miscellaneous other properties of the routine's "signature"), you must ensure that the Fortran caller knows how to properly call it (it's not true only for exe<->dll, but in general, i.e. even within the same project).

There are multiple ways to achieve such "explicit interface" but, long story short, you can write INTERFACE blocks for the dll routine and make sure they're visible in the calling routine(s) via declaration, INCLUDEing or USEing:
interface
 subroutine eval(evalflag,itire,var1,Alpha,Gamma,SlipRatio,P, &
V, MuRoad, Fx, Fy, Fxc, Fyc, &
Mx, Mz, Fxbest, SRbest,Fybest,SAbest,KT,var2,Re)

!DEC$ ATTRIBUTES STDCALL, REFERENCE :: eval

  end subroutine 
end interface

(Btw, if none of the arguments is a CHARACTER(*), your longish second REFERENCE declaration is redundant, as the one with STDCALL implies it for all arguments).

0 Kudos
mbrengartner
Beginner
5,647 Views

OK, so does the interface go in the dll? or the calling program? And I thought the INCLUDE or USE function was to import .mod files?

I'll remove the other reference attribute; you're right, they're all real(8) variables.

Thanks again,

Mike

0 Kudos
Jugoslav_Dujic
Valued Contributor II
5,647 Views
Interface goes to the calling program.

INCLUDE performs just the text substitution (its argument is a file name); USE makes the compiler search and process the .mod file (which is binary), and its argument is the module name (which is the same as the .mod file name without .mod extension).

Alternatively, if you do have the .mod file coming from the dll compilation (by means of /gen-interfaces), you can skip writing the interface and USE that mod file. It's not exactly the method I would use, as it depends on the compiler settings for the dll, but it should work. (I'd prefer the third method, explicitly putting MODULE...CONTAINS...END MODULE around dll routines, and as result a proper .mod file will be always generated)
0 Kudos
mbrengartner
Beginner
5,647 Views

OK, so now I get "error LNK2019: unresolved external symbol _FRTTIRE_EVAL referenced in function _DLLTEST._TEST"

I did a depends on the dll, and got these two entries: frttire_eval and _frttire_eval@88, both with the same entry point. Do I need an alias in my interface like this:

!DEC$ ATTRIBUTES ALIAS : _frttire_eval@88 :: frttire_eval

Thanks!

program dllTest

call test

contains

subroutine test

implicit

real(8) (a-z)

interface

subroutine frttire_eval(evalflag,itire,var1,Alpha,Gamma,SlipRatio,P, &

V, MuRoad, Fx, Fy, Fxc, Fyc, &

Mx, Mz, Fxbest, SRbest,Fybest,SAbest,KT,var2,Re)

!DEC$ ATTRIBUTES STDCALL, REFERENCE :: frttire_eval

end subroutine

end interface

iTire = 1

RL = 13.5

!in

Alpha = 2

!deg

Gamma = -1

!deg

SlipRatio = 0.08

!unitless

P = 0

!psi

V = 150

!mph

MuRoad = 1

!unitless

FZ = -2000

!lb

dblFlag = 1

dblSlipAngle = 2.0

call frttire_eval (dblFlag, iTire, FZ, dblSlipAngle, Gamma, SlipRatio, P, V, MuRoad, Fx, Fy, Fxc, Fyc, Mx, Mz, Fxbest, SRbest, Fybest, SAbest, KT, RL, Re)

end subroutine

end

program

0 Kudos
Jugoslav_Dujic
Valued Contributor II
5,647 Views
Huh. Which version of the compiler? I pasted your exact code above in a console application of mine (last VF version 9.1.037) and I got:

error LNK2019: unresolved external symbol _frttire_eval@88 referenced in function _DLLTEST._test

note that the name decoration is correct and matches your dll export (and I get the link error because I don't have the dll library). I vaguely recall someone mentioned a VF bug regarding STDCALL, REFERENCE, but sorry, I don't know which version is affected... searching... see here, but it was back in June 2006; the workaround allegedly was to spell out the REFERENCE atribute for individual arguments.

In addition, you have to add at least implicit real(8) again within the interface statement (after !DEC$ATTRIBUTES), in order to declare the arguments -- the one outside doesn't "leak" into interface block.
0 Kudos
mbrengartner
Beginner
5,647 Views

C:Program FilesIntelCompilerFortran9.1IA32Bin>ifort
Intel Fortran Compiler for 32-bit applications, Version 9.1 Build 20070109
Z Package ID: W_FC_C_9.1.034
Copyright (C) 1985-2007 Intel Corporation. All rights reserved.

I've added the implicit real(8) (a-z) and still get the linker error. Is my alias statement right? It won't work if I use it as typed above.

Thanks,

Mike

0 Kudos
Jugoslav_Dujic
Valued Contributor II
5,647 Views
You must enclose the alias in quotes (and preferrably put it within the interface):

!DEC$ATTRIBUTES ALIAS: "_whatever@88":: whatever

But it should work without the alias statement... I'm not sure what's going on.
0 Kudos
mbrengartner
Beginner
5,647 Views

OK, hmmm.. still no luck. So the only thing I need to set in the project properties is the "additional dependencies" set to the .lib file of the dll? Nothing else needs to be set?

Thanks,

Mike

0 Kudos
Jugoslav_Dujic
Valued Contributor II
5,647 Views
That still does not explain the odd linker error. Like I said, if it were a linker setup problem, you should get "unresolved external _frttire_eval@88", not "unresolved external _FRTTIRE_EVAL". That error message somehow indicates that the compiler did not recognize the interface or !DEC$ATTRIBUTES. Typo?

Could you zip and attach all relevant project and source files? Alternatively, you can ask for Intel Premier support.
0 Kudos
mbrengartner
Beginner
5,647 Views

OK, I got it! I had a small typo in the interface code that caused the differences (frttire_eval vs _frttire_eval@88) once that was fixed, everything fell into place quickly.

Ok, so now how do I get this interface out? Can I put it into a .f90 file or compile it into a .mod? Then would I just include that into the subroutine that needs to call it?

Thanks to everybody for their help, and especially big thanks to JugoslavDujic for staying with me on this ;)

Thanks,
Mike

0 Kudos
DavidWhite
Valued Contributor II
5,647 Views

The easiest way to do this is not to expose your main routine in the DLL.

Then write TWO wrapper routines for the two ways you want to call the routine, one with a Fortran export (no STDCALL) and the other with using STDCALL.

Otherwise, you risk corrupting the arguments, especially if strings are involved.

I have successfully done this with DLL's callable from Excel VBA and from other Fortran projects.

Regards,

David

0 Kudos
mbrengartner
Beginner
5,647 Views

Ok, so here's a weird one (to me anyways) - With a release build of the dll I'm trying to access, the debug version of my test program (see above) works fine, but the release build doesn't work! I put some write statements in to see the results and it's acting like I'm not passing in the correct values or that I'm not allowed to call that function (we have code that checks to see if you're authorized). Again, the debug version OF THE TEST PROGRAM works fine!

Any ideas on why that would be? I've checked, and there are no obvious compiler switch differences between the two builds...

Any help would be appreciated!

Thanks,
Mike

0 Kudos
mbrengartner
Beginner
5,647 Views

Ok, here's a little tidbit I just ran across - if I set my Config Properties->Fortran->General->Debug Information Format to anything other than "None", it works as expected. If it's set to "None", I get the funky output.

Here's the Command Line text:

/nologo /Zi /module:"$(INTDIR)/" /object:"$(INTDIR)/" /libs:static /threads /c

Thanks,

Mike

0 Kudos
Jugoslav_Dujic
Valued Contributor II
5,647 Views
Sounds as if you have uninitialized variables and/or array out of bounds errors. I suggest that you add /check:all compiler switch (Configuration/Fortran/Run time/Run time error checking) and try it in debug configuration. I betcha something will come up.
0 Kudos
mbrengartner
Beginner
5,647 Views

Changing that switch seemed to actually help: it works when the switch is there at all.If I set it to "Custom" and then seteverything to "no" I get the error. Setting any one to "Yes" or "All" I got no warnings.

It seems to be limited to the Charachter(256) variable - if I pull that out of the call and interface, replace it with a real(8) dummy variable and change the dll to hard code the string, everything works great.

I can even add a variable 'filelength' to the end of the frttire_init call in the dll, NOT specify it in the interface, and it works... but I can't then access the filelength variable (access violation) I think a buffer overrun isthe likely cause.

Any ideas?

Thanks,
Mike

I also changed the parameter order to put the string variable last...

0 Kudos
DavidWhite
Valued Contributor II
5,647 Views
Mike,

If you are passing Character arguments, it is best not to have variable length arguments, and if you are using the STDCALL protocol, pad the argument in the calling routine to its full length.

This seems to be because in addition to the string itself, the length of the string is passed as a hidden argument. If your string is not the correct length in the caller, then you may not be able to pass back the 256 characters that you want.

If you don't get this right, you will find that some of the arguments point to the wrong memory location and/or are overwritten, resulting in strange values, and unrepeatable execution.

Hope this helps,

Regards,

David White
0 Kudos
mbrengartner
Beginner
5,544 Views

Thanks for that tip - but I thought that by declaring the variable asa Character(256) that it pads the rest for you? In debug mode, if I hover over the variable, it looks like it is already padded. But perhaps it's not. Is there an easy way to pad it, or do you have to just hit the space bar 200 times? :)

Thanks,

Mike

0 Kudos
Reply