Posts Tagged ‘Win32’

Unexpected behavior of Win32 when checking serial ports.

I recently noticed very interesting behavior in ClearCommError() function of Win32.
This code returns how many bytes there are in incoming buffer for a serial port.

DWORD CSerialPort::BytesWaiting()
{
  //Validate our parameters
  ASSERT(IsOpen());

  //Check to see how many characters are unread
  COMSTAT stat;
  GetStatus(stat);
  return stat.cbInQue;
}

void CSerialPort::GetStatus(COMSTAT& stat)
{
  //Validate our parameters
  ASSERT(IsOpen());

  DWORD dwErrors;
  if (!ClearCommError(m_hComm, &dwErrors, &stat))
  {
    DWORD dwLastError = GetLastError();
    TRACE(_T("CSerialPort::GetStatus, Failed in call to ClearCommError, Error:%d\n"), dwLastError);
    ThrowSerialException(dwLastError);
  }
}

However, after actually debugging it I found out that there are cases where

  • The number of bytes in its incoming buffer doesn’t increase while it is calling BytesWaiting() to see if it received enough bytes to read. Then, it should read its incoming buffer for the given COM port to have BytesWaiting() starts to return accumulated number of bytes after reading.
  • When BytesWaiting() returns 0, you still need to read the buffer not to fail in detecting any further data arrived from the other side of communication. reading 0 bytes in this case, will cause the BytesWaiting() keeps returning the number of bytes read after that point.

Normally, whether you read or not, callying BytesWaiting() correctly reports the number of bytes in an incoming buffer.
However, if any of those listed above happens, it stops reporting correct number of bytes.

So, this code fails.

int CLensControllerDlg::ReadIncomingPacket(BYTE incomingBuffer[], int numOfBytesToRead, bool *pShouldGiveUp)
{
     DWORD bytesInReceptionQueue;
     int readNumOfBytes, extraReadNumOfBytes;

     readNumOfBytes = extraReadNumOfBytes = 0;

     // First read
     readNumOfBytes = m_pSerialPort->Read( incomingBuffer, numOfBytesToRead );
     int lengthToReadFurther;
     int secondTryReadLength = 0;

     static int trialCount = 0;

     // Check if it needs to read once more
     if( (incomingBuffer[ 0] == (BYTE) 0x50) &&
           (incomingBuffer[ 1] == (BYTE) 0xAF ))
     {
           lengthToReadFurther = static_cast<int >(incomingBuffer[4]) + 1; // payload length + check sum
            // Read length + 1 more ( 1 is for checksum )

            //!!!!! [1] !!!!!
            // There are cases where bytesInReceptionQueue doesn't have accumulated number of
            // bytes in a reception bufffer.
            while( (bytesInReceptionQueue = m_pSerialPort->BytesWaiting()) < lengthToReadFurther )
           {
                Sleep( 3 ); // 10 msec
                trialCount++;

                //!!!!! [2] !!!!!
                // So, if the bytesInReceptionQueue doesn't contain the accumulated number of bytes
                // through the iteration, try to read some.
                // Then it will start to work again.
                 if( trialCount > 10 )
                {
                      if( bytesInReceptionQueue > 0 )
                     {
                            // Give another chance
                           secondTryReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
                           readNumOfBytes += secondTryReadLength;

                           lengthToReadFurther -= secondTryReadLength;

                           trialCount = 0;

                     }
                      else
                     {
                            // let's see what it happens if try reading for 0 bytes and read further
                           BYTE temp[ 16];
                            int tempReadLength;

                            // bytesInReceptionQueue == 0
                           tempReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
                           readNumOfBytes += tempReadLength;
                           lengthToReadFurther -= tempReadLength;

                            //// Once read, somehow, bytesInReceptionQueue starts to be accumulated again.
                            //bytesInReceptionQueue = m_pSerialPort->BytesWaiting();

                            //tempReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
                            //readNumOfBytes += tempReadLength;
                            //lengthToReadFurther -= tempReadLength;

                           CString msgString;
                           msgString.Format( _T( ">> Will give up this incoming data : %x %x %x %x %x\r\n"),
                                                  incomingBuffer[0], incomingBuffer[1 ],
                                                  incomingBuffer[2], incomingBuffer[3 ],
                                                  incomingBuffer[4] );

                           WriteLogMessage( msgString );
                           TRACE( msgString );

                            if( pShouldGiveUp != NULL )
                           {
                                *pShouldGiveUp = true;
                           }

                           trialCount = 0;
                            return 0;
                     }
                }
           }

            // In the queue, now there are lengthToReadFurther or more to read.
           extraReadNumOfBytes = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], lengthToReadFurther );
     }

     return (readNumOfBytes + extraReadNumOfBytes);
}

