gajits.com - shareware, freeware and Delphi™ stuff

 

Win32 Tutorial

Home Products Download Purchase Delphi stuff  

EctoSet Modeller
EctoSet Modeller - making UML™ affordable

Win32

A presentation to ADUG (Australian Delphi User Group):
by
Graeme Chandler

http://www.gajits.com
Sep 20, 1999

Contents:


What is Win32

Quite simply WIN32 is an API.

Some areas covered by the Win32 API:
 
 

Atoms Networks
Child Controls Pipes and mailslots
Clipboard manipulation Printing
Communications Processes and threads
Consoles Resources
Dynamic-link libraries Security
Event logging Services
Files Structured exception handling
Graphics drawing primitives System Information
Keyboard and mouse input Tape backup
Multimedia services Window management
Many of the Win32 functions are exported from Kernel32.dll. Delphi defines them in Windows.pas.

How many platforms implement Win32

The Win32 API is implemented on four platforms:

  • Win32s
  • Windows NT and above
  • Windows 95/98
  • Windows CE

Win32s is basically a set of dynamic-link libraries and a virtual device driver that adds the Win32 API to the 16 bit Windows 3.x system.

A lot of thunking goes on to support Win32s on top of 16 bit Windows 3.x.

Windows NT was the second Win32 platform from Microsoft. Windows NT native applications are Win32 applications which gives them the power, robustness and speed provided by the Win32API.

Windows 95/98: Windows 95 doesnít contain the full implementation of the Win32 API as found in Windows NT.

Why? In part so that Microsoft could fit Windows 95 in a 4MB machine. Makes sense! Otherwise Windows 95 would require the same sort of grunt that Windows NT does.

What doesnít Windows 95/98 support? Some of the Win32:

  • asynchronous file I/O functions
  • debugging functions
  • registry functions
  • security functions
  • event logging functions

The functions exist but they have restricted implementations.

Windows CE: This OS was created to fit the needs of new hardware devices Ė handheld computing devices and the like. Surprisingly most of the Win32 API applies to Windows CE. Windows CE is a surprisingly powerful OS, supporting  memory-mapped files, structured exception handling, DLLís, the registry, preemptive scheduling of threads and much more.

Objects in the Win32 World

Objects exist in the Win32 world, but theyíre not the OO or COM objects with which most of us are familiar. In fact, there is even a distinction between the objects of the Win32s API (16 bit) and 32 bit Win32.

There are basically two types of objects in Win32: kernel objects and GDI/USER objects.

Kernel Objects

Win32 developers create, open and manipulate kernel objects regularly. Whether we know it or not, most Delphi developers are utilizing Win32 objects in their daily programing. The system creates and manipulates several types of kernel objects including:
 

Event objects Mailslot objects Process objects
File-mapping objects Mutex objects Semaphore objects
File objects Pipe objects Thread objects

There are sets of Win32 functions (API calls) to create and manage these objects. For example, CreateFileMapping causes the system to create a file mapping object.

Like most things in Windows, a kernel object is really just a block of memory - of course the block of memory has a specific data structure for a given kernel object. And, the memory block is only directly accessible by the kernel.

These data structures have members which are common to all object types (object name, security descriptor, usage count and so on), but most member are specific to particular object types. For example, a process object has a process ID, base priority and an exit code. File objects have a byte offset, a sharing mode and an open mode.

Incidentally, the handles to kernel objects are process relative. If you were to pass a handle value to a thread in another process, API calls that this other process could make using your processís handle value would fail.

Processes

What is a process? Is it an application or an application instance?

A process is very much like a computer without an operating system. It has a lot of substance but doesn't do much without a little push. Just as a computer needs an operating system to make the collection of chips and peripherals do something useful, a process needs something more to make it process than just a collection of.executable code.

When a process is created by the operating system it is allocated one thread - its primary thread. The primary thread is responsible for executing the code within the context of the process that owns the thread. Naturally, the primary thread, via your executable code, may create additional threads, but it has only the one main thread.

