Win32 Basics

[Previous] [Next]

Visual Basic has done an excellent job of hiding the fine details of how 32-bit Windows applications are truly structured, and rightfully so. Knowing these details defeats the purpose of programming in Visual Basic. Still, some information about threads had to be revealed in order to deliver ActiveX and COM to the Visual Basic developer. This information shows itself in the frame labeled Threading Model on the General tab of the Visual Basic IDE Project Properties dialog box. (See Figure 3-1.) Depending on the type of ActiveX project you select, certain options are enabled and others are disabled. I will cover these options in detail later in the chapter in the sections "Thread Management" and "The Cost of Living for Visual Basic Objects." First let me give you a brief overview of how 32-bit Windows applications are defined with respect to the operating system.

click to view at full size.

Figure 3-1. Visual Basic 6 Project Properties dialog box.

A Windows application is defined in terms of a process, which is an instance of a running application. The process can own an address space of up to two gigabytes, consisting of memory and resources. Memory comprises the code and data of an application's EXE file and dependent DLLs. Resources consist of kernel objects such as thread, mutex, and file objects. Resources also include user objects such as windows, menus, brushes, and fonts. When a process terminates, all memory and resources it owns are reclaimed by the operating system.

Processes are dormant—a process must own a thread that will execute the code residing in the process's address space. In fact, when a process is initialized, the operating system automatically creates a single thread (known as the primary thread) that ultimately calls the application's WinMain function. This primary thread continues to execute until the WinMain function returns, which leads to the termination of the primary thread. Because it makes no sense for a process to exist without its primary thread, the process also will terminate at this point, relinquishing all memory and resources to the operating system.

A process can contain more than one thread, hence the term multithreaded application. All code execution within a thread is synchronous; however, you might sometimes want your application to execute different segments of its code asynchronously. For example, you might want your application to print reports while allowing the user to continue entering data via the user interface. In order for two tasks within an application to run simultaneously, you must create another thread. To do this, you need to call the Win32 API function CreateThread. This function takes several parameters. Please refer to the Microsoft Platform SDK available at msdn.microsoft.com/downloads/sdks/platform) for a full disclosure of these parameters. More important, however, is that CreateThread requires the address of a function prototyped as follows:

 DWORD WINAPI ThreadFunc( LPVOID ) 

This function—often referred to as the ThreadFunc function—accepts a single 32-bit pointer as an argument and returns a 32-bit exit code. When CreateThread is called, it creates a new thread that executes a given ThreadFunc. When ThreadFunc returns, the thread terminates. Visual Basic does not have inherent support for creating threads, except in the case of creating an out-of-process ActiveX EXE, which is configurable only by using the Project Properties dialog box as previously described. Nevertheless, provided you declare the appropriate Win32 API functions correctly in Visual Basic (see the Win32api.txt file on your Visual Basic 6 CD for examples of Win32 API declarations in Visual Basic), you can create threads by calling the CreateThread function as illustrated in the code extract below:

 Sub DoSomeThing() ' Code executes  ' Spawn a new thread that executes the code defined ' in function MyThreadFunc. CreateThread(..., ..., AddressOf MyThreadFunc, ..., ..., ...) ' Code here continues to execute without waiting for ' MyThreadFunc to return. End Sub 

This approach is somewhat unorthodox because, for various reasons, you can easily violate access to memory. For instance, the Visual Basic IDE is not thread-safe. Any attempt to debug an application will result in a general protection fault, which causes an involuntary termination of the application.

What makes a multithreaded application capable of executing code in different threads concurrently is the fact that Microsoft Windows 2000, Microsoft Windows NT, Microsoft Windows 98, and Microsoft Windows 95 are preemptive multitasking operating systems. In short, the operating system schedules time for all threads—not processes—to execute on the processor. The process's priority in conjunction with the priority level of the thread determines how frequently the operating system will schedule a given thread. If all threads have the same priority, the operating system gives each thread equal time in a round-robin fashion until all threads have terminated. In general, this time-slicing algorithm is so fast and efficient that it gives the illusion that multiple tasks are executing concurrently. Actually, only one task can execute at a time on a single-processor system. If, however, the computer has multiple processors, tasks can run concurrently because each processor can execute a separate thread.

Obviously, multithreaded applications can be very useful. Nonetheless, having an application with multiple threads introduces two possible problems: race conditions and deadlocks. A race condition occurs when the operating system gives control to a thread that changes the contents of memory that was being accessed by another thread. This leads to the corruption of unprotected memory shared by multiple threads and produces unpredictable, if not fatal, results. To avoid corrupting memory, Win32 allows you to synchronize thread access to a specific section of memory. The specific memory remains protected until the thread that initiated the protection releases it. For example, when the operating system gives control to Thread A, which wants to execute code in a section currently protected by Thread B, Thread A must wait until Thread B releases the protection before it can enter that code segment. Win32 primitives, including critical sections, mutexes, semaphores, and events, provide thread synchronization. Each primitive has unique features that favor one over the other in certain situations. More important, however, is that the primitives all help serialize access to memory shared by multiple threads.

The second problem, deadlock, is caused by serializing access to memory shared by multiple threads. A deadlock occurs when two or more threads are suspended while each waits on the other to release protection on a code segment. Deadlock is possible if a thread executes a block of code whose completion is contingent upon some other shared code segments, which the thread attempts to enter at the very point where it's needed. For example, Thread B has locked a segment of code that Thread A needs, which forces Thread A to wait. However, Thread B might very well be in a suspended state waiting to enter a code segment locked by Thread A, thus creating deadlock. To avoid the problem, a thread should establish protection (obtain locks) on all dependent shared code segments as a group at the beginning of the block of code. The thread remains in a suspended state until it can protect all dependent shared code segments using an "all or nothing" protection policy. This policy mandates that a thread cannot lock code segments as they become available unless all segments are available.



Microsoft Visual Basic Design Patterns
Microsoft Visual Basic Design Patterns (Microsoft Professional Series)
ISBN: B00006L567
EAN: N/A
Year: 2000
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net