The code above was written while I was debugging. So, it’s not clean nor compact.
So, here is better version if you want to see.

int CLensControllerDlg::ReadIncomingPacket(BYTE incomingBuffer[], int numOfBytesToRead, bool *pShouldGiveUp)
{
     DWORD bytesInReceptionQueue;
     int readNumOfBytes, extraReadNumOfBytes;

     readNumOfBytes = extraReadNumOfBytes = 0;

     // First read
     readNumOfBytes = m_pSerialPort->Read( incomingBuffer, numOfBytesToRead );
     int lengthToReadFurther;
     int secondTryReadNumOfBytes = 0;

     // Check if it needs to read once more
     if( (incomingBuffer[0] == (BYTE)0x50) &&
          (incomingBuffer[1] == (BYTE)0xAF ))
     {
          lengthToReadFurther = static_cast<int>(incomingBuffer[4]) + 1; // payload length + check sum
          // Read length + 1 more ( 1 is for checksum )
          while( lengthToReadFurther > 0 )
          {
               Sleep( 1 ); // 10 msec

               bytesInReceptionQueue = m_pSerialPort->BytesWaiting();

               // If it's normal, just waiting until bytesInReceptionQueue >= lengthToReadFurther,
               // and read the whole "lengthToReadFurther" amount of data all at once.
               //
               // However, sometimes the inbound buffer should be read to have further data accumulated.
               // If not,
               secondTryReadNumOfBytes = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], lengthToReadFurther );
               readNumOfBytes += secondTryReadNumOfBytes;

               lengthToReadFurther -= secondTryReadNumOfBytes;
          }
     }

     return readNumOfBytes;
}

LCD screen brightness control for MacBook with Intel GMA X3100 chipset

In my previous post, The Strange DeviceIoControl result with IOCTL_VIDEO_QUERY_DISPLAY_BRIGHTNESS, I said that query LCD screen as described in MSDN document didn’t work. Today I found out the reason. To tell the truth, I use a MacBook 13″ White with Intel GMA 950 chipset for the graphics component. So, the Windos 7 x64 couldn’t find a proper device driver for the built-in LCD screen and just installed Generic PnP display driver.

When a proper device driver is installed, it registers its device name. However, in the “Generic PnP display” driver case, it doesn’t register itself as “LCD”. So, the CreateFile() can’t create a handle to an LCD screen with that device name.

Then, what is a proper name for the unregistered device? It is not documented in any MSDN document. However, with help of querydosdevice program, I could find out the device name.

This is the device name.

So, in a source code, you will access the unregistered device like this.

// 1. Query the Backlight supported device
// IOCTL_VIDEO_QUERY_SUPPORTED_BRIGHTNESS s
HANDLE hLCDScreen;
hLCDScreen = CreateFile( _T("\\\\.\\DISPLAY#APP9C5F#4&30ca27f4&0&UID67568640#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"), GENERIC_READ,			// No access to the drive 
						FILE_SHARE_READ | FILE_SHARE_WRITE,	// share mode
						NULL,								// default security attributes
						OPEN_EXISTING,						// Disposition
						0,
						NULL );
if( hLCDScreen == INVALID_HANDLE_VALUE )
{
	dwError = GetLastError();
	TRACE1( "CreateFile() error(%d)\n", dwError );

	return;
}

However, the Generic PnP Display driver doesn’t support brightness level. So, I couldn’t write any more codes to control the brightness.