Instance Handles

Some Win32 API functions require an applicationís instance handle, whereas others require a module handle. In 16 bit Windows (Win32s) there was a distinction between these two values. This is not true under Win32. Every process gets its own instance handle. In Delphi 4 this is the HInstance variable.

Usually, HInstance will be the module handle as well in Win32. However: HInstance will not be the module handle of the application for code thatís compiled into packages. In this case use MainInstance to refer to the host application and HInstance to refer to the module of the package in which your code resides.

Is there another instance of this process?

In 16 bit Windows, the Delphi variable HPrevInst could be used to identify whether another instance of the process (application) was running. Under Win32, since each process runs in its own 4GB address space and can't see other proceses, this variable is no longer of any use and always has a value of zero. Therefore we must use some other technique to prevent multiple instances of applications from running.

Usage Counting

As mentioned earlier, your process doesn't own the kernel objects it creates. The kernel does. This could mean that if your process terminates without tidying up after itself, the kenel object might not be destroyed. Usually it will be destroyed, but if another process is using a kernel object created by your process, the kernel will not destroy the object until the other process has stopped using it. This is achieved via usage counting.

Using a Mutex

There are several different kinds of kernel objects. When a kernel object is created it exists in the address space of the process and that process gets a handle to that object. This handle canít be passed to another process or reused by the next process to access the same kernel object. However a second process can obtain its own handle to a kernel object that already exists by using an appropriate Win32 API function.

For example, the CreateMutex() function creates a named or unnamed mutex object and returns its handle. The OpenMutex() Win32 API function returns the handle to an existing named mutex object.

The following code demonstrates two ways of using calls to Mutex functions to prevent multiple instances of an application from existing.

1. Sample code using CreateMutex and WaitForSingleObject

Code in red indicates lines added to a standard project file to implement the mutex code that will prevent multiple instances of an application running.

program Project1;

uses
  Windows,
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

var
  hMutex: THandle;

begin
  hMutex := CreateMutex(nil, False, 'myUniqueName');
  if WaitForSingleObject(hMutex, 200) = Wait_Object_0 then begin
    Application.Initialize;
    Application.CreateForm(TForm1, Form1);
    Application.Run;
    ReleaseMutex(hMutex);
  end;
  CloseHandle(hMutex);
end.


2. Sample code using OpenMutex
 

program Project1;

uses
  Windows,
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

var
  hMutex: THandle;

begin
  hMutex := OpenMutex(MUTEX_ALL_ACCESS, false, 'myUniqueName');

  if hMutex <> 0 then begin
    CloseHandle(hMutex);
    exit;
  end;

  CreateMutxe(nil, true, 'myUniqueName');

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
  ReleaseMutex(hMutex);
  CloseHandle(hMutex);
end.


Why doesnít the exit code call ReleaseMutex? Because it doesnít own the mutex object. (A ReleaseMutex is not really necessary prior to a close but its a good habit to get into.) The first instance of the running application owns the mutex. But any other instance should close the handle to its reference to the mutex object.

The above code samples demonstrate that multiple processes can access the same kernel objects. This is achieved via usage counts.

GDI and User Objects

Objects in 16-bit Windows referred to entities that could be referenced by a handle. This didnít include kernel objects because they didnít exist under 16-bit Windows.

In 16-bit Windows there are two types of objects; those stored in the GDI and USER local heaps and those allocated from the global heap. Examples of GDI objects are brushes, pens, fonts, palettes, bitmaps and regions. Examples of user objects are windows, window classes, atoms and menus.

There is a direct relationship between an object and its handle. An object's handle is a selector that, when converted into a pointer, points to a data structure describing an object. This structure exists in either the GDI or USER default data segment. Additionally, a handle for an object referring to the global heap is a selector to the global memory segment.

A consequence of this particular design is that objects in 16-bit Windows are shareable. Think of the consequences.

