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

Calling PB from CVF 6.6

ngc2023_isp
Beginner
669 Views
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:


! 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
0 Kudos
11 Replies
Steven_L_Intel1
Employee
669 Views
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.
0 Kudos
ngc2023_isp
Beginner
669 Views
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.

Message Edited by NGC2023_ISP on 04-12-200604:33 PM

0 Kudos
Steven_L_Intel1
Employee
669 Views
Ok, my mistake. That doesn't make a difference. But how does the routine you're calling know what to expect?
0 Kudos
ngc2023_isp
Beginner
669 Views
Well, just by changing the procedure declaration:



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?
0 Kudos
ngc2023_isp
Beginner
669 Views
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)
0 Kudos
Steven_L_Intel1
Employee
669 Views
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.
0 Kudos
ngc2023_isp
Beginner
669 Views
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
0 Kudos
Jugoslav_Dujic
Valued Contributor II
669 Views
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.
0 Kudos
Steven_L_Intel1
Employee
669 Views
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.
0 Kudos
ngc2023_isp
Beginner
669 Views
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
0 Kudos
Jugoslav_Dujic
Valued Contributor II
669 Views
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).
0 Kudos
Reply