Can anyone tell me what chipset MacBook with Intel GMA 940 uses for LCD screen? or tell me where I can find a proper device driver?

CreateEx() and Create() for CDockablePane

Historically CreateEx() has been thought as Create() with additional functionality like more styles.
So, if you take a look at many MFC classes which provide CreateEx() and Create(), you can see that CreateEx() calls Create() and do more chores to provide added functionality.

However, I found out that CDockablePane’s doesn’t.
Although MSDN document shows that there is CreateEx() for CDockablePane, it actually doesn’t exist.
So, you shouldn’t be fooled by the MSDN document and create CDockablePane with CreateEx() and wonder why it doesn’t work.

Storing a pointer value in a non-pointer variable vs. Storing general value in a pointer variable : INT_PTR, LONG_PTR etc vs. MAKEINTRESOURCE

If you are a programmer who studied C/C++ after Windows 95, you may not know the existence of windows data types like INT_PTR, LONG_PTR, because you don’t really need to use it.

So, when you have a chance to port legacy codes to x64 platform, you may  fail in considering proper porting of variables declared with those or variables which don’t use them but semantically means the same.

At StackOverFlow, there is good explanation on this subject.

DWORD_PTR, INT_PTR, LONG_PTR, UINT_PTR, ULONG_PTR When, How and Why?

However, I would like to add more comment.

During MS DOS era, many programmers wrote codes in a way that the longest data type available on his/her machine to store addresses or pointer values. There were a little reason for that. For example, you want to do somewhat mathematical calculation with addresses without bordering with the width of the data type.
To me, it has been always better to calculate on addresses with the width of data type in mind, but there are some other cases you should have disregard the width of data type and just calculate something. It was kind of high technique for doing something weird. ( Some can be, while others are not. )

Some Windows API functions and member functions still require to use such convention. They ask you to pass address value to a variable which is not pointer variables. For that purpose, they use *_PTR.

So, I noticed that not a few programmers, who don’t know history, tended to fail in porting legacy code to x64.

Also, there is opposite example.
This time, it is to store non-pointer value to a pointer variable. Such an example is MAKEINTRESOURCE() macro.

#define MAKEINTRESOURCE(i)(LPTSTR)((DWORD)((WORD)(i)))

So, it takes an integer number for a resource like resource ID for a bitmap or an icon, and converts it to pointer to string (LPTSTR).

So, if you are one of new guys who don’t know its history, just get familiar with this and don’t be puzzled!

LoadLibrary() can’t load a DLL, and have no clue why?

How do you link or load a DLL file?
My approach is to link a stub *.lib file to a host project. This approach supports best of dynamic load and static load. In other words, each DLL files can be maintained independently, while it is very easy to figure out what functions are not found properly and what functions are in conflict, etc.

However, if you really want to write a host program which can support plug-ins such that each plug-ins can be dropped in a given folder and the host program recognize the plug-in on the fly and starts to provide functionality defined in the plug-ins, you will want more dynamic approach.

Yes. MFC/Win32 provides such approach. With LoadLibrary(), anywhere in your source codes, DLL files can be loaded. Then you can define some common interface/parent class for pluggable architecture.
Neat, isn’t it?
However, there is one problem with LoadLibrary(). It can be difficult to troubleshoot. For example, if your DLL file can’t be loaded, you will check if the search path is correct, if the DLL file is really there, and so on. However, what if all those which affect visibility of the DLL are satisfied but the LoadLibrary() still fails?

Let’s take a look at such codes here.

// JongAm
// Let's try loading "Mediabase.dll". Why the hell WBMediabase started failing in finding the dll?

// Enable the line below to see what is wrong with LoadLibrary()
//SetErrorMode( 0 );

HMODULE dynamicLibrary_Handle = LoadLibrary( _T("MediaBase.dll") );

if( dynamicLibrary_Handle )
{
	AfxMessageBox( _T("Successful in finding Mediabase.dll") );
	TRACE( "Successful\n" );
}
else
{
	int lastError = GetLastError();

	CString message;
	message.Format( _T("Failed in finding Mediabase.dll (%d)"), lastError );
	AfxMessageBox( message );

	TRACE( "Can't find the mediabase.dll\n" );
}

