I have a general question about threads prompted by something strange I'm seeing in an application I developed involving ODBC access to Microsoft Access and SQL Server databases. Let me present the context of the problem first. I have been developing some tutorials on ODBC database access in two different languages, e.g., C++ and PowerBASIC. The programs are GUI apps in pure Sdk style. The way I created the demonstration programs is I created a main window with four buttons on it. When the 1st button is pressed the program scours the registry for SQL Driver information and opens up a scrollable output screen where all the registered ODBC drivers plus their attributes are listed.

Pressing the second button dumps a small sample Microsoft Excel worksheet to another output screen using ODBC.

Pressing the 3rd button creates a Microsoft Access database, creates a table within the database, inserts a few sample records in the database, then creates the output screen and displays diagnostic info and the records added.

The fourth button does the exact same thing as the third except with either Sql Server Express or MSDE.

The program is designed so that each button press creates a new output window, and if you repeatedly click a button, instead of creating a new database, records are simply added to the one created on the first button press. Each output window takes care of its own memory and cleanup.

In the various button press procedures I first turn the mouse cursor into an hourglass, and then turn it back into an arrow when the ODBC functionality is complete and the output screen displays its info. I noted a problem with this with the Sql Server button in that when the button was clicked I would very, very, briefly see the hourglass cursor - and sometimes not at all, but the output window might not become visible for a variable number of seconds - sometimes five or six, while the heavy duty Sql Server database engine created the database, inserted records, and extracted records. This is naturally disconcerting to the user when something like this happens as you wonder if the program locked up or whatever. We've all experienced this with hourglass cursors and progress bars.

It disconcerted me enough that I put a lot of effort into moving the code around in various places to see if I could get the hourglass cursor to remain until the output window showed itself, but all my modifications failed.

My only explanation for this is that asynchronous processing is occurring within the various ODBC and Sql Server layers involving multiple threads of execution such that my program's thread is returning before other threads terminate; hence no or little hourglass cursor in my program.

So - not wanting to give up and determined to 'beat it', I decided to fight fire with fire! I used my own threads! Now I'm not a threading expert by any means, although I have used them.

What I did was spawn a new thread in the button click procedures for the Access & Sql Server functionality I described above. I also did a WaitForSingleObject() on the thread handles in the button click procedures so that I would turn the cursor back into an arrow only when all the database machinations in the various procedures were completed. And it worked! It worked wonderfully well. It completely solved the problem with the code in both languages, and I have yet to see it fail once! In the case of Sql Server, if its the first time I've run the program with a computer startup, it oftentimes takes several seconds for the output window to appear, and the hourglass remains the whole time.

So what is my problem? Well, the problem is this - for me to get the program to work in C++ I had to enclose some of the ODBC code in CRITICAL_SECTIONs. If I didn't, very often something would fail somewhere - either in data insertions or dumps (SELECT statements). I could not get the program to work satisfactorily without doing this. This might not seem strange to you except that these programs have no global variobles at all! None. Zilch. In the case of data insertions all the data was stored in local variables, i.e., stack variables within each function. Same with everything else.

My understanding of the need for critical sections is that global data needs protected from context switches that might leave static data in an unstable state. However, while Sql Server itself is 'static' or 'global', so to speak, to my application, my data itself isn't. My understanding is that if there was a thread context switch (and there surely were) at any point during the execution of my ODBC code, my local variables would have been saved to some sort of thread local storage or stack storage and restored upon resumption of my database thread. Not so? I'm asking here. I just don't know. Could you give me any explanation of what is going on here? I can assure you there are no errors in the ODBC code. Without critical sections or even threads the code runs fine. Its just that hourglass thing. However, if I use threads, the same code won't work unless I enclose some of the insertion/retrieval code in critical sections.

Here is another strange wrinkle. Today I just finished the exact translation of the C++ program to PowerBASIC. And that one doesn't seem to require the critical sections. I did the thread thing and the WaitForSingleObject() on the thread handle, and so far it seems to work perfectly without the need to enclose any of the thread code in critical sections.

Can someone please give me some feedback on this. It is a rather interesting problem! Also, if anyone is interested enough, I'd be happy to forward the code for either or both programs. The C++ program I've tested with VC6, VC9, Dev-Cpp, and CodeBlocks. It behaves the same in all of them.

yonghc commented: Laudable effort at sharing very useful codes. However, the mouse movement codes are not working with CODEBLOK yet. +1

The problem is finally solved, and it had nothing to do with threads. The problem stemmed from my unbelievable failure to understand how to toggle the mouse cursor between an hourglass and an arrow when intensive processing had to occur. If anyone ever has that problem, the critical steps are as follows...