Win32 deals with GDI and USER objects a bit differently. Techniques used in the 16 bit world to access some of these objects might not apply.

To begin with, Win32 introduces kernel objects. Also the implementation of GDI and USER objects is different under Win32 than under 16-bit Windows. In Win32, in addition to GDI objects being stored in the address space of the running application, each process gets its own handle table in which to store kernel objects.

The two books I used to prepare this presentation conflicted on the types of objects they claimed were stored in the handle table. One said GDI Objects and the other said Kernel objects!

Closing Kernel Object Handles

What happens if you forget to call CloseHandle()? Will there be a memory leak?

It is possible for a process to leak resources (such as kernel objects) while the process runs. However, when the process terminates, the operating system ensures that any and all resources used by the process are freed Ė this is guaranteed. For kernel objects, the system performs the following: When your process terminates, the system automatically scans the processís handle table. If the table has any valid entries (objects that you didnít close before terminating), the system closes these object handles for you. If the usage count of any of these objects goes to zero, the kernel destroys the object.

Thread Synchronization

Win32 provides a variety of ways to synchronize threads. The main ones are: critical sections, mutexes, semaphores, events and waitable timers. For the most part all synchronization objects behave similary. Their differences, however, often make one type of object more suitable for a particular task than another.

In general a thread synchronizes itself with another thread by putting itself to sleep. Just before it puts itself to sleep, however, it tells the system what special event has to occur for the thread to resume execution. The OS remains aware of the threadís request and watches to see whether and when this special event occurs. When it does occur, the thread will again be eligible to be scheduled to a CPU and will continue its execution Ė the thread has now synchronized its execution with the special event.

Critical Sections

A critical section is a small section of code that requires exclusive access to some shared data before the code can execute. Of all the thread synchronization techniques, critical sections are the simplest to use, but they can only be used to synchronize threads within a single process.

EnterCriticalSection (TryEnterCriticalSection Ė NT only)

The EnterCriticalSection function waits for ownership of the specified critical section object. The function returns when the calling thread is granted ownership.
 

var
  CS: TRTLCriticalSection;

procedure TDoStuffThread.Execute;
var
  i: Integer;
begin
  OnTerminate := Form1.ThreadsDone;
  EnterCriticalSection(CS);           // CS begins here
  for i := 1 to MaxSize do
  begin
    GlobalArray[i] := GetNextNumber;  // set array element
    Sleep(5);                         // let thread intertwine
  end;
  LeaveCriticalSection(CS);           // CS ends here
end;

procedure TForm1.ThreadsDone(Sender: TObject);
var
  i: Integer;
begin
  inc(DoneFlags);
  if DoneFlags = 2 then
  begin // make sure both threads finished
    for i := 1 to MaxSize do
      { fill listbox with array contents }
      Listbox1.Items.Add(IntToStr(GlobalArray[i]));
    DeleteCriticalSection(CS);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  DoneFlags := 0;
  InitializeCriticalSection(CS);
  TDoStuffThread.Create(False);  // create threads
  TDoStuffThread.Create(False);
end;


WaitForSingleObject

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 or objects whose signaled state caused the function to return. For example, the count of a semaphore object is decreased by one.

Mutexes

Mutexes are very much like critical sections except that they can be used across process boundaries (multiple processes). Mutexes can also be given unique names with which to identify them.

The state of a mutex object is signaled when it is not owned by any thread.
 

var
  hMutex: THandle = 0;

procedure TDoStuffThread.Execute;
var
  i: Integer;
begin
  FreeOnTerminate := True;
  OnTerminate := Form1.ThreadsDone;
  if WaitForSingleObject(hMutex, INFINITE) = WAIT_OBJECT_0 then // signaled
  begin
    for i := 1 to MaxSize do
    begin
      GlobalArray[i] := GetNextNumber;  // set array element
      Sleep(5);                         // let thread intertwine
    end;
  end;
  ReleaseMutex(hMutex);