Nothing special, right? However, it failed on me. Why? Later I found out that MediaBase.dll also required other DLL files, and those DLL files also require other DLLs. They are all loaded using LoadLibrary() functions. So, they were not detected with Dependency Walker. Hmmmm….
So, whenever you write LoadLibrary(), it is better to provide some feedback message when it fails loading DLL files like “Failed in loading Blah Blah.dll”.

However, what if you already have a few 3rd parth DLL files which you can’t control?
In such case, there is a really handy diagnosis function. It is the SetErrorMode(). If 0 is passed as its parameter, it will display a message box which tells what is wrong, like “X.dll is missing”.
So, for example, when you load a A.dll using LoadLibrary() but the A.dll also load A-1.dll and the A-1.dll loads A-2.dll and the A-2.dll is missing, the SetErrorMode(0) will make a message box which says “A-2.dll is missing” displayed.

Here are a few screenshots.

When a DLL file required by the designated Mediabase.dll is missing

When the designated DLL file, Mediabase.dll, itself can't be found.

When all DLL file dependency are resolved

Wow.. quite helpful, isn’t it?

Visual C++ 2008 resource editing and control variable annoyance

Hmmm.. It was strange that VC++ 2008 add variables wizard didn’t let a “value” variable of a radio button. With VC++ 6, it was possible as far as I remember.

So, I searched to find out what changed since VC++ 6.

What I found are :

However, the most official document is :
Grouping Radio Buttons on a Dialog Box

People are confused by the different way of setting variable for a radio button to they way you do with its previous version, and MS didn’t seem to present “What is changed and How is changed” document.

So, the way it manipulate resources was changed. No, problem. However again MS’s approach shows problems.

  1. In the resource editor, there is no way to group radio boxes visually
  2. Because only one radio buttons is to be turned on exclusively in its group, there should be a visual way to make a series of radio buttons grouped. However, a group is defined by setting the order of radio buttons in series and only the first radio button is flagged as “Group”.

  3. Setting only the first radio button as “Grouped” is not reasonable.
  4. Basically there are multiple radio buttons in a group. Then all should be “grouped”. But MS want you to set only the 1st radio button as “grouped”. Then all the following radio buttons are grouped. It doesn’t feel natural.

  5. Setting the variable to the first radio button
  6. So, a variable for a group should be declared for the group. But a variable for the 1st button is declared and treated like that it is the representative button for the group.

All of them don’t look natural and reasonable.

I think this is why people get confused and couldn’t get a clue on how to make set a variable for radio buttons.

Debugging a release build with the Visual C++ 2005

Sometimes debug build doesn’t crash but release build crashes. This usually happens when memory area which is not valid is accessed. Debug build usually has some fence around allocated memory area.

However, recently I suffered somewhat different case. In this case, you need to debug a release build.
Yeah.. It is quite easy. Just enable debug information for a release build. However, in my recent case, doing so didn’t reveal symbols or display correct values for variables.
At first, I thought a stack is corrupted. However, I found out that optimization option did affect the symptom.

So, disabling optimization was the solution to check content of variables.
coptimization

The cause of the crash was due to #pragma pack.
In one of its header file, #pragma pack (push, 1) is used, and at the end of the file #pragma pack(pop, 1).
It should have been #pragma pack(pop) to reverting.

So, in a influenced class implementation file, it used correct alignment, while in other source file which included a troubled header file, the alignment of this class is not interpreted as it should have been.
So, it caused a vtable evasion in a source file which includes the troubled header file. Therefore, it crashed when it called one of the method of the influenced class.

It is strange that this didn’t cause problem with a debug build. There is not explanation on the MSDN site whether this is disabled in debug build or not.

opening a process with SE_DEBUG_NAME privilege

To open a process successfully, the opener should be allowed in the DACL ( discretionary access control list ). So, I also tried adding an ACE, Access Control Entries, to the DACL. At least all who tried seem to think that way after reading MSDN documents. Yeah..

Then, this kind of code will be written.

