Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Multithreading and Visual Basic


December 1996: Esposito

Multithreading and Visual Basic

Partitioning tasks among specialized routines

Dino Esposito

Dino is a senior software engineer specializing in Windows programming and a contributing editor to many Italian programming magazines. He can be reached at [email protected].


In a multithreaded application, tasks are partitioned among various specialized routines, called "threads," that run concurrently. From the system's point of view, these threads are single units waiting for CPU and memory. From the programmer's perspective, these threads provide a way to improve performance, especially for frequent and complex operations.

In concurrent environments, however, the rules are different from a traditional programming model. You cannot assume, for instance, that one line will execute before the next. The serialization of events does not come from the order in which you write your code, but from the semantics and behavior of the threads that will actually carry it out. So each time you need to verify operations before proceeding, you should synchronize all the threads involved to a global object.

In this article, I'll show how to create multithreaded applications using both the SDK and Visual Basic. The source code (available electronically) includes: two VB4 projects (vbsdk_thread and vbocx_thread), which show how to multithread with both the SDK and an OCX; source code for an auxiliary DLL, including samples of .AVI animation; and the full source code for the OCX. I compiled the DLL with Borland C++ 4.52; and the OCX with Visual C++ 4.1. The core of the VB code is isolated in the Form_Load Sub, VB_CreateThread, and VB_TerminateThread subroutines. The latter two functions are invoked in response to users clicking on Go and Stop buttons, respectively; see Figure 1.

Multithreading in Visual Basic 4

The Visual Basic run-time engine is an interpreter that works as a single-threaded process. The engine isn't aware of context-switching, concurrency, parallel processing, priorities, task scheduling, and the like. Consequently, the Visual Basic language does not natively support threads, and you cannot use VB code as the body of your threads.

Fortunately, Visual Basic provides full access to the Windows 95 API. The two API functions you need to know are CreateThread, which returns a handle to the new thread, and EndThread, which stops the thread.

CreateThread's first argument is a pointer to a special structure describing the security attributes of the thread. The argument points to a security descriptor for the handle just created. If this parameter is NULL, then checking will not occur when someone tries to use it. Consequently, the handle may be used to access the thread wherever such a handle is accepted.

Because security is only supported under Windows NT, the first parameter will usually be NULL in Windows 95. The second argument indicates the stack size, in bytes, for the thread. If it is 0 (the most-common setting), then the thread will be assigned the same stack size as the parent. The next two arguments define the actual behavior of the thread, representing the starting routine and its parameters. The code pointed to by the third parameter is the body of the thread, and the fourth is a pointer to its parameters.

CreateThread requires two more arguments. One specifies the creation flags, making it possible for you to create a thread in a suspended state. With the default of 0, the thread is launched immediately after being initialized. The last parameter is a pointer to a buffer that will be filled with the thread ID. You should always pass it a valid pointer; otherwise, you'll get a system error. Example 1 is a typical call to CreateThread. The thread code should be a THREAD_START_ ROUTINE object, which is a pointer to a function accepting a LPVOID value and returning a DWORD value.

To terminate a thread, you can wait for the body to come to a natural end or explicitly invoke ExitThread. It doesn't require the handle of the thread to quit, so it works on the current thread, and if you call it from within the Visual Basic code, you'll exit Visual Basic too! The only safe place where you can call ExitThread is inside the body of the thread.

How do you specify the body of a thread if you can't do so using native Visual Basic? The solution is to place the routines that contain the body of a thread into a separate DLL, and to call those routines from Visual Basic using CreateThread. Visual Basic does not have a native function that will obtain the address of these routines that CreateThread needs, so you use the API function GetProcAddress instead. GetProcAddress requires the name of the module that contains the function and the function's name or ordinal number.

Thus, if you want to create another thread from your VB program, start by writing a DLL with routines that run concurrently with the rest of the program. Figure 2 illustrates how the primary and secondary threads run in VB.

Using the SDK

First, I'll present an approach that separates the threaded routines in a DLL implemented in C. My Visual Basic application calls these routines using direct calls to the WIndows 95 API. I will also examine the characteristics of the new animation controls that come with the Windows 95 Common Controls library. Finally, I'll encapsulate the calls to the multithreading functions in the API in an OCX control. This will make implementing multithreading in Visual Basic simpler.

For the sake of illustration, I'll enter characters into a text box with the first thread, while starting a second thread that plays a .AVI file in the background. The necessary steps to accomplish this include:

1.Obtaining the instance of the module.

2.Creating an animated control to play the clip.

3.Loading the DLL containing the functions.

To reproduce a .AVI clip, you just create the control, then open and play the file. The Animate_* macros in commctrl.h make this a straightforward process (the source code for the DLL is available electronically).

