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:

page329xp

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;