HANDLE hProcess, hCommandEventLocal, hAllocationEventLocal;

BOOL isOK;
HANDLE hToken;

///////////////////////////////////////////////
// Setting SE_DEBUG_NAME
// First, get an access token for the process
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, processId ); // 1
if( hProcess == NULL )
{
	PrintError(”Failing in getting a process handle with PROCESS_QUERY_INFORMATION at %d (Error Code : %d)”,
				__LINE__, GetLastError());
	return E_FAIL;
}

// Second, get the access token for the process
isOK = OpenProcessToken( hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ); // 2
if( !isOK )
{
	PrintError(”Failing in getting a token handle at %d (Error Code : %d)”,
				__LINE__, GetLastError());
	return E_FAIL;
}

SetPrivilege( hToken, SE_DEBUG_NAME, TRUE );

CloseHandle( hToken );
CloseHandle( hProcess );

//////////////////////////////////////////////////////////////////////

hProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE, processId ); // 3

The code looks reasonable, doesn’t it?
While writing this code, I found one thing weird.
To open a process with PROCESS_DUP_HANDLE, you need a SeDebugPrivilege. To set SeDebugPrivilege, i.e SE_DEBUG_NAME, you need a token. To get a token, you need an open handle to the process. Oh.. circular dependency!!!!!
So, I tried opening a process with PROCESS_QUERY_INFORMATION.
However this fails at 2. Why? I don’t know there is no explanation on MSDN documents.

Huge barrier!!!!

However, I found a post from MSDN community forum. He also tried codes shown above, but found out the correct way to achieve the goal.
How?

HANDLE hProcess, hCommandEventLocal, hAllocationEventLocal;

BOOL isOK;
HANDLE hToken;
HANDLE hCurrentProcess;
hCurrentProcess = GetCurrentProcess(); // 1
isOK = OpenProcessToken( hCurrentProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken );
if( !isOK )
{
    return E_FAIL;
}

SetPrivilege( hToken, SE_DEBUG_NAME, TRUE );

//////////////////////////////////////////////////////////////////////

hProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE, processId );

Differences are to open current process instead of the target process, and set the token of the current process to have the SeDebugPrivilege. The the token of the current process have all the privilege to open other processes the way it wants.

Weird!!!! Isn’t it? It is totally different approach than the direction set by the MSDN documents.
Yeah…. MSDN documents.. as usual.

But if you think more, you can figure out why it works.
Probably default DACL can have an ACE like “allow any process with SeDebugPrivilege to open any process anyway it wants.”.
Then it is reasonable.
Why doesn’t MSDN document have this kind of explanation?
Um………………..

P.S. There is a related post at bits and bytes blog.

How to solve weirdness of the high resolution counter

In a previous post, some issues on QueryPerformanceCounter() was discussed.

Fortunately I found a very good blog, Zooba’s Blog on problems using counters like rdtsc and QueryPerformanceCounter. Because there is additional processing time needed to get the CPU frequency that is used along with the result of rdtsc, or because just approximate frequency is used by looking up a registry, I think it is not good to use the rdtsc.
So, the last option is to use the QueryPerformanceCounter.

There are two issues to solve.

  1. To guarantee the timing starts and ends where you want to do so.
  2. Because of optimization, the compiler may reorder instructions. So, your “Start Measuring” command can be placed earlier and later.

  3. To obtain reliable count.
  4. As it was discussed in the previous post, it doesn’t return reliable count number on multi-processors or multi-core processors.

To solve the 1st problem, special instructions called “serializing instruction” should be called.
According to the Zooba’s Blog, there are 3 of them : iret, rsm, cpuid.
However, the iret and rsm change the instruction pointer. So, they are out. The cpuid is for getting information a cpu. So, it has no harm.
(What is the “serialization instruction“? It is an instruction which forces codes to be serialized. So, instructions in the queue already will be flushed out, and an instruction like cpuid is processed. So, you can ensure that the instruction for starting and stopping measuring will be located as they are expected. )