The instance of the module is a parameter required for creating the animated control. You should declare and use GetWindowLong and CreateWindowEx for the first two steps; see Example 2. The animated controls belong to a class called SysAnimate32, and the sample creates one of them inside a picture box. To get the address of a function exported by a DLL, you need a handle to the library itself. LoadLibrary does this for you. All this needs to be done during the loading of the form. At this point, the example is ready to start a new thread that plays the .AVI clip. By clicking on the Go button, you will: 1. Get the address of a function called "Go" from a library called "theLib.dll;" and 2. Create the thread.

The address of the function is stored in a Long variable and passed as the third argument of CreateThread. Be sure to indicate ByVal vbNullString in place of the attributes for the security of the thread and to dereference the address you pass as the fourth argument. It should be a void pointer to the parameters for the main thread routine. In this example, anyway, I only have a Long value to pass, whereas the declaration requires an argument by reference. So insert the ByVal keyword before the variable name. My library exports two functions, Go and Quit. Go is intended to open and play an .AVI file, whose name is hard coded to sample.avi. Quit stops the playing that is going on in the control. Calling Quit sends a stop message to the animation control causing the thread to end.

Using an OCX

With tools such as C++ language, MFC, and ControlWizard, you can create an OCX that helps you to support multithreading. The OCX, called "thread.ocx," is invisible at run time and exposes two methods (Create, Terminate) and three attributes (Library, Function, Params). Its only purpose is to provide a simple interface for calling a function from a DLL, using it as the body of a new thread. The Create method loads the library specified in the Library data member, gets the address of a routine called Function, and makes a call to CreateThread specifying the content of Params as the argument the new thread will receive. The OCX's Create returns the handle to the thread; see Example 3.

Since the OCX is invisible at run time, you can add it anywhere in your form. The prototype of the CreateThread API lets you specify just a LPVOID parameter as the arguments of the thread. To pass it more data, just gather all the data in a single, user-defined structure. Don't forget to call the OCX's Terminate to unload the library from memory (despite the name, the method won't do anything else).

Thread Monitoring

For debugging, you can use PView or Spy++ (both of which come with Visual C++). They let you snoop around the active processes and threads. If you run my samples from within the VB4 environment, you'll see that the number of threads increases by one, passing from two to three. The first two threads are the VB primary component (priority 8, normal) and a secondary thread with a highest priority (23, critical), probably used for checking the syntax of your code. You can use SetThreadPriority to modify the priority of your thread.

To terminate a thread there is a brute-force approach, too. If you call TerminateThread specifying the handle of the thread you want to kill, you effectively destroy the thread but have no stack deallocated, no cleanup code, and no related DLL notified of it! What does it mean? Try modifying the VB code responding to the Stop button, turn off the line that quits the animation, add an explicit call to TerminateThread, and start the program. You'll notice that the Stop button is no longer able to quit the .AVI. Why? It's just because the thread refers to an external DLL (comctl32.dll for playing the clip) and killing the thread does not affect any code other than the thread itself.

Conclusion

Visual Basic is an environment thought to make Windows programming easier and faster. It supports almost all the features offered by Windows 95, but not multithreading. A workaround is possible, but since multithreading is not a built-in characteristic of Visual Basic, you cannot expect it to be smoothly implemented. However, the only serious drawback is that you need to isolate the body of the threads in external DLLs and load them by means of a handful of APIs. Once you know this, multithreading in Visual Basic is no more difficult than making any other API call. In addition, the OCX provided with the article should hide the most of the SDK programming details.

Be careful with data manipulated by threads your Visual Basic application creates, since that data may be accessed concurrently by different pieces of the same process and in unpredictable order. If your threads need synchronization, use special data structures to control how the threads share the process resources. You can, for instance, use semaphores and mutexes to limit the number of the threads accessing a resource, isolate into critical sections the code that must execute without interruption, and generate events to let a thread sleep and awake when another thread signals that an expected situation occurred. There are several functions in the Windows API that let you use semaphores, mutexes, critical sections, and events, including WaitForSingleObject, WaitForMultipleObjects, EnterCriticalSection, CreateSemaphore, and CreateMutex.

Figure 1: How threads run in VB4.

Figure 2: Running the sample program; OCX version.

Example 1: Typical call to CreateThread.

pfn = GetProcAddress( m_hModule, m_function );
m_hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)pfn, 
            (LPVOID)m_params, 0, &dwThreadId );

Example 2: Using GetWindowLong and CreateWindowEx.

hInst = GetWindowLong(hWnd, GWL_HINSTANCE)
hwndAni = CreateWindowEx(0, "SysAnimate32", "", 
          ACS_TRANSPARENT Or WS_CHILD Or WS_VISIBLE, 0, 0, 0, 0, hWnd,
          1, hInst, vbNull)

Example 3: Create returns the handle to the thread.

Thread1.Library = "thelib.dll"
Thread1.Function = "Go"
Thread1.Params = hwndAni
hThread = Thread1.Create


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.