- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
This looks like a great forum and there are lots of examples of fortran calling dll's in various languages. I am working with dll's created by PowerBasic, and I don't see any related discussions (if you are not familiar with it, PB is fully compiled and is not an intrepreted Basic such as VB).
Before working with the actual dll package I have to work with, I am starting by just building test subs and testing how things work with increasing complexity. By doing this I have found something I don't understand when passing string parameters. If there is any parameter in the list that follows a string parameter I get a GPF. I thought maybe this is the famous "hidden length parameter" but based on my options I think there should be no length parameter. Anyway, this is what I'm doing:
Does anyone know why the second type of call (integer following string) doesn't work? It may help if I say that I have tested many other types of combinations and ANY parameter following a string will not work, including multiple strings.
Thanks for any help you can give!
Kate
Before working with the actual dll package I have to work with, I am starting by just building test subs and testing how things work with increasing complexity. By doing this I have found something I don't understand when passing string parameters. If there is any parameter in the list that follows a string parameter I get a GPF. I thought maybe this is the famous "hidden length parameter" but based on my options I think there should be no length parameter. Anyway, this is what I'm doing:
! kernel32 required for loadlibrary, freelibrary, getprocaddress
use kernel32
! Variable declarations
implicit none
character(8) s
integer n
pointer (q6,ptr_Msg6)
pointer (q6b,ptr_Msg6b)
! Declare an interface block to the sixth routine
! with one integer and one string parameter
interface
subroutine Msg6(i, s)
!DEC$ ATTRIBUTES STDCALL:: Msg6
!DEC$ ATTRIBUTES REFERENCE:: i
!DEC$ ATTRIBUTES REFERENCE:: s
integer i
character*8 s
end subroutine
end interface
! Declare an interface block to the sixth routine
! with one string array
interface
subroutine Msg6b(s, i)
!DEC$ ATTRIBUTES STDCALL:: Msg6b
!DEC$ ATTRIBUTES REFERENCE:: s
!DEC$ ATTRIBUTES REFERENCE:: i
character*8 s
integer i
end subroutine
end interface
print *, 'Begining'
! Locate the dll and load it into memory
p = loadlibrary("pb_dll.dll"C)
if (p == 0) then
type *, "Error occurred opening pb_dll.dll"
type *, "Program aborting"
goto 1000
endif
! Set up a pointer to the sixth routine
q6 = getprocaddress(p, "Msg6"C)
if (q6 == 0) then
type *, "Error occurred finding Msg6 in pb_dll.dll"
type *, "Program aborting"
goto 1000
endif
! Set up a pointer to the sixth routine
q6b = getprocaddress(p, "Msg6b"C)
if (q6 == 0) then
type *, "Error occurred finding Msg6b in pb_dll.dll"
type *, "Program aborting"
goto 1000
endif
! Call the sixth routine
n = 3
s = "test6"
! THIS WORKS
call ptr_Msg6(n, s)
! THIS DOESNT WORK
call ptr_Msg6b(s, n)
print *, 'Exiting'
1000 continue
!
! Free up the library
status = freelibrary(p)
Does anyone know why the second type of call (integer following string) doesn't work? It may help if I say that I have tested many other types of combinations and ANY parameter following a string will not work, including multiple strings.
Thanks for any help you can give!
Kate
Link Copied
11 Replies
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Welcome to the forum, Kate!
You've wandered into a rather messy corner of the implementation - messy largely because it was so in Microsoft Fortran PowerStation which we wanted to be compatible with.
What I think you need to do is add the REFERENCE attribute to the routine name as well as the argument. The way you have it now, the string length will be passed. Try that and see how it works for you.
You've wandered into a rather messy corner of the implementation - messy largely because it was so in Microsoft Fortran PowerStation which we wanted to be compatible with.
What I think you need to do is add the REFERENCE attribute to the routine name as well as the argument. The way you have it now, the string length will be passed. Try that and see how it works for you.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thanks for the reply! I think I understand what you meant, and
I changed the interface as follows:
interface
subroutine Msg6b(s, i)
!DEC$ ATTRIBUTES REFERENCE:: Msg6b
!DEC$ ATTRIBUTES REFERENCE:: s
!DEC$ ATTRIBUTES REFERENCE:: i
character*8 s
integer i
end subroutine
end interface
but still the same problem occurs.
I changed the interface as follows:
interface
subroutine Msg6b(s, i)
!DEC$ ATTRIBUTES REFERENCE:: Msg6b
!DEC$ ATTRIBUTES REFERENCE:: s
!DEC$ ATTRIBUTES REFERENCE:: i
character*8 s
integer i
end subroutine
end interface
but still the same problem occurs.
Message Edited by NGC2023_ISP on 04-12-200604:33 PM
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Ok, my mistake. That doesn't make a difference. But how does the routine you're calling know what to expect?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Well, just by changing the procedure declaration:
also, might be helpful to provide this:
SDECL This is the default if neither BDECL nor CDECL are pecified. SDECL (and its synonym STDCALL) specifies that the declared procedure uses the "Standard Calling Convention" as defined by Microsoft. When calling an SDECL procedure, parameters are passed on the stack right to left.
PowerBASIC Subs and Functions that use the SDECL/STDCALL convention automatically clean up the stack before execution returns to the calling code. In the event the called procedure is imported or exported, PowerBASIC will automatically capitalize the function name unless an explicit ALIAS clause is specified.
As well as:
Normally, PowerBASIC passes parameters to a procedure either by reference or by copy. Either way, the address of a variable is passed, and the procedure has to look at that address to get the value of the parameter. If you do not need to modify the parameter (true in many cases), you can speed up your procedures by passing the parameter by value using the BYVAL keyword with your parameter name.
Does that help?
SUB Msg6 ALIAS "Msg6" (I AS LONG, s AS STRING*8) EXPORT
SUB Msg6b ALIAS "Msg6b" (s AS STRING*8, I AS INTEGER) EXPORT
also, might be helpful to provide this:
SDECL This is the default if neither BDECL nor CDECL are pecified. SDECL (and its synonym STDCALL) specifies that the declared procedure uses the "Standard Calling Convention" as defined by Microsoft. When calling an SDECL procedure, parameters are passed on the stack right to left.
PowerBASIC Subs and Functions that use the SDECL/STDCALL convention automatically clean up the stack before execution returns to the calling code. In the event the called procedure is imported or exported, PowerBASIC will automatically capitalize the function name unless an explicit ALIAS clause is specified.
As well as:
Normally, PowerBASIC passes parameters to a procedure either by reference or by copy. Either way, the address of a variable is passed, and the procedure has to look at that address to get the value of the parameter. If you do not need to modify the parameter (true in many cases), you can speed up your procedures by passing the parameter by value using the BYVAL keyword with your parameter name.
Does that help?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Another bit of strangeness, if I try to do the same thing with functions instead of procedures, CVF complains about ptr_Fn1: "Error: This name does not have a type, . . . ", even though it is defined
! kernel32 required for loadlibrary, freelibrary, getprocaddress
use kernel32
! Variable declarations
implicit none
character(8) s
integer j
integer n
integer p
logical status
pointer (f1,ptr_Fn1)
! Declare an interface block to the first function
interface
integer function Fn1(i, s)
!DEC$ ATTRIBUTES STDCALL:: Fn1
!DEC$ ATTRIBUTES REFERENCE:: i
!DEC$ ATTRIBUTES REFERENCE:: s
integer i
character*8 s
end function
end interface
print *, 'Begining'
! Locate the dll and load it into memory
p = loadlibrary("pb_dll.dll"C)
if (p == 0) then
type *, "Error occurred opening pb_dll.dll"
type *, "Program aborting"
goto 1000
endif
! Set up a pointer to the first function
f1 = getprocaddress(p, "Fn1"C)
if (f1 == 0) then
type *, "Error occurred finding Fn1 in pb_dll.dll"
type *, "Program aborting"
goto 1000
endif
n = 3
s = "test"
j = ptr_Fn1(n, s)
print *, j
print *, 'Exiting'
1000 continue
!
! Free up the library
status = freelibrary(p)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
You haven't given ptr_Fn1 a type. I suspect that you want Fn1 here instead.
I still don't see from the code you posted how the PB routine is supposed to know in which order the arguments are passed.
I still don't see from the code you posted how the PB routine is supposed to know in which order the arguments are passed.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Steve,
Thanks so much for your continued patience with these questions. Changing all references from ptr_Fn1 to Fn1 did work (although by analogy I am not sure why that wasn't necessary for procedures in the above example, and now I think I need to go back to that and make sure I understood what was really being done with the statements such as pointer (q6,ptr_Msg6) . . .)
On the PB side, I guess I don't really understand your question.
The subroutine in the PB Dll is defined as some variation of :
SUB Msg6 ALIAS "Msg6" (I AS LONG, s AS STRING*8) EXPORT
coupled with knowing that parameters are passed on the stack right to left, would seem to answer your question, unless I am misunderstanding what you are asking.
Also, if I use this same calling procedures and pass several numeric variables and check the order they are received in the dll, they are passed in the correct order. So, I think the parameter order is consistent on both sides.
Although that makes me wonder, if the parameters are passed right to left, and if there is a hidden length parameter (although from the CVF documentation I don't think there should be), where would it be in the order?
- Kate
Thanks so much for your continued patience with these questions. Changing all references from ptr_Fn1 to Fn1 did work (although by analogy I am not sure why that wasn't necessary for procedures in the above example, and now I think I need to go back to that and make sure I understood what was really being done with the statements such as pointer (q6,ptr_Msg6) . . .)
On the PB side, I guess I don't really understand your question.
The subroutine in the PB Dll is defined as some variation of :
SUB Msg6 ALIAS "Msg6" (I AS LONG, s AS STRING*8) EXPORT
coupled with knowing that parameters are passed on the stack right to left, would seem to answer your question, unless I am misunderstanding what you are asking.
Also, if I use this same calling procedures and pass several numeric variables and check the order they are received in the dll, they are passed in the correct order. So, I think the parameter order is consistent on both sides.
Although that makes me wonder, if the parameters are passed right to left, and if there is a hidden length parameter (although from the CVF documentation I don't think there should be), where would it be in the order?
- Kate
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Right-to-left is a red herring; all modern calling conventions have right-to-left. So, no problem there, as SDECL seems to be the same as expected STDCALL.
Judging on your interfaces, you don't pass the hidden length. (As a test, you can write a dummy routine of the same prototype as the interface in a separate Fortran source file, then examine the .obj file using dumpbin /symbols foo.obj. You should get _FOO@8 for an integer + string-by-reference, not _FOO@12. Your interface seems (close to) OK to me.
I'd focus on investigating how PowerBasic passes strings, though. VB has somewhat odd convention that ByVal for strings means "pass address" and ByRef means "pass pointer to address". If PB does the same, you should either declare the string ByVal in PB side, or add the POINTER attribute to both Fortran interface and the string variable you pass.
As an experiment, you can try to make a dummy Fortran dll called from test VB program, and try to exchange strings that way; take a look at LOC(stringargument),x in the Fortran debugger and see what's in there in "Memory" window.
Judging on your interfaces, you don't pass the hidden length. (As a test, you can write a dummy routine of the same prototype as the interface in a separate Fortran source file, then examine the .obj file using dumpbin /symbols foo.obj. You should get _FOO@8 for an integer + string-by-reference, not _FOO@12. Your interface seems (close to) OK to me.
I'd focus on investigating how PowerBasic passes strings, though. VB has somewhat odd convention that ByVal for strings means "pass address" and ByRef means "pass pointer to address". If PB does the same, you should either declare the string ByVal in PB side, or add the POINTER attribute to both Fortran interface and the string variable you pass.
As an experiment, you can try to make a dummy Fortran dll called from test VB program, and try to exchange strings that way; take a look at LOC(stringargument),x in the Fortran debugger and see what's in there in "Memory" window.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
For a CALL, there is no datatype required for the routine name. Since you use IMPLICIT NONE, a function reference requires the function name to be declared, which the interface does.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Steve,
Got it! That makes sense.
Jugoslav,
In PB, for parameters passed by reference it is the address of a variable that is passed. I'll try your diagnostics, although I suppose I do have to make a CVF dll (that works with the same interface) to test those ideas.
The other thing I will do today is start testing passing arrays, and maybe that will provide some additional clues as to how to make everyone play nice.
Thanks so much!
- Kate
Got it! That makes sense.
Jugoslav,
In PB, for parameters passed by reference it is the address of a variable that is passed. I'll try your diagnostics, although I suppose I do have to make a CVF dll (that works with the same interface) to test those ideas.
The other thing I will do today is start testing passing arrays, and maybe that will provide some additional clues as to how to make everyone play nice.
Thanks so much!
- Kate
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
In PB, for parameters passed by reference it is the address of a variable that is passed.
I know, consider strings separately – it doesn't make sense at all to pass strings by value, especially as they can be of unknown length. VB choose to adopt notation ByVal = char*, ByRef = char**. I was just wondering if that's the case with PowerBasic.
Googling:
this explains a lot. It says that if you want a Fortran-compatible PB string, you should use ASCIIZ type, not STRING (and terminate the string on Fortran side with char(0)). If you have the control over VB dll interface, that's the simplest thing to do. If you don't, you have to check what STRING is on low-level; probably a TYPE(VARIANT), and construct that on Fortran side (not too difficult but bothersome).
I know, consider strings separately – it doesn't make sense at all to pass strings by value, especially as they can be of unknown length. VB choose to adopt notation ByVal = char*, ByRef = char**. I was just wondering if that's the case with PowerBasic.
Googling:
this explains a lot. It says that if you want a Fortran-compatible PB string, you should use ASCIIZ type, not STRING (and terminate the string on Fortran side with char(0)). If you have the control over VB dll interface, that's the simplest thing to do. If you don't, you have to check what STRING is on low-level; probably a TYPE(VARIANT), and construct that on Fortran side (not too difficult but bothersome).
![](/skins/images/872293744008A34B36F8ABF94A46CC66/responsive_peak/images/icon_anonymous_message.png)
Reply
Topic Options
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page