The 2nd issue is raised especially when the CPU you use is multicore processor or multi processor. Also when your CPU has the speed-step technology, it happens.
However, as it was mentioned in the Zooba’s Blog, the speed-step case is minimized. Because in the code you want to measure its performance, it would make your CPU sweat enough in most cases. So, the most troublesome case is the multi-core, multi-processor case.
How to solve this problem? It is also explained in the Zooba’s blog. (Thank you, Zooba!)
If you set the a specific processor runs the QueryPerformanceCounter(), it will return reliable result. So, the SetProcessorAffinity() or the SetThreadAffinity() can be used.

So, here is the code example.

// performance_measure.h
#ifndef PERFORMANCE_MEASURE
#define PERFORMANCE_MEASURE

#define DECLARE_GLOBAL_FOR_PEFORMANCE_MEASURE()\
    LARGE_INTEGER g_Start_Counter, g_End_Counter, g_Frequency;\
    DWORD g_Old_ProcessAffinityMask,g_New_ProcessAffinityMask, g_SystemAffinityMask;\
    HANDLE hCurrentProcess;

DECLARE_GLOBAL_FOR_PEFORMANCE_MEASURE();

inline void INIT_PERFORMANCE_MEASURE( void )
{
    hCurrentProcess = GetCurrentProcess();
    GetProcessAffinityMask( hCurrentProcess, &g_Old_ProcessAffinityMask, &g_SystemAffinityMask );

    QueryPerformanceFrequency( &g_Frequency );
}   

inline void START_PERFORMANCE_MEASURE( void )
{
    int CPUInfo[4];

    // Serializing Information
    __cpuid( CPUInfo, 0 );  // used the intrinsic version of the cpuid

    g_New_ProcessAffinityMask = 0x01;
    SetProcessAffinityMask( hCurrentProcess, (DWORD_PTR)&g_New_ProcessAffinityMask );

    QueryPerformanceCounter( &g_Start_Counter );

    // Revert to back
    SetProcessAffinityMask(hCurrentProcess, (DWORD_PTR)&g_Old_ProcessAffinityMask );
}

inline void STOP_PERFORMANCE_MEASURE( void )
{
    int CPUInfo[4];

    __cpuid( CPUInfo, 0 );  // Serializing Information
    SetProcessAffinityMask( hCurrentProcess, (DWORD_PTR)&g_New_ProcessAffinityMask );

    QueryPerformanceCounter( &g_End_Counter );

    // Revert to back
    SetProcessAffinityMask(hCurrentProcess, (DWORD_PTR)&g_Old_ProcessAffinityMask );
}

double GET_PERFORMANCE_MEASURE( void )
{
    return ((double)g_End_Counter.QuadPart - (double)g_Start_Counter.QuadPart)/(double)g_Frequency.QuadPart;
}

#endif

Insert above code like this in your code.

#include 
#include 
using namespace std;

// This header file contains above code
#include "performance_measure.h"

void matrix_multiplication( void )
{
    ...

    printf("Single\n");

    INIT_PERFORMANCE_MEASURE();
    
    START_PERFORMANCE_MEASURE();

    start_t = clock();

    for( iteration = 0; iteration < 90000; iteration++ )
    {
        for( i = 0; i < 8; i++ )
            for( j = 0; j < 8; j++ )
            {
                temp = 0;
                for( k = 0; k < 8; k++ )
                {
                    temp += matA[i][k]*matB[k][j];
                }
                matC[i][j] = temp; 
            }
    }
    duration_t = clock() - start_t;

    STOP_PERFORMANCE_MEASURE();

    printf("Duration = %f (%f)\n", (double)duration_t/CLOCKS_PER_SEC, 
        GET_PERFORMANCE_MEASURE() );

Now, you will get a reliable result.

Hew….

Difference in Concurrency Model in MacOS X and the Windows (3)

3. Event

Windows is made based-on event-driven model. Therefore, events play very important role on Windows environment, and are used very often whether a programmer make one or use ones provided by the OS. Let’s take a look at how events are used.

Windows는 event-driven 모델을 써서 만들어졌다. 그러므로 event는 상당히 중요한 역할을 하고, 많은 프로그램들이 OS가 제공하는 event를 사용하건, 아니면 해당 프로그램에서 event를 만들건 이 event를 많이 사용한다.
우선 이 event가 사용되는 예를 보자.

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hThread[kMaxThreads];

    int i;
    
    initEvent();

    for( i = 0; i < kMaxThreads; i++ )
    {
	// Threads wait on their events and trigger events for others.
        hThread[i] = CreateThread( NULL, 0, doMultiThreadWay, 0, 0, &gThreadID[i] );

        if( hThread[i] == NULL )
        {
		...
            ExitProcess(i);
        }
        else
        {
		...
        }
    }

   // Until now, all threads are created and wait for their events.

   // set the 1st event, gEvents[0], or fire an event.
    SetEvent( gEvents[0] );

    // Wait until all threads have terminated
    WaitForMultipleObjects( kMaxThreads, hThread, TRUE, INFINITE );

    // Close all thread handles
    for( i = 0; i < kMaxThreads; i++ )
        CloseHandle( hThread[i] );

    destroyEvent();

	return 0;
}

