Call VB code from CVF - Store pointer to VB subroutine and calling it

Call VB code from CVF - Store pointer to VB subroutine and calling it

The Callback example which comes with CVF shows how to call a VB subroutine from Fortran. However, I need to store the pointer to the VB subroutine and call it from a different module. I understand that I need to use

INTEGER(INT_PTR_KIND()) :: vbcallback

but am not clear on the details. I am new to F9x and am confused by the new features such as 'Interface'. Please could someone post the example modified code show me how? I would really appreciate some help on this.

8 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

Take a look at this recent thread.

James

The thread James mentioned originated from this one. You might find some interesting links there as well.

Jugoslav

Jugoslav
www.xeffort.com

Thanks for the link. I have applied what I learnt from this (all Fortran example) and tried to apply it to the callback example supplied with CVF. The code compiles and runs okay until it tries to call the VB routine cbproc3 at which point I get 'an unhandled exception'. The code and I am using is based on the original example but I am attempting to store pointer references to the VB subroutines:

subroutine dll_rout(int_arg,str_in,cbproc1in,cbproc2in,cbproc3in)
!dec$ attributes dllexport ::dll_rout
!dec$ attributes alias :'dll_rout'::dll_rout
use dfcom
use DataGlobals

! Define interface blocks for each of the callback routines

INTEGER(INT_PTR_KIND()) :: cbproc1in
INTEGER(INT_PTR_KIND()) :: cbproc2in
INTEGER(INT_PTR_KIND()) :: cbproc3in

! Define the parameters passed to this routine
integer int_arg
character *(*) str_in

character *25 str_out
integer temp
character *5 int_str

! The following paradigm is used for interfacing to the ConvertStringToBSTR
! routine. The string created is Unicode, which is 16 bit. CHARACTER is
! 8 bit, and so an array of INTEGER*2 are used instead of an array of
! CHARACTERs.
pointer (p1, my_str_in)
pointer (p2, my_str_out)
integer*2 my_str_in(25)
integer*2 my_str_out(25)

fptr1 = cbproc1in
fptr2 = cbproc2in
fptr3 = cbproc3in

! Multiply the value passed by 10, and give it to cbproc3
temp = int_arg * 10
call cbproc3(temp)

! Convert the integer to string, and append it to the string passed,
! and then give it back to cbproc2 as a BSTR
write(int_str,'(i5.5)')int_arg
str_out = str_in // int_str
p2 = ConvertStringToBSTR(str_out)
call cbproc2(p2)

! Create our message, and pass both it and the original integer to cbproc1
p1 = ConvertStringToBSTR("Fortran says Hello!")
call cbproc1 (p1, int_arg)

! Clean up after ourselves
call SysFreeString(p1)
call SysFreeString(p2)
return
end

AND in a separate module:

MODULE DataGlobals
PUBLIC
interface
subroutine cbproc1(c1, i1)

pointer (c1, foo1)
character *(200) foo1
integer*4 i1

end
subroutine cbproc2(c1)

pointer (c1, foo1)
character *(20) foo1

end
subroutine cbproc3(i1)

integer*4 i1

end
end interface

pointer (fptr1, cbproc1)
pointer (fptr2, cbproc2)
pointer (fptr3, cbproc3)

end

What I am doing wrong? Thanks

The code looks mostly OK; I'll supply few comments & guesses, but at the end you'll have to debug it.

- I'm not sure if one unnamed INTERFACE block for three routines and three separate INTERFACEs mean the same thing; I've always used separate INTERFACE blocks for each routines.

- It's strange that the compiler let you declare cbproc1 and cbproc2 in the way you did:
subroutine cbproc2(c1)
pointer (c1, foo1)
character *(20) foo1

Cray POINTER attribute is not applicable to dummy arguments. I'd suggest instead:
subroutine cbproc2(c1)
integer(int_ptr_kind()) c1

- You should test whether the values of cbproc1in, ... are non-zero. If it still crashes, try printing them out to a file -- these should be big numbers of similar values.

- Finally, a guess: I think the real culprit is str_in argument. In fortran, string arguments are passed as two actual arguments -- string address and length. Thus, dll_rout reads the arguments from the stack as in left column, but what is actually supplied is as in right column:

&int_arg        &int_arg
&str_in         &str_in
str_in_length   &cbproc1in
&cbproc1in      &cbproc2in
&cbproc2in      &cbproc3in
&cbproc3in      [garbage]

To avoid expecting str_in_length, you should declare it as:

dec$attributes dllexport ::dll_rout
!dec$attributes dllexport, stdcall::dll_rout
!dec$attributes reference:: str_in
!dec$attributes reference:: int_arg

- Finally, when doing mixed-language programming, you should learn about the difference between passing arguments by value and by reference, and how various !dec$attributes affect the ways arguments are passed. I wouldn't elaborate here at the moment since I feel I'd be more confusing than helpful.

Jugoslav

Jugoslav
www.xeffort.com

Thanks for your reply. I've tried all your suggestions but still without any luck and so have tried to really simplify the problem down to the bare essentials. But I still can't seem to solve it. The code and I am using now is as follows:

In one module
-------------
MODULE DataGlobals
PUBLIC
interface
subroutine cbproc1(i1)
integer*4 i1
end
end interface
pointer (fptr1, cbproc1)
end

In another module
-----------------
subroutine dll_rout(cbproc1in)
!dec$ attributes dllexport ::dll_rout
!dec$ attributes alias :'dll_rout'::dll_rout
use dfcom
use DataGlobals
INTEGER(INT_PTR_KIND()) :: cbproc1in
integer*4 i
fptr1 = cbproc1in
i=20
call cbproc1 (i)
return
end

VB form Code
------------
Private Declare Sub dll_rout Lib "debug/callback" (ByVal cb1 As Long)
Private Sub cmdGofor_Click()
Call dll_rout(AddressOf ghproc1)
End Sub

VB module code
--------------
Public Sub ghproc1(p2 As Long)
MsgBox p2
End Sub

This should get round any problems with passing strings between Fortran and Visual Basic. The programme still crashes at the point when it tries to call the Visual Basic subroutine. So I'm still at a loss as to how one should store pointers to Visual Basic subroutines in Fortran. Please help.

Andy

Andy,

I don't use VB but it appears you defined the parameter to dll_rout to be passed by value. What you want is the address of the callback routine to be passed by reference.

James

Yes, I think so too. Andy, if you are confused, please skip the rest of this message :-). Note that you don't have to use Cray pointers, especially if the callback is to be called from the main DLL routine. You can treat the argument as an external procedure. You can study the following two samples in spare time; I sincerely hope they are correct (otherwise, I'll do more harm than benefit).

Sample 1 -- using a Cray pointer:

####################################
Declare Sub FSUB(ByRef lpCallback As Long) Lib "DebugCallback"
Dim lpCallback As Long
lpCallback = AddressOf(Callback)
Call FSUB(lpCallback)
!=8<===========================
Public Sub Callback(p1 As Long)
MsgBox p1
End Sub
!=8<===========================
SUBROUTINE FSUB(lpCallback)
!DEC$ATTRIBUTES DLLEXPORT, ALIAS: "fsub":: FSUB
INTEGER(INT_PTR_KIND):: lpCallback
INTERFACE 
   SUBROUTINE Callback(p1)
   INTEGER:: p1
   END SUBROUTINE
END INTERFACE
POINTER(lpfnCallback, Callback)
lpfnCallback = lpCallback
CALL Callback(1)
END SUBROUTINE SUB

Sample 2 -- Passing callback as external

####################################
Declare Sub FSUB(ByVal lpCallback As Long) Lib "DebugCallback"
Dim lpCallback As Long
lpCallback = AddressOf(Callback)
Call FSUB(lpCallback)
!=8<===========================
Public Sub Callback(p1 As Long)
MsgBox p1
End Sub
!=8<===========================
SUBROUTINE FSUB(Callback)
!DEC$ATTRIBUTES DLLEXPORT, ALIAS: "fsub":: FSUB
INTERFACE 
   SUBROUTINE Callback(p1)
   INTEGER:: p1
   END SUBROUTINE
END INTERFACE
CALL Callback(1)
END SUBROUTINE SUB
####################################

Note ByRef in the first sample but ByVal in the second. In the first sample, you're passing an intermediate Long variable which holds the address of Callback. Since ByRef is default in both VB and Fortran, that will work. However, in the second sample, Fortran expects an address of the routine to be found on the stack -- let's say that Callback starts on address 12345678. That must be matched with ByVal on VB side, so that 12345678 will be located on the stack. If there were ByRef in sample 2, VB would pass address of lpCallback variable (say 12348888) to the stack. Fortran would call the routine on address 12348888, which does not contain code but data... kaboom.

Hope it sheds some light,
Jugoslav

Jugoslav
www.xeffort.com

Leave a Comment

Please sign in to add a comment. Not a member? Join today