Handling arguments by reference
So far all the method calls you have seen had only input arguments, i.e. arguments that you pass to the ActiveX object your program is controlling. As you would expect, some objects expose methods with arguments that are also used as output. Example 6.4 has one of these methods (GetInputData). The method can receive three optional arguments (InitValue, EndValue and StepIncrement that return directly the values that were entered by the user, saving you from having to call PropertyGet later to retrieve these values.
Arguments by reference (those on which the ActiveX object will return values) must be passed as Variants by reference (i.e. Variants with a VT_BYREF mask on their VT-Type). From Chapter 3 you may remember that these Variants do not contain the real value, but a pointer to the value. Here is the new version of The Ultimate Logarithms Program, which uses the return values from GetInputData instead of querying object UltimateLogsIO for its properties.
Example 6.4B
program UltimateLogsB
use f90VBDefs
use f90VBBStrings
use f90VBSafeArrays
use f90VBVariants
use f90VBAutomation
implicit none
type(VARIANT)::UltLogsIO,TmpVar,TmpLogTable
real(8),pointer::MapArray(:,:)
integer(HRESULT_KIND)::iRet, i
type(EXCEPINFO)::EInfo
character(len=255)::TmpStr
integer(SAFEARRAY_KIND)::LogTable
real(8)::InitValue, FinalValue, IncStep
type(VARIANT)::VarInitValue, VarFinalValue, VarIncStep
real(8)::Value
iRet = OleInitialize()
!create an instance of UltimateLogsIO object
UltLogsIO = CreateOleObject('UltimateLogsIOServer.UltimateLogsIO',iRet)
if (iRet.ne.S_OK) goto 1000
VarInitValue%VarVal%vt=VT_R8+VT_BYREF
VarInitValue%VarVal%byRef = loc(InitValue)
VarFinalValue%VarVal%vt=VT_R8+VT_BYREF
VarFinalValue%VarVal%byRef = loc(FinalValue)
VarIncStep%VarVal%vt=VT_R8+VT_BYREF
VarIncStep%VarVal%byRef = loc(IncStep)
!Show input screen and get range for the table
TmpVar = ExecMethod(UltLogsIO,'GetInputData', VarInitValue, &
VarFinalValue, VarIncStep, iRet=iRet,EInfo=EInfo)
if (iRet.ne.S_OK) goto 1000
!Compute size of array and allocate an appropriate Safe Array
call SafeArrayCreate(LogTable,VT_R8, &
1,int((FinalValue - InitValue) / IncStep + 1), &
1,2)
!Map Fortran array to Safe Array for fast access
call SafeArrayAccessData(LogTable,MapArray)
!compute the table of logs
Value = InitValue
do i=1,ubound(MapArray,1)
MapArray(i,1)=Value !VariantCreate(VT_R8,Value)
MapArray(i,2) =dlog(Value) !VariantCreate(VT_R8,dlog(Value))
Value = Value+IncStep
enddo
!we don't need the mapped array anymore, so unmap it
call SafeArrayUnAccessData(LogTable)
!hand-build a variant containing LogTable + VT_BYREF
TmpLogTable%varVal%vt = VT_BYREF+VT_R8+VT_ARRAY
TmpLogTable%varVal%byref = loc(LogTable)
!show the logarithm table
TmpVar = ExecMethod(UltLogsIO,'ShowLogTable',TmpLogTable,iRet=iRet,EInfo=EInfo)
if (iRet.ne.S_OK) goto 1000
goto 2000
1000 continue
!Print error messages
print *, 'The following Errors were detected:'
print *, 'HRESULT:',iRet
call StrCopy(EInfo%bstrDescription,TmpStr)
print *,TmpStr
2000 continue
!release all variants, safe arrays, etc.
call Release(UltLogsIO)
call ExceptionClear(EInfo)
call VariantClear(TmpVar)
call SafeArrayDestroy(LogTable)
call VariantClear(TmpLogTable)
call OleUnInitialize()
stop
end
As you might have noticed most of the code looks the same, but there are four changes:
First, we have added three more Variant definitions:
type(VARIANT)::VarInitValue, VarFinalValue, VarIncStep
These are the Variants that will be passed as arguments to method GetInputData. Second, notice that we create the Variants manually, rather than using function VariantCreate. f90VB’s Variants Library does not create Variants by reference:
VarInitValue%VarVal%vt=VT_R8+VT_BYREF
VarInitValue%VarVal%byRef = loc(InitValue)
VarFinalValue%VarVal%vt=VT_R8+VT_BYREF
VarFinalValue%VarVal%byRef = loc(FinalValue)
VarIncStep%VarVal%vt=VT_R8+VT_BYREF
VarIncStep%VarVal%byRef = loc(IncStep)
Note that what is stored in the Variants are addresses, rather than real values.
Third, you see that now we call GetInputData passing these variants as arguments:
TmpVar = ExecMethod(UltLogsIO,'GetInputData', VarInitValue, &
VarFinalValue, VarIncStep, iRet=iRet,EInfo=EInfo)
Forth, and final, note that we removed a section from UltimateLogsA in this version. In the original version we had:
InitValue = VariantToDouble(PropertyGet(UltLogsIO,'InitValue'))
FinalValue = VariantToDouble(PropertyGet(UltLogsIO,'EndValue'))
IncStep = VariantToDouble(PropertyGet(UltLogsIO,'StepIncrement'))
But these statements are gone now. The reason for this is that the Variants now contain the addresses of InitValue, FinalValue and IncStep, so when the Variants are updated, so are these variables. Note that independently of whether an argument for a method is of input or output type, you can always pass the argument by reference. The ActiveX object is responsible for de-referencing Variants that contain arguments by reference. If you are willing to create your Variants manually (f90VB’s VariantCreate function cannot create Variants with the VT_BYREF flag), you can save considerable processing time and memory by taking advantage of this behavior.
Now, before you go mad passing all your arguments by reference, there is one thing you should keep in mind; ActiveX objects are not allowed to release any dynamic variables that have been allocated outside the object. If you are wondering why this is a problem, take a few seconds to study the lines of code presented below:
BStrHndl = BStrAlloc('10')
VarInitValue%VarVal%vt=VT_BSTR+VT_BYREF
VarInitValue%VarVal%byRef = loc(BStrHndl)
TmpVar = ExecMethod(UltLogsIO,'GetInputData', VarInitValue, &
VarFinalValue, VarIncStep, iRet=iRet,EInfo=EInfo)
Note that instead of passing a double precision argument in VarInitValue, we are passing a BString by reference. The ActiveX object will take and convert the variant into the appropriate type, and return you another BString variant back. But you have lost the handle to the original BString, and with it the capability of returning to the operating system the memory used by the original BString. In other words, your program just leaked some memory. As a rule of thumb, whenever you call a method that receives Variants containing BStrings or Safe Arrays by reference (i.e. methods that may return a BString or Safe Array), make sure you have a backup handle for the BString or Safe Array that the variant refers to.