// This is how the events are initialized.
void initEvent( void )
{
    int i;

    for( i = 0; i < kMaxThreads; i++ )
    {
	// events are automatically reset if there are once set.
        gEvents[i] = CreateEvent( NULL, FALSE, FALSE, NULL );

        if( gEvents[i] == NULL )
            outputString( __T("Error in creating events\n"), FOREGROUND_RED | FOREGROUND_INTENSITY );
    }
}

// Threading function
DWORD WINAPI doMultiThreadWay( LPVOID lpParam )
{
    TCHAR msgBuf[kBuffSize];
    size_t cchStringSize;
    DWORD dwChars;
    DWORD threadID;
    DWORD dwWaitResult;
    WORD textColor;

    int i;
    
    threadID = GetCurrentThreadId();
    if( threadID == gThreadID[0] )
        textColor = FOREGROUND_GREEN | FOREGROUND_RED;
    else if( threadID == gThreadID[1] )
        textColor = FOREGROUND_BLUE | FOREGROUND_RED;
    else
        textColor = FOREGROUND_BLUE | FOREGROUND_GREEN;

    // Thread safe way of outputting
    StringCchPrintf( msgBuf, kBuffSize, __T("doMultiThreadWay (%d)\n"), threadID );
    outputString( msgBuf, textColor );

    for( i = 0; i < 5; i++ )
    {
	// Each threat wait for its event.
        if( threadID == gThreadID[0] )
        {
            dwWaitResult = WaitForSingleObject( gEvents[0], INFINITE );
            outputString(__T("First thread says \"Do It\" to the second thread\n"), textColor );
		// An event, e.g. gEvents[0], is automatically reset.
            SetEvent( gEvents[1] );
        }
        else if ( threadID == gThreadID[1] )
        {
            dwWaitResult = WaitForSingleObject( gEvents[1], INFINITE );
            outputString(__T("Second thread says \"Do It\" to the third thread\n"), textColor );
            SetEvent( gEvents[2] );
        }
        else
        {
            dwWaitResult = WaitForSingleObject( gEvents[2], INFINITE );
            outputString(__T("Third thread says \"Do It\" to the First thread\n\n"), textColor );
            SetEvent( gEvents[0] );
        }

    }

    return 0;

}

What the threading function does is to wait for their event and trigger the next event. It is to wake up threads one by one.
This illustrates the effect of using events.

위에 있는 쓰레드 함수가 하는 것은, 각 쓰레드에 대응하는 이벤트를 기다리다가, 자기 것이 트리거되면, 해당 쓰레드가 다음의 이벤트를 fire함으로써, 다음번 쓰레드가 깨어나게 하는 것이다.
이런 행동이 바로 이벤트를 사용함으로써 얻고자 하는 효과이다.

If you don't want to read the whole text above, here is the screenshot which will help you what the codes do.

위의 긴 글을 읽기 싫다면, 다음의 스크린샷을 보면 위의 코드가 무엇을 하는지 대번에 눈치를 챌 수있을 것이다.
What the codes do.

As it has always been, events can be implemented using mutex or semaphore. However, using events will simplify things.