1) Get handle of cursor you want;
2) Set it in the REGCLASSEX struct with SetClassLong()
3) Call SetCursor(hDesiredCursor) to change cursor.

It could easily be made into a little utility function like so...

void MousePtr(HWND hWnd, HCURSOR hDesiredCursor)
{
 SetClassLong(hWnd,GCL_HCURSOR,(long)hDesiredCursor);
 SetCursor(hDesiredCursor);
}

A great deal of documentation on this issue suggests that the WM_SETCURSOR message must be handled to accomplish this, but that is not so.

Dear Frederick2,

> Also, if anyone is interested enough, I'd be happy to forward the code for either or both programs.

It would do forum participants a lot of good, and indeed to mankind if we have more of people like Frederick2, a Junior Poster in Training notwithstanding. I would like to request for both your codes. Also, if you don't mind, I would like to request for your Windows-API-mouse control codes for C++ you mentioned in a thread I had raised earlier regarding INKEY input (preferably for CODE::BLOCK).

BTW, have you done the C++ link with MySQL, a free database or foxpro DBF?

Regards,

Thanks for the kind words Yonghc.

I actually followed with interest your recent thread on console input. That's why I thought I'd finally mention the Win32 Api functions to you because it seemed you might not be aware of them.

Anyway, here is an amazingly short little sample for you that ought to compile & run OK with your newly acquired CodeBlocks. I have that also, but I just tested this quick with VC++6; I'm fairly certain it will work OK with CodeBlocks.

