A Visual C++ Callback Function
We will now extend the Visual C++ 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, the bound on the objective, and also include a button to allow the user to interrupt the solver if they desire. You can find the complete project for this sample in the directory \LINGO17\Programming Samples\VC++\STAFF2.
The first step is to design the dialog box that we will post whenever a new integer solution is found. Here is a look at the dialog box for this example:
The box has three edit fields for the iterations, objective, and bound. There are two buttons—one to interrupt the solver and the other to clear the dialog box.
The next step is to use the ClassWizard to attach a handler class to the new dialog box. This class was named CNewIPDlg. When a class has been attached to the dialog box, then the ClassWizard must be used to assign member variables to handle the Iterations, Objective, and Bound edit fields. Once this is done, the header file for the dialog box will resemble:
// NewIPDlg.h : header file
//
#include "resource.h"
////////////////////////////////////////////////////////////////
// CNewIPDlg dialog
class CNewIPDlg : public CDialog
{
// Construction
public:
CNewIPDlg(CWnd* pParent = NULL); //standard constructor
// Dialog Data
//{{AFX_DATA(CNewIPDlg)
enum { IDD = IDD_NEW_IP_SOLUTION };
CString m_csBound;
CString m_csIteration;
CString m_csObjective;
//}}AFX_DATA
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CNewIPDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); //}}AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CNewIPDlg)
// NOTE: the ClassWizard will add member functions here
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
Callback Dialog Header File (NewIPDlg.h)
Here is the code to handle events from the new dialog box:
// NewIPDlg.h : header file
//
/////////////////////////////////////////////////////////////////
// CNewIPDlg dialog
class CNewIPDlg : public CDialog
{
// Construction
public:
CNewIPDlg(CWnd* pParent = NULL); // standard constructor |
// Dialog Data |
//{{AFX_DATA(CNewIPDlg) |
enum { IDD = IDD_NEW_IP_SOLUTION }; |
CString m_csBound; |
CString m_csIteration; |
CString m_csObjective; |
//}}AFX_DATA |
// Overrides |
// ClassWizard generated virtual function overrides |
//{{AFX_VIRTUAL(CNewIPDlg) protected: |
virtual void DoDataExchange(CDataExchange* pDX); |
//}}AFX_VIRTUAL |
// Implementation
protected:
// Generated message map functions |
//{{AFX_MSG(CNewIPDlg) |
// NOTE: the ClassWizard will add member functions here |
//}}AFX_MSG |
DECLARE_MESSAGE_MAP() |
};
Callback Dialog Handler (NewIPDlg.cpp)
The code in these two files was entirely generated by the ClassWizard, requiring no actual user input.
Next, the code for the callback function must be added. Here is a copy of the global routine that was added for this purpose:
int __stdcall MyCallback( void* pModel, int nReserved, void* pUserData)
// Callback function called by the LINGO solver
//
// return value: >= 0 if solver is to continue, else < 0 to interrupt
// Get current best IP
int nErr;
double dBestIP;
nErr = LSgetCallbackInfoLng( pModel, LS_DINFO_MIP_BEST_OBJECTIVE_LNG, &dBestIP);
if ( nErr) return( 0);
// Get best IP value published in dialog box
double* pdBestIPShown = (double*) pUserData;
// Is current better than incumbent?
if ( dBestIP < *pdBestIPShown)
// Yes ... save its value
*pdBestIPShown = dBestIP;
// Get iteration count from LINGO
int nIterations;
LSgetCallbackInfoLng( pModel, LS_IINFO_ITERATIONS_LNG,
&nIterations);
// Get bound on solution
double dBound;
LSgetCallbackInfoLng( pModel, LS_DINFO_MIP_BOUND_LNG, &dBound);
// Create a dialog to show the current solution value
CNewIPDlg dlgNewIP;
// Initialize the fields of the dialog
dlgNewIP.m_csIteration.Format( "%d", nIterations);
dlgNewIP.m_csObjective.Format( "%d", (int) dBestIP);
dlgNewIP.m_csBound.Format( "%d", (int) dBound);
// Post the dialog and check for a user interrupt
if ( dlgNewIP.DoModal() == IDCANCEL) return( -1);
}
return( 0);
}
Callback Routine
Of course, this routine has the same interface that was described in the previous section:
int __stdcall MySolverCallback( pLSenvLINGO pL, int nReserved, void* pUserData)
The following code uses the LSgetCallbackInfo routine to get the value of the current best integer solution:
// Get current best IP
int nErr;
double dBestIP;
nErr = LSgetCallbackInfoLng( pModel,
LS_DINFO_MIP_BEST_OBJECTIVE_LNG, &dBestIP);
if ( nErr) return( 0);
The constant, LS_DINFO_MIP_BEST_OBJECTIVE, is defined in the LINGD17.H header file.
We then get the value for the best integer solution that we have currently posted in the dialog box with the statement:
// Get best IP value published in dialog box
double* pdBestIPShown = (double*)pUserData;
Note that this statement references the user data pointer, pUserData. This pointer is passed to LINGO when the callback function is established, and it is useful as a means of accessing the data from within the callback function. In this particular case, a single variable is being pointing to. If needed, the pointer could reference a large data structure containing whatever data desired.
Next, test to see if the latest integer solution is better than the incumbent solution with the statement:
// Is current better than incumbent?
if ( dBestIP < *pdBestIPShown)
If the new solution is better, then we get additional information from LINGO on the iteration count and solution bound. Also, an instance of the callback dialog box to display the latest information in is created with the line:
// Create a dialog to show the current solution value
CNewIPDlg dlgNewIP;
The new data is then loaded into the fields of the dialog box:
// Initialize the fields of the dialog
dlgNewIP.m_csIteration.Format( "%d", nIterations);
dlgNewIP.m_csObjective.Format( "%d", (int) dBestIP);
dlgNewIP.m_csBound.Format( "%d", (int) dBound);
As the final step in this callback routine, we display the dialog box, and if the user hits the interrupt button we return a –1, indicating that the solver should stop and return with the best answer found so far:
// Post the dialog and check for a user interrupt
if ( dlgNewIP.DoModal() == IDCANCEL) return( -1);
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. A good place to do this is right after creating the LINGO environment in the OnSolve() handler code for our Solve button. The changes are listed below in bold type:
// create the LINGO environment object
pLSenvLINGO pLINGO;
pLINGO = LScreateEnvLng();
if ( !pLINGO)
{
AfxMessageBox("Unable to create LINGO Environment");
return;
}
// Pass LINGO a pointer to our callback function
nError = LSsetCallbackSolverLng( pLINGO, &MyCallback,
&dBestIPShown);
if ( nError) goto ErrorExit;
// Open LINGO's log file
nError = LSopenLogFileLng( pLINGO, "LINGO.log");
if ( nError) goto ErrorExit;