Building the Application in C++
In this section, we will walk you through the steps involved in building the project to solve the staff scheduling model. If you would rather skip the details involved in constructing this project and would prefer to experiment with the completed application, it can be found under the path \Lingo15\Programming Samples\VC++\STAFF1\STAFF.EXE.
We will be working from a code base generated using Visual C++'s AppWizard facility. The generated code is for a dialog based application. In other words, the interface to the application will consist of a single dialog box. This dialog box will be modified, so it resembles the one illustrated below with fields for staffing requirements, daily employee starts, and so on.
The source code for this project may be found in the Programming Samples\STAFF1 subdirectory or you can create your own project using Visual C/C++ 6.0 as follows:
1. | Start Visual C++ and issue the File|New command. |
2. | Select Projects tab, give the project a name, select MFC AppWizard(Exe) as the type of application, and then click the OK button. |
3. | Click the dialog based radio button and then the Next button. |
4. | Clear the About box button, check the OLE Automation button, check the OLE Controls button, and press the Next button. OLE support is required for some of the features in the LINGO DLL. |
5. | Click the Next button again. |
6. | Click the Finish button. |
After completing these steps, the following summary of your project should be shown:
Click the OK button, and AppWizard will generate the skeleton code base.
Use the resource editor to modify the application's dialog box, so it resembles the following:
Next, you will need to use the ClassWizard in Visual C++ to associate member variables with each of the fields in the dialog box. From the View menu, select the ClassWizard command and then select the Member Variables tab.
At this point, the LINGO DLL import library must be added to the project in order to make the LINGO DLL available to the code. Do this by running the Project|Add to Project|Files command and select the file \LINGO15\Programming Samples\LINGD15.LIB for addition to the project.
After doing that, add the definitions of the LINGO DLL routines to the project. Simply include the Lingd15.h header file at the top of the dialog class header file as follows (code changes listed in bold):
// staffDlg.h : header file
//
#include "lingd15.h"
#if
!defined(AFX_STAFFDLG_H__74D746B7_CA4D_11D6_AC89_00010240D2AE__INCLUDED_)
#define
AFX_STAFFDLG_H__74D746B7_CA4D_11D6_AC89_00010240D2AE__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
/////////////////////////////////////////////////////////////////////////////
// CStaffDlg dialog
.
.
.
All the groundwork has now been laid down and we’re ready to begin writing the actual code to call LINGO to solve the staffing model. Go to the Resource View of the project, open the dialog box resource, and then double click on the Solve button. You will be prompted to create a handler function for the button, which should be named OnSolve. Now, edit the stub version of OnSolve, so it contains the following code:
void CStaffDlg::OnSolve()
{
int nError, nPointersNow;
CString csScript, cs;
double dNeeds[7], dStart[7], dOnDuty[7], dStatus, dTotal;
// Get user's staffing requirements from our dialog box
UpdateData();
// Load staffing requirements into the LINGO transfer array.
// LINGO uses double precision for all values.
dNeeds[ 0] = (double) m_nNeedsMon;
dNeeds[ 1] = (double) m_nNeedsTue;
dNeeds[ 2] = (double) m_nNeedsWed;
dNeeds[ 3] = (double) m_nNeedsThu;
dNeeds[ 4] = (double) m_nNeedsFri;
dNeeds[ 5] = (double) m_nNeedsSat;
dNeeds[ 6] = (double) m_nNeedsSun;
// create the LINGO environment object
pLSenvLINGO pLINGO;
pLINGO = LScreateEnvLng();
if ( !pLINGO)
{
AfxMessageBox("Unable to create LINGO Environment");
return;
}
// Open LINGO's log file
nError = LSopenLogFileLng( pLINGO, "LINGO.log");
if ( nError) goto ErrorExit;
// Pass memory transfer pointers to LINGO
// @POINTER(1)
nError = LSsetPointerLng( pLINGO, dNeeds, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(2)
nError = LSsetPointerLng( pLINGO, dStart, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(3)
nError = LSsetPointerLng( pLINGO, dOnDuty, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(4)
nError = LSsetPointerLng( pLINGO, &dTotal, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(5)
nError = LSsetPointerLng( pLINGO, &dStatus, &nPointersNow);
if ( nError) goto ErrorExit;
// Here is the script we want LINGO to run
csScript = "SET ECHOIN 1\n";
csScript = csScript +
"TAKE \\LINGO15\\SAMPLES\\STAFFPTR.LNG\n";
csScript = csScript +
"GO\n";
csScript = csScript +
"QUIT\n";
// Run the script
dStatus = -1.e0;
nError = LSexecuteScriptLng( pLINGO, (LPCTSTR) csScript);
// Close the log file
LScloseLogFileLng( pLINGO);
// Any problems?
if ( nError || dStatus)
// Had a problem
AfxMessageBox("Unable to solve!");
} else
// Everything went ok ... load results into the dialog box
m_csStartMon.Format( "%d", (int) dStart[0]);
m_csStartTue.Format( "%d", (int) dStart[1]);
m_csStartWed.Format( "%d", (int) dStart[2]);
m_csStartThu.Format( "%d", (int) dStart[3]);
m_csStartFri.Format( "%d", (int) dStart[4]);
m_csStartSat.Format( "%d", (int) dStart[5]);
m_csStartSun.Format( "%d", (int) dStart[6]);
m_csOnDutyMon.Format( "%d", (int) dOnDuty[0]);
m_csOnDutyTue.Format( "%d", (int) dOnDuty[1]);
m_csOnDutyWed.Format( "%d", (int) dOnDuty[2]);
m_csOnDutyThu.Format( "%d", (int) dOnDuty[3]);
m_csOnDutyFri.Format( "%d", (int) dOnDuty[4]);
m_csOnDutySat.Format( "%d", (int) dOnDuty[5]);
m_csOnDutySun.Format( "%d", (int) dOnDuty[6]);
m_csCost.Format( "%g", dTotal);
UpdateData( FALSE);
}
goto Exit;
ErrorExit:
cs.Format( "LINGO Errorcode: %d", nError);
AfxMessageBox( cs);
return;
Exit:
LSdeleteEnvLng( pLINGO);
}
The first section of OnSolve is straightforward and deals with extracting the user’s staffing requirements from the dialog box. Note that the data is stored in a double precision array rather than as integers. This is because these values will be passed to LINGO, which only passes values in double precision format.
Our first call to LINGO creates the LINGO environment object with the following code:
// create the LINGO environment object
pLSenvLINGO pLINGO;
pLINGO = LScreateEnvLng();
if ( !pLINGO)
{
AfxMessageBox("Unable to create LINGO Environment");
return;
}
The pLSenvLINGO data type is defined in the LINGO header file, lingd15.h.
Then, a log file for LINGO is established with the following call:
// Open LINGO's log file
nError = LSopenLogFileLng( pLINGO, "LINGO.log");
if ( nError) goto ErrorExit;
As mentioned above, opening a log file for LINGO is good practice, at least when you’re debugging the application. If something should go wrong, the log file will generally contain a helpful clue.
Our next step is to pass LINGO the physical addresses it will use to resolve the @POINTER() function references used in the data section of the model (refer to the The Model section for more details). This is done as follows:
// Pass memory transfer pointers to LINGO
// @POINTER(1)
nError = LSsetPointerLng( pLINGO, dNeeds, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(2)
nError = LSsetPointerLng( pLINGO, dStart, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(3)
nError = LSsetPointerLng( pLINGO, dOnDuty, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(4)
nError = LSsetPointerLng( pLINGO, &dTotal, &nPointersNow);
if ( nError) goto ErrorExit;
// @POINTER(5)
nError = LSsetPointerLng( pLINGO, &dStatus, &nPointersNow);
if ( nError) goto ErrorExit;
In summary, when LINGO is called to solve the model, the staffing needs are passed to LINGO in the dNeeds array via the @POINTER( 1) reference. Solution information is passed from LINGO back to the application in the dStart, dOnDuty, dTotal, and dStatus structures via @POINTER() references 2 through 5, respectively. If any of this is unfamiliar, review The @POINTER() Function section above.
Next, the following code is used to build the command script:
// Here is the script we want LINGO to run
csScript = "SET ECHOIN 1\n";
csScript = csScript +
"TAKE \\LINGO15\\SAMPLES\\STAFFPTR.LNG\n";
csScript = csScript +
"GO\n";
csScript = csScript +
"QUIT\n";
The script consists of four commands, which are each terminated with a new line character (ASCII 10). The end of the script is terminated with a NULL character (ASCII 0). These commands and their functions are:
Command |
Function |
SET ECHOIN 1 |
Causes LINGO to echo input to the log file. This is a useful feature while building and debugging an application. |
TAKE |
Loads the model from a disk file. The TAKE command may be used to load model files, as in this example. It may also be used to run nested command scripts contained in files. |
GO |
Calls the solver to optimize the model. |
QUIT |
Closes down LINGO’s script processor and returns control to the calling application. |
At this point, the script is ready to be passed off to LINGO for processing with the following call:
// Run the script
dStatus = -1.e0;
nError = LSexecuteScriptLng( pLINGO, (LPCTSTR) csScript);
Note that dStatus is initialized to –1. LINGO returns the model status through memory transfer location number 5 (i.e., @POINTER( 5)) to the dStatus variable. LINGO will only return a status value if it was able to solve the model. If an unexpected error were to occur, LINGO might not ever reach the solution phase. In that case, dStatus would never be set. Initializing dStatus to a negative value tests for this situation. Given that LINGO returns only non-negative status codes, a negative status code would indicate a problem. This method of error trapping is effective, but not particularly elegant. Another method that involves specifying an error callback routine is demonstrated below.
Now, LINGO’s log file may be closed down by calling LScloseLogFileLng():
// Close the log file
LScloseLogFileLng( pLINGO);
Next, the following code tests to see if LINGO was able to find an optimal solution:
// Any problems?
if ( nError || dStatus)
// Had a problem
AfxMessageBox("Unable to solve!");
} else
Note that the code returned in nError pertains only to the mechanical execution of the script processor. It has nothing to do with the status of the model’s solution, which is returned in dStatus via the use of the @POINTER() and @STATUS() functions (see the The Model section). A model may actually be infeasible or unbounded, and the error code returned by LSexecuteScript() will give no indication. Thus, it is important to add a mechanism to return a solution’s status to the calling application, as done here with the @STATUS() -> @POINTER(5) -> dStatus link. The end result in the sample code is that "Unable to solve" is printed if either error condition occurs. A more user-friendly application would offer more specific information regarding the error condition.
As a final step, in order to avoid memory leaks in your application, remember to free up LINGO’s environment when through:
Exit:
LSdeleteEnvLng( pLINGO);
If everything has been entered correctly, you should now be able to build and run the project.