Its a standard Win32 console mode program with no MFC (I guess with CodeBlocks I didn't have to tell you that - it matters with Visual Studio). It displays both keypresses and mouse coordinates as you type or move the mouse. Also, it displays left or right mouse button clicks. I didn't code mouse wheel movements, so that data is garbage. You'll probably have fun figuring it out. Here it is...

#include <windows.h>
#include <stdio.h>

int main(void)
{
 HANDLE hStdInput,hStdOutput;
 INPUT_RECORD ir[128];
 UINT blnLoop=TRUE;
 DWORD nRead;
 COORD xy;
 UINT i;
 
 hStdInput=GetStdHandle(STD_INPUT_HANDLE);
 hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
 FlushConsoleInputBuffer(hStdInput);
 while(blnLoop==TRUE)
 {
   if(WaitForSingleObject(hStdInput,3000)==WAIT_TIMEOUT)
      blnLoop=FALSE;  //3 secs
   else
   {
      ReadConsoleInput(hStdInput,ir,128,&nRead);
      for(i=0;i<nRead;i++)
      {
          switch(ir[i].EventType)
          {
           case KEY_EVENT:
             if(ir[i].Event.KeyEvent.wVirtualKeyCode==VK_ESCAPE)
                blnLoop=FALSE;
             else
             {
                xy.X=0;xy.Y=0;
                SetConsoleCursorPosition(hStdOutput,xy);
                printf
                (
                 "AsciiCode = %d: symbol = %c\n",
                 ir[i].Event.KeyEvent.uChar.AsciiChar,
                 ir[i].Event.KeyEvent.uChar.AsciiChar
                );
             }
             break;
           case MOUSE_EVENT:
             xy.X=0, xy.Y=1;
             SetConsoleCursorPosition(hStdOutput,xy);
             printf
             (
              "%.3d\t%.3d\t%.3d",
              ir[i].Event.MouseEvent.dwMousePosition.X,
              ir[i].Event.MouseEvent.dwMousePosition.Y,
              ir[i].Event.MouseEvent.dwButtonState
             );
             break;
          }
      }
   }
 };
 
 return 0;
}


/*
WAIT_ABANDONED   = 128
WAIT_OBJECT_0    = 0
WAIT_TIMEOUT     = 258

Process returned 0 (0x0)   execution time : 0.016 s
Press any key to continue.


WaitForSingleObject   The WaitForSingleObject function returns when one of the following occurs:

                      1) The specified object is in the signaled state.
                      2) The time-out interval elapses.

DWORD WaitForSingleObject
(
  HANDLE hHandle,        // handle to object to wait for
  DWORD dwMilliseconds   // time-out interval in milliseconds
);

Parameters

hHandle            Handle to the object. For a list of the object types whose handles can be 
                   specified, see the following Remarks section.  Windows NT: The handle must 
                   have SYNCHRONIZE access. For more information, see Standard Access Rights.

dwMilliseconds     Specifies the time-out interval, in milliseconds. The function returns if 
                   the interval elapses, even if the object's state is nonsignaled. If 
                   dwMilliseconds is zero, the function tests the object's state and returns 
                   immediately. If dwMilliseconds is INFINITE, the function's time-out interval 
                   never elapses.

Return Values

If the function succeeds, the return value indicates the event that caused the function to return. 
This value can be one of the following.

Value Meaning

WAIT_ABANDONED   128  The specified object is a mutex object that was not released by the thread 
                      that owned the mutex object before the owning thread terminated. Ownership 
                      of the mutex object is granted to the calling thread, and the mutex is set 
                      to nonsignaled.

WAIT_OBJECT_0      0  The state of the specified object is signaled.

WAIT_TIMEOUT     258  The time-out interval elapsed, and the object's state is nonsignaled.


If the function fails, the return value is WAIT_FAILED, which is equal to 4294967295. To get 
extended error information, call GetLastError.

Remarks

The WaitForSingleObject function checks the current state of the specified object. If the object's 
state is nonsignaled, the calling thread enters an efficient wait state. The thread consumes very 
little processor time while waiting for the object state to become signaled or the time-out 
interval to elapse.  Before returning, a wait function modifies the state of some types of 
synchronization objects. Modification occurs only for the object whose signaled state caused the 
function to return. For example, the count of a semaphore object is decreased by one.

The WaitForSingleObject function can wait for the following objects:

Change notification
Console input
Event
Job
Mutex
Process
Semaphore
Thread
Waitable timer

For more information, see Synchronization Objects.  Use caution when calling the wait functions and 
code that directly or indirectly creates windows. If a thread creates any windows, it must process 
messages. Message broadcasts are sent to all windows in the system. A thread that uses a wait function 
with no time-out interval may cause the system to become deadlocked. Two examples of code that 
indirectly creates windows are DDE and COM CoInitialize. Therefore, if you have a thread that creates 
windows, use MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than WaitForSingleObject.
*/
/*
#include <windows.h>
#include <stdio.h>

int main(void)
{
 HANDLE hStdInput,hStdOutput;
 INPUT_RECORD ir[128];
 UINT blnLoop=TRUE;
 DWORD nRead;
 COORD xy;
 UINT i;
 
 hStdInput=GetStdHandle(STD_INPUT_HANDLE);
 hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
 FlushConsoleInputBuffer(hStdInput);
 while(blnLoop==TRUE)
 {
   ReadConsoleInput(hStdInput,ir,128,&nRead);
   for(i=0;i<nRead;i++)
   {
       switch(ir[i].EventType)
       {
        case KEY_EVENT:
          if(ir[i].Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE)
             blnLoop=FALSE;
          else
          {
             xy.X=0;xy.Y=0;
             SetConsoleCursorPosition(hStdOutput,xy);
             printf
             (
              "AsciiCode = %d: symbol = %c\n",
              ir[i].Event.KeyEvent.uChar.AsciiChar,
              ir[i].Event.KeyEvent.uChar.AsciiChar
             );
          }
          break;
        case MOUSE_EVENT:
          xy.X=0, xy.Y=1;
          SetConsoleCursorPosition(hStdOutput,xy);
          printf
          (
           "%.3d\t%.3d\t%.3d",
           ir[i].Event.MouseEvent.dwMousePosition.X,
           ir[i].Event.MouseEvent.dwMousePosition.Y,
           ir[i].Event.MouseEvent.dwButtonState
          );
          break;
       }
   }
 }; 
 
 return 0;
}
*/

/*
while (1) 
{
 iRet=ReadConsoleInput(hStdin,irInBuf,128,&cNumRead); 
 for(i = 0; i < cNumRead; i++) 
 {
     switch(irInBuf[i].EventType) 
     { 
      case KEY_EVENT: // keyboard input 
        KeyEventProc(irInBuf[i].Event.KeyEvent); 
        break; 
      case MOUSE_EVENT: // mouse input 
        MouseEventProc(irInBuf[i].Event.MouseEvent); 
        break; 
      case WINDOW_BUFFER_SIZE_EVENT: // scrn buf. resizing 
        ResizeEventProc(irInBuf[i].Event.WindowBufferSizeEvent); 
        break; 
      case FOCUS_EVENT:  // disregard focus events 
      case MENU_EVENT:   // disregard menu events 
        break; 
      default: 
        MyErrorExit("unknown event type"); 
        break; 
     } 
 }
} 
*/


/*
void cls(HANDLE hStdOutput)
{
 COORD coordScreen = {0,0};
 DWORD cCharsWritten;
 CONSOLE_SCREEN_BUFFER_INFO csbi;
 DWORD dwConSize;
 
 GetConsoleScreenBufferInfo(hStdOutput, &csbi);
 dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
 FillConsoleOutputCharacter(hStdOutput,(TCHAR)' ',dwConSize,coordScreen,&cCharsWritten);
 GetConsoleScreenBufferInfo(hStdOutput,&csbi);
 FillConsoleOutputAttribute(hStdOutput,csbi.wAttributes,dwConSize, coordScreen,&cCharsWritten);
 SetConsoleCursorPosition(hStdOutput,coordScreen);
}
*/

/*
=================================================================================================
=================================================================================================
WriteConsole  The WriteConsole function writes a character string to a console screen buffer 
              beginning at the current cursor location. 

BOOL WriteConsole
(
  HANDLE                hConsoleOutput,          // handle to a console screen buffer
  CONST VOID            *lpBuffer,               // pointer to buffer to write from
  DWORD                 nNumberOfCharsToWrite,   // number of characters to write
  LPDWORD               lpNumberOfCharsWritten,  // pointer to number of characters written
  LPVOID                lpReserved               // reserved
);
 
Parameters

hConsoleOutput          Handle to the console screen buffer to be written to. The handle must 
                        have GENERIC_WRITE access. 

lpBuffer                Pointer to a buffer that contains characters to be written to the screen 
                        buffer. 

nNumberOfCharsToWrite   Specifies the number of characters to write.
 
lpNumberOfCharsWritten  Pointer to a 32-bit variable that receives the number of characters 
                        actually written. 

lpReserved              Reserved; must be NULL.
==================================================================================================
==================================================================================================



==================================================================================================
==================================================================================================
ReadConsoleInput   The ReadConsoleInput function reads data from a console input buffer and 
                   removes it from the buffer. 

BOOL ReadConsoleInput
(
  HANDLE                  hConsoleInput,            // handle to a console input buffer
  PINPUT_RECORD           lpBuffer,                 // address of the buffer for read data
  DWORD                   nLength,                  // number of records to read
  LPDWORD                 lpNumberOfEventsRead      // address of number of records read
);
 
Parameters

hConsoleInput             Handle to the input buffer. The handle must have GENERIC_READ access. 

lpBuffer                  Pointer to an INPUT_RECORD buffer that receives the input buffer data. 

nLength                   Specifies the size, in input records, of the buffer pointed to by the 
                          lpBuffer parameter.
 
lpNumberOfEventsRead      Pointer to a 32-bit variable that receives the number of input records read.
 
Return Values

If the function succeeds, the return value is nonzero.  If the function fails, the return value is 
zero. To get extended error information, call GetLastError. 

Remarks

If the number of records requested in the nLength parameter exceeds the number of records available 
in the buffer, the number available is read. The function does not return until at least one input 
record has been read.  A process can specify a console input buffer handle in one of the wait 
functions to determine when there is unread console input. When the input buffer is not empty, the 
state of a console input buffer handle is signaled.  To determine the number of unread input records 
in a console's input buffer, use the GetNumberOfConsoleInputEvents function. To read input records 
from a console input buffer without affecting the number of unread records, use the PeekConsoleInput 
function. To discard all unread records in a console's input buffer, use the FlushConsoleInputBuffer 
function. 

Windows NT: 

This function uses either Unicode characters or 8-bit characters from the console's current code page. 
The console's code page defaults initially to the system's OEM code page. To change the console's 
code page, use the SetConsoleCP or SetConsoleOutputCP functions, or use the chcp or 
mode con cp select= commands.
==================================================================================================
==================================================================================================
 
*/

I see there is a bunch of junk after the code. I was going to cut it out, but figured some of it might be useful for you in figuring out the MSDN documentation.


In terms of the ODBC program, I think I killed the last bug just tonight! I'll gladly give you the code as I wrote it to help others. I havn't decided how to distribute it yet though. I might give it to a few friends for comments first. I wouldn't want to give anything out with errors in it.

What I did was put a really thin wrapper class on the ODBC functions. This will be useful to me in my work also because I use direct ODBC in my professional work. I've always hated connection strings, so what I did was create logic in the class to automatically build the connection string just by setting a few necessary parameters sush as...

SQL Sql;

Sql.strDriver=_T("Sql Server");
Sql.strServer=_T("localhost");
Sql.strDatabase=_T("TestData");

Sql.ODBCConnect();
if(Sql.blnConnected==TRUE)
{


}
else

etc.

However, the only datasources it works with now are Microsoft Excel, Access, and Sql Server. I guess you can figure out I live in a Microsoft dominated world. However, it would be really easy to modify the code for connecting to any other datasource once you knew what the connection strings looked like.

Anyway, gotta go now - its late here. I'll have to decide on how to distribute the code. Tutorials are pretty much out of the question here (none are good enough), so maybe I could even put it in the code snippets place.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.