A Visual Basic Callback Function
We will now extend the Visual Basic staffing example by introducing a callback function. The callback function in this example will post a small dialog box each time the solver finds a better integer solution. The dialog box will display the solver’s iteration count, the objective value for the new solution, and the bound on the objective. You can find the complete project for this sample in the directory \LINGO17\Programming Samples\VBasic\STAFF2.
First, we need to construct a callback function and place it in a separate Module file (.bas file). Here are the contents of our callback function file Module1.bas:
Public Function MySolverCallback(ByVal pModel As Long, _
ByVal nReserved As Long, ByRef dBestIP As Double) As Long
' Callback function called by the LINGO DLL
' during model solution.
'
' Return value: >= 0 if solver is to continue,
' else < 0 to interrupt the solver
Dim nReturnVal As Long
nReturnVal = 0
' Get current best IP
Dim dObj As Double
Dim nError As Long
nError = LSgetCallbackInfoDoubleLng(pModel, _
LS_DINFO_MIP_BEST_OBJECTIVE_LNG, dObj)
' Check for any error
If (nError = LSERR_NO_ERROR_LNG) Then
' Is it better than the best one displayed so far?
If (dObj < dBestIP) Then
' Yes ... display this solution
' Save the new best objective value
dBestIP = dObj
' Get the iteration count from LINGO
Dim nIterations As Long
nResult = LSgetCallbackInfoLongLng(pModel, _
LS_IINFO_ITERATIONS_LNG, nIterations)
' Get the objective bound from LINGO
Dim dBound As Double
nResult = LSgetCallbackInfoDoubleLng(pModel, _
LS_DINFO_MIP_BOUND_LNG, dBound)
' Display the information in a dialog box
Dim nButtonPressed
Dim cMessage As String
cMessage = "Objective:" + Str(dBestIP) _
+ Chr(10) + "Bound:" + Str(dBound) _
+ Chr(10) + "Iterations:" + Str(nIterations)
nButtonPressed = MsgBox(cMessage, vbOKCancel)
If (nButtonPressed = vbCancel) Then
nReturnVal = -1
End If
End If
End If
MySolverCallback = nReturnVal
End Function
VB Callback Module (Module1.bas)
Note: | A VB callback function must be placed in a Module file (.bas file) . The callback function will not work if it is placed in a Forms file (.frm file). Also, the callback function and the module file must have different names. If the module file has the same name as the callback function, then the VB AddressOf operator will not be able to return the address of the callback function. |
You will recall from the section Specifying a Callback Function that the callback routine must use the following calling sequence:
int __stdcall MySolverCallback( pLSenvLINGO pL, int nReserved, void* pUserData)
An equivalent function definition using VB code is:
Public Function MySolverCallback(ByVal pModel As Long, _
ByVal nReserved As Long, ByRef dBestIP As Double) As Long
VB uses the standard call (__stdcall) convention by default, so we need not specify this explicitly.
We will make use of the user data pointer to pass the value of the best objective displayed so far in the dBestIP argument. This variable will hold the objective value of the best integer solution found so far. We compare each new objective value to the best one found so far. If the latest is an improvement over the incumbent, then we display a dialog box summarizing the new solution.
The following code uses the LSgetCallbackInfo routine to get the value of the current best integer solution:
' Get current best IP
Dim dObj As Double
Dim nError As Long
nError = LSgetCallbackInfoDoubleLng(pModel, _
LS_DINFO_MIP_BEST_OBJECTIVE_LNG, dObj)
In the VB header file for LINGO (LINGD17.BAS), we created two aliases for the LSgetCallbackInfoLng() function: LSgetCallbackInfoDoubleLng() and LSgetCallbackInfoLongLng(). These were for retrieving, respectively, double and long data from LINGO. This is required due to VB not supporting the void data type found in C. We use LSgetCallbackInfoDoubleLng() to retrieve the objective value given that it is a double precision quantity.
Next, we check for any errors in retrieving the objective value. If none occurred, we check to see if the latest objective is better than the incumbent:
' Check for any error
If (nError = LSERR_NO_ERROR_LNG) Then
' Is it better than the best one displayed so far?
If (dObj < dBestIP) Then
If the new objective is better than the incumbent, then we save the new objective value, and retrieve the iteration count and objective bound:
' Save the new best objective value
dBestIP = dObj
' Get the iteration count from LINGO
Dim nIterations As Long
nResult = LSgetCallbackInfoLongLng(pModel, _
LS_IINFO_ITERATIONS_LNG, nIterations)
' Get the objective bound from LINGO
Dim dBound As Double
nResult = LSgetCallbackInfoDoubleLng(pModel, _
LS_DINFO_MIP_BOUND_LNG, dBound)
We post a summary of the new solution in a dialog box:
' Display the information in a dialog box
Dim nButtonPressed
Dim cMessage As String
cMessage = "Objective:" + Str(dBestIP) _
+ Chr(10) + "Bound:" + Str(dBound) _
+ Chr(10) + "Iterations:" + Str(nIterations)
nButtonPressed = MsgBox(cMessage, vbOKCancel)
If the user pressed the Cancel button, as opposed to the OK button, then we set the return value to –1 before returning, which will cause the LINGO solver to interrupt:
If (nButtonPressed = vbCancel) Then
nReturnVal = -1
End If
End If
End If
MySolverCallback = nReturnVal
End Function
At this point, there is one more piece of code to add. We must make a call to the LssetCallbackSolverLng() routine to pass LINGO a pointer to our callback routine. This is accomplished at the start of the Solve button handler routine with the call:
' Pass LINGO a pointer to the callback routine
Dim nError As Long
Dim dBestObj As Double
dBestObj = 1E+30
nError = LSsetCallbackSolverLng(pLINGO, _
AddressOf MySolverCallback, dBestObj)
Note the use of the VB AddressOf operator in the call to LSsetCallbackSolverLng(). This operator may be used only in function calls to pass the address of routines contained in module files.
Note: | The AddressOf operator was added to VB starting with release 5.0. Thus, earlier releases of VB won’t be able to exploit the callback feature in LINGO. Also, Visual Basic for Applications (VBA), the VB macro capability supplied with Microsoft Office, does not support the AddressOf operator. Thus, VBA applications calling LINGO will also not be able to establish callback routines. |