end;

procedure TForm1.ThreadsDone(Sender: TObject);
var
  i: Integer;
begin
  Inc(DoneFlags);
  if DoneFlags = 2 then    // make sure both threads finished
  begin
   for i := 1 to MaxSize do
      { fill listbox with array contents }
      Listbox1.Items.Add(IntToStr(GlobalArray[i]));
    CloseHandle(hMutex);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  DoneFlags := 0;
  hMutex := CreateMutex(nil, False, nil);
  TDoStuffThread.Create(False);  // create threads
  TDoStuffThread.Create(False);
end;

Semaphores

Another technique for thread synchronization involves using semaphore API objects. Semaphores build on the functionality of mutexes while adding one important feature. They offer the capability of resource counting so that a pre-determined number of threads can enter synchronized pieces of code at one time.

For example, lets say that a computer has three serial ports. No more than three threads can use the serial ports at any given time; each port can be assigned to one thread. This situation provides a perfect opportunity to use a semaphore. To monitor serial port usage, you can create a semaphore with a count of 3 Ė one for each serial port. A semaphore is signaled when its resource count is greater than 0 and non-signaled when the count is equal to 0. (The count can never be less than 0.) Every time a thread calls WaitForSingleObject and passes the handle of a semaphore, the system checks whether the resource count for the semaphore is greater than 0. If it is, the system decrements the resource count and wakes the thread. If the resource count is 0 when the thread calls WaitForSingleObject, the system puts the thread to sleep until another thread releases the semaphore (increments the resource count).
 

var
  hSem: THandle = 0;

procedure TDoStuffThread.Execute;
var
  i: Integer;
  WaitReturn: DWORD;
begin
  OnTerminate := Form1.ThreadsDone;
  WaitReturn := WaitForSingleObject(hSem, INFINITE); // signaled
  if WaitReturn = WAIT_OBJECT_0 then
  begin
    for i := 1 to MaxSize do
    begin
      GlobalArray[i] := GetNextNumber;  // set array element
      Sleep(5);                         // let thread intertwine
    end;
  end;
  ReleaseSemaphore(hSem, 1, nil);
end;

procedure TForm1.ThreadsDone(Sender: TObject);
var
  i: Integer;
begin
  Inc(DoneFlags);
  if DoneFlags = 2 then      // make sure both threads finished
  begin
    for i := 1 to MaxSize do
      { fill listbox with array contents }
      Listbox1.Items.Add(IntToStr(GlobalArray[i]));
    CloseHandle(hSem);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  DoneFlags := 0
  hSem := CreateSemaphore(nil, 1, 1, nil);
  TDoStuffThread.Create(False);  // create threads
  TDoStuffThread.Create(False);
end;


The state of a semaphore object is signaled when its count is greater than zero and nonsignaled when its count is equal to zero. The process that calls the CreateSemaphore function specifies the semaphore's initial count (parameter 2) and its maximum count (parameter 3). Each time a waiting thread is released because of the semaphore's signaled state, the count of the semaphore is decreased by one.

Name Space for Synchronization Objects

If the Name parameter of an event, mutex, semaphore or file-mapping matches the name of an existing event, semaphore, or file-mapping object, the function fails and the GetLastError function returns ERROR_INVALID_HANDLE. This occurs because event, mutex, semaphore, and file-mapping objects share the same name space. The same name cannot be given to a mutex and a sepmaphore simultaneously, for example.

Synchronization objects and Win32s

Of the above three mechanisms of thread synchronization, only Critical Sections are available on Win32s. Can you think why?
 

Sources:

  • Jeffrey Richter: Advanced Windows (Third Edition)
  • Steve Teixeira & Xavier Pacheco: Delphi 4 Developerís Guide
  • Win32.hlp

What's New FAQ Contact Subscribe

gajits.com - shareware, freeware and Delphi™ stuff


Last Modified 24 May 2002
Copyright © 1998-2004 - Coogara Consulting