역시 여기서도 생각해 볼 수있는 것이, 이 Event라는 것도 semaphore나 mutex을 이용하면 구현할 수있을 거라는 생각이다. 하지만 event를 사용하면 편리하게 구현을 할 수가 있다.

It is characteristic that there are functions like WaitForSingleObject() and WaitForMultipleObjects(), and this makes the Windows different from other OSes like Unix. So, a student who learned multiprocessing and parallel computing model based on Unix and other Oses than Windows can be confused.
However, it is also easy and reasonable model, and there is no problem in learning this Windows model.

이상에서 살펴본 Win32에서의 synchronization 모델에는 그 특징이 있다.
Critical Section, Mutex, Semaphore, Event등을 선언하고 세팅한 후, WaitForSingleObject()와 같은 함수를 이용해서 해당 상황이 발생하는지 기다리는 것이다. 이것이 주목해야 할 Win32의 synchronization 프로그래밍 모델이다.
무척 이해하기가 쉽고 논리적으로 설계가 되었지만, 다른 OS에는 이런 WaitForSingleObject()와 같은 함수가 없다. 그러므로 Unix와 같은 다른 OS에서 프로그래밍을 하다가 Windows에서 하게 되었을때, 혼동을 일으킬 수있다.

Windows multithreading (MFC)

MFC contains lots of wrappers to Win32 data types and their behaviour. So, it is framework.
MFC는 바로 이상의 것들을 감싸서 사용하기 쉬운 클래스로 만들어준 것이다. 즉 Framework인 것이다.

However, the MFC wrappers to synchronization do more than that.
It makes the synchronization model of Windows look similar to that of the Unix.
Let’s take a look at an example.

그런데 synchronization에 관해서 MFC의 wrapper들은 단순히 wrapping해서 쓰기 쉽게만 해주는 것이 아니라, 그 모델을 Unix의 그것과 비슷하게 해준다.
자 예를 한번 보자.

// Global Mutex Object
CMutex g_m;
int g_C;

UINT ThreadFunction1(LPVOID lParam)
{
    // Create object for Single Lock using the mutex
    CSingleLock lock(&g_m);

	// try obtaining a lock.
    lock.Lock();

    // code block protected by the lock.
	...

	// release the lock
    lock.Unlock();

    return 0;
}


UINT ThreadFunction2(LPVOID lParam)
{
    // Single Lock Construct Mutex
    CSingleLock lock(&g_m);

   // If the other thread already obtained the lock, this thread will wait here.
    lock.Lock();

    // code block protected by the lock.
	...

    lock.Unlock();

    return 0;
}

Where the Lock() function is located is comparable to the lines where WaitForSingleObject() is used in Win32.
For critical section, i.e. CCriticalSection, can be also implented by replacing g_m with a CCriticalSection. So, for mutex, semaphore, event, and critical section, the style how they are locked and and unlocked are the same.
This is the major difference between the Win32 model and the MFC model.

Anyway, where it is locked and unlocked are similar to the model for the Unix.

Lock() 메소드가 쓰여진 부분이 바로, Win32의 경우에 WaitForSingleObject()가 쓰여진 부분에 대응한다고 볼 수있다.
MFC에서는 그 locking variable이 뭐던간에, 즉 critical section이냐, mutex냐, event냐에 상관없이 모두 같은 프로그래밍 모델을 제공한다. 즉 위의 코드에서 CMutex로 선언된 부분을 CCriticalSection으로 바꾸면, 거의 코드를 고칠 필요없이, 그대로 사용할 수있게 된다. 즉 다시 말하자면, 다른 locking variable에 대해서 통합된 모델을 제공한다는 것이다.

아무튼 전체적으로 lock을 하고 unlock을 하는 부분이 Unix를 닮은 부분이다.

So far, we tried figuring out how synchronization looks like on the Windows.
In the next post, let’s try the Objective-C and Cocoa case.

자 이상으로 Windows에서의 synchronization에 대해서 알아보았다.
다음에는 Objective-C와 Cocoa의 경우를 살펴보기로 하자.

%d bloggers like this: