Programming Sail Using Visual C++

Introduction

For the graphical user interface (GUI) to the SAIL project, Visual C++ was chosen to be the programming platform. While a few of the SAIL control modules had been programmed using the Microsoft Foundation Class (MFC), many others were written using the C language or within another integrated programming environment. The variety of code used caused some problems when I attempted to put together a sensible, user friendly interface. Using Visual C++ and the MFC to do the majority of the interface work greatly eased my job. I did, however, have to write code using Win32 API calls in the cases where MFC did not directly provide enough flexibility to incorporate the variety of code already written.

The opening screen of the SAIL user interface looks very similar to the prototype which Laura Blackwood had begun developing the previous year, although it's functionality is entirely different. If all of the researchers in the lab would have used Laura's basic prototype for modeling their individual modules, the entire SAIL package would have been fairly easy to assemble. As it was, much of the code in use has been built on code and functions developed in previous years, and none of it was designed in such a manner to be consistent with Laura's vision. Given the wide variety of software which needed to function together as one virtual package, I approached the problem from a slightly different angle.

Having never before worked with an integrated programming environment such as Visual C++, there was quite a steep learning curve for me when I began my tasks at the MSU PRIP Lab. Luckily, there were many fine resources as well as online help and tutorials available in the lab to help me become quickly familiar with the basic functionality of the Visual C++ environment and the MFC. One book in particular, Charles Petzold's, Programming Windows95, proved to be very helpful when I needed to go beyond MFC and use API calls directly. According to the author, "[his] book shows the hard way to do Windows programming--but it is also the most basic fundamental, and powerful way. Moreover, by learning classical Windows programming using C and the raw APIs, you can more clearly understand how Windows and your application interact...."

Most of the books talk about a 6 month period for really learning and understanding Windows programming, and from my experience, this may well be true. In the 3 months which I had to accomplish my project, was barely enough time to get it done. The programming experience I gained with Windows, however, will be very beneficial to me forever, and I feel very fortunate to have had this chance to develop my programming skills.

The Project

The project I was involved with was to create a sensible manual user interface for the SAIL robot. In order to tie in the variety of programs without having to rewrite each one individually, I developed an interface which calls up the other modules rather than directly incorporating them into the interface. My interface displays each module on its own tab and appears to work as a single unit to the user, even though in effect many separate programs may be running simultaneously. There are two advantages to this approach. The first is, when SAIL is upgraded to a multiprocessor machine, the modules will be better able to take full advantage of the processing power since in effect the program runs as a multi-threaded process since each module is its own process rather than part of my program (some of the individual modules are multi-threaded themselves when deemed necessary). The second advantage of this approach is that it is very easy to add additional functionality to the robot by adding additional modules, which are really just frameless dialog boxes along with any code needed to make them functional.

To date, there are seven modules -- two head modules which control the robots vision, one a manual control and the other an automatic convergence control; two speech modules, one which provides for text reading and the other which builds words and sentences phonetically; one Reach module which controls the robots arm; one Listen module which controls the robots 'ears'; and one Move module which controls the mobile base unit.

Additional modules can be added as needed. The bulk of this paper will show how to incorporate another module into the project and in doing so will give a little insight into how the SAIL GUI is currently put together.

The SAIL GUI

The SAIL GUI program is aptly named Sail.exe. When this executable is run a number of thing happen. First, the user interface framework is drawn on the screen with the client area being filled with a tab control. This part of the interface was implemented using the MFC classes and was fairly easy to code since Visual C++ and MFC did the majority of the work for me. The second thing that happens is a blank dialog box is called up by the framework initialization routine which covers the entire client area except for the tabs of the tab control. The reason this blank dialog box is implemented, is so that when the user opens another tab by choosing a module from the menu, the previous modules dialog box gets blanked over while the new module is being loaded into memory. This gives the user an instant visual indication that the program is indeed responding to their mouse action. Finally, the framework initialization routine creates a tab page on the tab control labeled "Main", and then calls the program Main.exe which draws a dialog window over top of the SAIL interface window so that it appears to be part of the Main tab though it is actually another process running with its window brought to the top of the Z-order. When the initial splash screen (which is nice although really only for decoration) disappears, either after a time-out period or when the user clicks the left mouse button on the SAIL interface window, the resulting screen looks like this:

As you can see, the result looks entirely like a normally implemented tab control in Windows with only one tab being available, although in reality at this time there are already four processes running -- the Main tab dialog window over top of the blank dialog window over top of the SAIL framework window in addition to a dynamically linked library which provides for error and status messages to be displayed in the main window. This may at first seem unnecessary since the main tab is always available and cannot be removed from the tab control. However, because of the implementation of this program, this is a very necessary step. If this entire framework as seen above were one window, then every time the user would choose an item from the main menu, the entire window would come to the top of the Z-order and the main tab would become the visible one regardless of what was previously the currently selected tab. Obviously, the main tab being brought to the front every time the mouse choose an item from the menu would be completely unacceptable behavior. Therefore, even the Main tab is implemented as a separate process.

Although I won't go into detail here about every item on each tab (see the Robot Manual Mode for detailed information), there is one thing I should mention here. The main window on the main tab shown above is a text window implemented through a direct link library (dll) named Message.dll. This is a shared dll and any of the modules can write text to this window simply by calling the Message function in their module (of course they have to include the message library in the linking of their code). This message window can be saved to a file or printed out and used for diagnostic purposes when the robot is running in automatic mode. That is why the main tab is always loaded with the Sail executable program so that the message window is always available when the other modules wish to send error or status messages to this common destination.

One of the reasons that this interface works so well is that it is of a fixed unmovable size. The size was fixed at 640 x 480 so that it would display properly on the LCD monitor which is used with the SAIL robot. In order for it to display properly on larger monitors, the window interface places itself in the upper left corner of the monitor where it remains as long as it is running. This way all the modules windows properly overlay one another as they are accessed.

The Main Function Calls

The heart of the Sail program are a number of functions which perform the following tasks:

The functions which perform these tasks are listed below:

Starting A Process

The application specific functions are callback functions and are used in conjunction with the Windows API EnumThreadWindows function. The Windows API CreateProcess function is called when a user starts using one of the program modules by choosing an item from the menu bar. The CreateProcess function does a number of things. First, if the function is successful, a Boolean value of TRUE is returned. I use this value to be sure that the requested module was found and could be loaded into memory. Secondly, the CreateProcess function takes as an argument a PROCESS_INFORMATION structure which is updated when the function returns. From this structure, I can retrieve information about the process created. The two values I store and use store are a handle to the process (hProcess) and the thread identification (threadID) of the process.

If the CreateProcess function is successful, I then create a new tab in the tab control with an appropriate title to identify the process associated with that tab. For example, in the graphic above, the main window is associated with the "Main" tab. When the user clicks the "Main" tab, the main process window will come to the front of the Z-order regardless of which other processes are currently running.

EnumThreadWindows

This brings us to the EnumThreadWindows function which is provided as part of the Win32 API. This function takes three arguments. The first argument is the threadID which was retrieved from the PROCESS_INFORMATION structure when the CreateProcess function was called. The second argument is the name of the callback function which the EnumThreadWindows function is going to use and the last argument can be additional information that you need to pass to the callback function specified in the second argument. The EnumThreadWindows function then begins to enumerate or step through all of the windows which are associated with the processId which was passed to it as the first argument. For each window of the process, EnumThreadWindows makes a call to the specified callback function with the handle of the window and the additional information which was supplied to it as a third argument. The callback function then does whatever I programmed it to due and returns control to the EnumThreadWindows function which gets the handle of the next window of the process and again invokes the callback function which was specified. This continues until there are no more windows owned by the threadID specified or until a value of FALSE is returned from the callback function.

The Callback Functions

The three callback functions used in the SAIL project provide a means for altering the Z-order of the windows displayed on screen as well as a way for terminating the various modules of the robot when no longer needed or when the program shuts down. As specified above, these three functions are WindowToTop, WindowToBottom, and WindowTerminate. The use of each function should be rather obvious from the names I choose, but we will view them in some detail here to see how they work within the SAIL framework. The WindowToTop function is specified like this:

BOOL CALLBACK CSAILView::WindowToTop(HWND hWnd, LPARAM lParam)
{
if(g_pCurrentWnd = CWnd::FromHandle(hWnd))
g_pCurrentWnd->SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
return FALSE;
}

When EnumThreadWindows first invokes this callback function it sends the handle of the main window of the process to the function. I do not need the additional information which could be specified in the lParam argument and so make no use of it in this function. What I do use is the handle of the window. From this, I get a pointer to the window using the CWnd::FromHandle function which is then stored in the global variable g_pCurrentWnd (g for global, p for pointer and CurrentWnd to indicate this is a pointer to a window). Using this pointer, I can then set the position of the window on screen. In the SetWindowPos function, we send the address of the top most window and our window is then placed above this in the Z-order. The return value of FALSE means that I do not want the EnumThreadWindows function to call us back again since I have already performed the only step that is required by the SAIL program. Notice the SWP_NOMOVE and SWP_NOSIZE parameters which SetWindowPos takes as the last argument. These insure that the window is not moved or resized with this call. It is only brought to the top of the Z-order.

The next callback function that I make use of is this:

BOOL CALLBACK CSAILView::WindowToBottom(HWND hWnd, LPARAM lParam)
{
if(g_pCurrentWnd = CWnd::FromHandle(hWnd))
g_pCurrentWnd->SetWindowPos(&wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
return FALSE;
}

It provides basically the same functionality as the previous function only it moves a window to the bottom of the Z-order rather that to the top.

The final callback function used is this:

BOOL CALLBACK CSAILView::WindowTerminate(HWND hWnd, LPARAM lParam)
{
if(g_pCurrentWnd = CWnd::FromHandle(hWnd))
g_pCurrentWnd->SendMessage(WM_CLOSE);
return TRUE;
}

This function again gets a pointer to the main window of the process used in the EnumThreadWindows call which invoked this function. I use the WM_CLOSE message to terminate the process specified rather than using a brute force method so that the process can ask the user to save information before closing if it needs to do this.

Creating a New Module for use in SAIL

Now lets take a step by step look at adding a new module to the SAIL project. In doing so, we can accomplish two things. First, we'll see the steps needed to add another module and secondly, we can gain some insight into the functionality of the program.

Lets pretend that we have a new module called Smell to add to the manual user interface for the SAIL project. Furthermore, assume that the name of the program is smell.exe and that it is a frameless dialog window of no greater than 625 x 390 pixels with its origin at (7,45). The size restriction is because of the screen size of the LCD display used with the SAIL robot. It is easy to create a dialog based interface using Visual C++ by choosing the dialog-based option when stepping through the AppWizard creation process. To make the dialog frameless, change the styles in the dialog properties to 'none' for border and uncheck the titlebar box which will remove the titlebar form the dialog. Also the following lines must be commented out in the SmellDlg.cpp file OnInitDialog() function.

/*
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
*/

This is because we aren't using a system menu in our dialog since the titlebar is not present and trying to add one will cause an error in the program.

If you would rather create a multiple-tabbed dialog box using the CPropertyPage class, the procedure is a little more involved. after creating the property pages and the property sheet, you must change a little code to get the frameless look that we need To make the property sheet frameless, remove (or comment out) the following code which was generated by the AppWizard in Visual C++ and is in the CSmellDialog class InitInstance function:

/*
CSmellDialog dlg("Smell Control", NULL, 1);
m_pMainWnd = dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
*/

Add this code instead:

CSmellDialog* dlg = new CSmellDialog();
dlg->Create(NULL, WS_POPUP , 0);
dlg->SetWindowPos(NULL, 5, 68, 0, 0, SWP_NOSIZE);
dlg->ShowWindow(SW_SHOW);
m_pMainWnd = dlg;
return TRUE;

Of course in place of CSmellDialog you would use whatever class the AppWizard created for you. If left to App Wizard, this would be C[NAME_OF_PROGRAM]Dialog. In other words, in place of Smell there would be whatever you named the program when you first create it with the AppWizard. For a closer look at a frameless property sheet example, examine the code for the Move module which incorporates the changes as shown above.

In either case, remember to keep the size of the dialog box smaller than 625 x 390 pixels with its origin at (7,45) and you will be all set to use this 'Smell' module in the SAIL project.

Here's an example of a simple smell window ready for use in the SAIL project:

Implementing the new Module in SAIL

Now that we have a module to use, we must modify the SAIL program to allow the user to use the new module. This requires several modifications, but all are within the CSailView class program and header files so it is fairly easy to accomplish. First we must add some new variables to the CSailView header file and initialize them in the CSailView constructor. the variables we need are as follows:

The m_nSmellTab variable stores the position of the smell tab in the tab list when the Smell module is activated by the user from the menu and is initialized to Zero. It is used to determine when the smell tab is clicked-on so that the Smell dialog box can brought to the front of the Z-order. The m_bSmellCtrlExists variable keeps track of whether or not the Smell module is in use so that the menu can properly reflect the status of the module (menu item is checked when in use). It also is used to be sure that only one instance of the smell module can be activated. It is initially set to FALSE. The m_hSmellHandle is a handle to the Smell process which is useful in terminating the process when necessary and it is initialized to NULL. The m_dwSmellThrdID is the threadID of the Smell process to be used in conjunction with the EnumThreadProcess function and it is also initially set to NULL.

We also must add a menu item for the smell control and this is easy to do using the the resource editor which is part of the Visual C++ package.

Now that the necessary variables are created and initialized, and we have a menu item for the Smell module, we need to add functions to be called when the user chooses the Smell module from the menu and also to update the status of the menu. These functions we will name OnSmellControl and OnUpdateSmellControl. Since these functions must be called in response to user input, they must be included in the message map for the CSailView class. Therefore we need to add the following lines to the CSailView header file in the message map section:

afx_msg void OnSmellControl();
afx_msg void OnUpdateSmellControl(CCmdUI* pCmdUI);

In addition, these lines must be added to the CSailView.cpp file in the message map section:

ON_COMMAND(ID_SMELL_CONTROL, OnSmellControl)
ON_UPDATE_COMMAND_UI(ID_SMELL_CONTROL, OnUpdateSmellControl)

This gives Windows the instruction on which functions to call and in which case to call them. When the user chooses the Control item from the Smell menu, Windows adds a call to OnSmellControl to the message cue and that function will be called in our CSailView class.

The easiest way to create these two functions, is to copy existing functions and then to modify them for the new module. The OnReachControl function looks as follows and is easily modified to become an OnSmellControl function.

void CSAILView::OnReachControl()
{
if(m_bReachCtrlExists) {
// Remove the Reach tab, set the focus to another tab, decrement the number of tabs,
// reset the m_bReachCtrlExists to FALSE and renumber the tabs as needed.
// NOTE: There needs to an if statement included for every possible tab.
if(m_hReachHandle) {
EnumThreadWindows(m_dwReachThrdID, (WNDENUMPROC)WindowTerminate,
(LPARAM)(LPTSTR)szWindowName );
m_dwReachThrdID = 0;
m_hReachHandle = NULL;
}
m_nCurTab = m_pTabCtrl->GetCurSel();
m_pTabCtrl->DeleteItem(m_nReachTab);
m_bReachCtrlExists = FALSE;
m_nNumTabs--;
if(m_nListenTab > m_nReachTab)
m_nListenTab--;
if(m_nSpeakTab > m_nReachTab)
m_nSpeakTab--;
if(m_nSpeakLpcTab > m_nReachTab)
m_nSpeakLpcTab--;
if(m_nMoveTab > m_nReachTab)
m_nMoveTab--;
if(m_nHeadTab > m_nReachTab)
m_nHeadTab--;
if(m_nAutoHeadTab > m_nReachTab)
m_nAutoHeadTab--;
if(m_nCurTab == m_nReachTab) { // m_nReachTab was the active tab
m_pTabCtrl->SetCurSel(m_nCurTab - 1);
TabChanged(m_nCurTab - 1);
}
} else {
// Create a Reach tab, set the tab number and make the new tab have the focus
// Also change the m_bReachCtrlExists to TRUE to control the menu.
ZeroMemory(&m_StartupInfo, sizeof(m_StartupInfo));
ZeroMemory(&m_ProcessInfo, sizeof(m_ProcessInfo));
m_StartupInfo.cb = sizeof(m_StartupInfo);
m_StartupInfo.dwFlags = STARTF_FORCEONFEEDBACK;
if(::CreateProcess(".\\ReachDlg\\Debug\\MainCtrl.exe",NULL,
NULL, NULL, NULL, 0, NULL, NULL,
&m_StartupInfo, &m_ProcessInfo)) {
m_dwReachThrdID = m_ProcessInfo.dwThreadId;
m_hReachHandle = m_ProcessInfo.hProcess;
m_TabCtrlItem.mask = TCIF_TEXT;
m_TabCtrlItem.pszText = "Reach";
m_pTabCtrl->InsertItem(m_nNumTabs, &m_TabCtrlItem);
m_pTabCtrl->SetCurSel(m_nNumTabs);
m_nReachTab = m_nNumTabs;
m_nNumTabs++;
m_bReachCtrlExists = TRUE;
EnumThreadWindows(m_dwBlankThrdID, (WNDENUMPROC)WindowToTop,(LPARAM)(LPTSTR)szWindowName );
Sleep(1500);
EnumThreadWindows(m_dwReachThrdID, (WNDENUMPROC)WindowToTop,(LPARAM)(LPTSTR)szWindowName );
}
}
}

Before we modify the code, however, lets take a good look at it as this is where the greatest functionality of the SAIL interface is implemented.

Deleting a Tab in SAIL

The first statement in the OnReachControl function is a check to see is the control already exists. If it does, then we know that the user has chosen to terminate use of this control and we go ahead and do this. The next statement checks to be sure that the threadID pointer is valid (non-NULL), and if so, the call to EnumThreadWindows proceeds and the Reach module is then terminated. The remainder of the statements in the first half of the function are used to update the Reach variables, remove the Reach tab, and, to rearrange and reassign the remaining tabs in order to maintain a valid tab count and ordering. There are three possibilities when the Reach tab is removed. One, is that the Reach tab is the currently selected tab. The second possibility is that a lower number tab than the Reach tab is the current tab, and the last possibility is that the currently selected tab is greater than the Reach tab. The reason we need to know this is because we would like the currently selected tab to remain selected, or in the case that the reach tab was the current tab, we need to select another tab when the Reach tab is removed.

In order to make the programing easier, I choose to select the tab previous to the Reach tab to become the selected tab in the case that the Reach tab is removed it is the selected tab.This is because the Reach tab can never be the first tab (the main tab is always available) and so it always has a predecessor which makes for only on case rather than testing for two possible cases.

Before we delete any of the tabs, we must get the currently selected tab in order to make some choices about which tab will become active. After we have saved the current tab (in the m_nCurTab variable), we go ahead and delete the Reach tab and decrement the total tab count ( the variable m_nNumTabs). Next we decrement the tab number of any tabs that followed the Reach tab.

Now that the tabs are all properly ordered, we must set the proper tab as the selected tab. The only case we have to worry about is when the Reach tab was the current tab. In this case, we set the current tab to one less than it was and activate that tab. That is the purpose of these statements:

if(m_nCurTab == m_nReachTab) { // m_nReachTab was the active tab
m_pTabCtrl->SetCurSel(m_nCurTab - 1);
TabChanged(m_nCurTab - 1);
}

In all of the other cases, the current tab will remain the current tab and the software module pertaining to that tab will remain where it was at the top of the Z-order. Also note that all of the variables corresponding to the reach module have been reset to their initialized default values.

Adding a Tab in SAIL

The second half of the OnReachControl example from above takes care of creating a process module and adding the corresponding tab to the tab control. If the Reach tab does not exist (the first check in the function), then we create it. If the Reach module is found and loaded, then we go ahead and create a tab for it in the tab control and bring it's window to the top of the Z-order. We also increment the tab count (m_nNumTabs) and initialize the variables corresponding to the reach module ( the m_hReachHandle, the m_dwReachThrdID, the value of the m_nReachTab and the m_bReachCtrlExists variable).

The last two function calls within the OnReachControl function are of some interest. The first call to EnumThreadWindows with the arguments m_dwBlankThrdID and WindowToTop, calls up a blank dialog box to the top of the Z-order which effectively blanks out the previous screen and gives the user an instantaneous visual feedback that the program is working while the process that was created gets its window created. Once the widow is created, we call EnumThreadWindows again to bring that new window to the top of the Z-order. To the end user, the effect is one of a single program in action rather than a multitude of programs working together.

Adding an OnSmellControl Function to SAIL

The easiest way to add this function to the CSailView class as stated before, is to copy the body of an existing function (such as OnReachControl as above), and to modify it somewhat. The first modification is just a matter of changing the function name form OnReachControl to OnSmellControl. Next we change all of the variable names from the old names to the new ones like this:

If you keep the names consistent as above so that only one part of the name changes, as in Reach becomes Smell, it is very easy to make these changes in any kind of text editor that will change all occurrences of the string Reach to the string Smell. This way you can just copy the OnReachControl function into the editor, changes all the occurrences of Reach to Smell and then paste the whole thing back into the CSailView file. This way, you won't accidentally miss any occurrences that should have been changed and the bulk of the work is done automatically for you by the text editor.

The next item that needs to be changed, is the path to the Smell program has to be put into the CreateProcess portion of the OnSmellControl function. In our example, the path is ".\\Smell\\Debug\\Smell.exe" which is a relative path rather than a full path name. Usually it is better to use the relative path in case we need to move the files to a new drive or machine. As long as the files are all moved as a group so that relative paths are maintained, then we do not have to go back and update the paths to the executable modules if such a move should ever occur.

Now that we have the possibility of a new tab, all of the functions that modify the tabs must be updated to test for the new tab when repositioning the tabs. In the SAIL project at this time, there are seven modules and seven corresponding functions that need to have a test (if statement) added. They are OnReachControl, OnListenControl, OnSpeakControl, OnSpeakLpc, OnMoveControl, OnHeadControl and OnHeadAutomatic. For an example, we'll just modify OnReachControl although all of the other functions will need to have a similar update as well. For the OnReachControl function, we just add the following lines to the first half of the function where we are testing to see if the tabs exist or not. Her are the lines to be inserted:

if(m_nSmellTab > m_nReachTab)
m_nSmellTab--;

What we are testing for, is to see if the smell tab came after the Reach tab. If it did, then its tab number needs to be decremented when the reach tab is removed. After we add similar lines to all seven functions, we need to make one more change to the OnSmellControl function. Since we copied the OnReachControl function to create the OnSmellControl function, the OnSmellControl function does not contain a test to check for the Reach tab. so we'll add one as follows:

if(m_nReachTab > m_nSmellTab)
m_nReachTab--;

Now that all of the tabs function properly we have to add the body of the function that updates the Smell menu item, that is OnUpdateSmellControl(CCmdUI* pCmdUI). This function was already declared above and added to the message map for the CSailView class. The body of the function looks like this:

void CSAILView::OnUpdateSmellControl(CCmdUI* pCmdUI)
{
if(m_bSmellCtrlExists)
pCmdUI->SetCheck(1);
else
pCmdUI->SetCheck(0);
}

This function is called by Windows when the user chooses a menu item. What we need to do, is check that menu item when the module is running and uncheck it if it is not been loaded. To so this we simply check the variable m_bSmellCtrlExists which is a boolean value set to TRUE or FALSE in the OnSmellCommand function discussed earlier. If it is TRUE then we set the checkmark and if not, we don't set the checkmark.

One Last Change

The only thing we need to add now is a statement in the CSailView destructor to test to see if our new module is in service when the user terminates the SAIL program. Since we don't want to leave any loose modules lying around in memory when we exit SAIL, we add a statement to test to see if the Smell module has been loaded. If it has, then we use our EnumThreadWindows function once again with the Smell threadID and the TerminateWindows callback functions as arguments to terminate the Smell process. Here's the only required code:

if(m_dwSmellThrdID)
EnumThreadWindows(m_dwSmellThrdID, (WNDENUMPROC)WindowTerminate, (LPARAM)(LPTSTR)szWindowName );

Simply stated, if the m_dwSmellThrdID is valid, terminate the process.

So here's a view of our Smell module when called from the SAIL interface.

Conclusion

That's all there is to it. Once a new module is needed, it is a simple matter to add it to the SAIL program. Since the modules are separate entities, the researches can work on them freely without worrying about corrupting the Sail program itself. The only restriction is to keep the dialog boxes for user interaction, small enough so as not to obscure the rest of the SAIL interface(ie. no larger than 625 x 390 pixels with an origin at (7,45)).

Although I doubt this type of program has much value in the commercial world (it is of fixed unmovable window size), it appears to be able to work nicely for our research purposes since it isolates the SAIL program from corruption by the individual modules as they are developed and refined.

In addition, this manual user interface is only for visual feedback when trying out new functions and procedures with the Sail robot. In the end, when SAIL becomes an Autonomous Learning entity, the manual interface will be of little value, and therefore should not require a lot of energy to maintain. In the meantime, it should be easily expanded to accommodate additional modules as they are developed.

So this is what I came up with. Is it the best implementation to meet the requirements of the SAIL project?

I doubt it.

Would I do it the same way again?

Depending on the amount of programming experience I get in the next few years, I'll probably look back and laugh at this first effort. And no doubt I'll find an easier and more efficient way to implement a program of this sort. However, this first contact with Windows programming and with the Visual C++ Integrated Environment has given me a glimpse of the power and possibilities of programming for Windows. The knowledge I take with me from this summer job has given me a great start on becoming a real programmer in a real world. And I am thankful for that opportunity.

THANKS

There are many great books on Windows programming that have helped me along and I'd like to list a few of the more important ones here:

In addition, I'd like to thank John Weng for the opportunity to work in his lab this summer. And Laura Blackwood for the documentation she left on her interface design (I didn't have to start completely from scratch.).

That's about it for now. I've got to get back to the books. Things are changing so rapidly, that there's no time to rest or I'll be falling behind just as I've begun to make headway.

Home | What is Sail? | What is Shoslif? | MSU Home Page | About the MSU Prip Lab | Robot Manual Mode | Robot Automatic Mode