Midterm System
Midterm System
Windows History
Keeping the demand of graphical user interface, Microsoft developed its first version Windows
3.1. In this version, DOS Kernel and FAT based file systems were used.
After that in the 1990s, certain new versions of Windows named Windows 95, 97 and 98 were
introduced supporting the 32-bit architecture of Intel’s processors.
Later on Windows NT versions were introduced supporting a file system based on new
technology called NTFS. Its security and file system was better than the previous versions.
Windows Server 2008 OS was developed for professional use to manage enterprise and server
applications. Support for multi-core technology and 64-bit applications was provided in this OS.
Other Windows versions supporting 32-bit, 64-bit architecture, multi-core and multiprocessing
were also introduced including Windows XP, Windows Vista, Windows 7, 8 and Windows 10.
Due to its dominance role, certain applications and software development tools are available in
the market that can easily integrate with Windows OS and can develop windows applications
ranging from small scale to enterprise level.
One of the key features of Windows OS is its rich GUI that makes its use very convenient. This
interface can be easily customized according to the local setup. The size, color and visibility of
graphical interface objects can also be changed by the user.
Compared to other operating systems, certain modern features exist in Windows due to which
most of the developers develop their applications for Windows targeting the huge market of
Windows.
Open Source Software is a software that is publically available with its source code to use,
modify and distribute with original rights. It is developed by the community rather than a single
company or vendor. In contrast, proprietary software is copyrighted and only available to use
under a license.
· As Windows components are provided and updated only by a single vendor, its
implementation remains uniform throughout the world. Further, extensions in Window
components or APIs are only vendor-specific and so no non-standard extension is possible
except for platform differences.
Windows also support various types of hardware platforms like open systems.
● In Windows OS, all the system resources including processes, threads, memory, pipes,
DLL etc. are represented by objects which are identified and referenced by a handle.
These objects cannot be directly accessed. In case, if any application approaches to
access these objects directly, Windows throws an appropriate exception. The only way
to access and operate on these objects is a set of APIs provided by Windows. Several
APIs can be related to a single object to manipulate it differently.
● A long list of parameters is associated with each API where each parameter has its own
significance but only few parameters are specified for a specific operation.
● To perform the task of multitasking and multi-threading efficiently, Windows provides a
number of synchronization constructs to arbitrate among the resources.
● The names of Windows APIs are long and descriptive for its proper and convenient use.
● Some pre-defined data types required for Windows APIs are:
■ BOOL (for storing a single logical value)
■ HANDLE (a handle for object)
■ LPTSTR (a string pointer)
■ DWORD (32-bit unsigned integer)
● Windows Data types avoid the pointer operator (*).
● Some lowercase prefix letters with variable names are used to identify the type of
variable. This notation is called Hungarian notation. For example, in the variable name
lpszFilename, ‘lpsz’ is Hungarian notation representing a long pointer to zero
terminated string.
● windows.h is a header file including all the APIs prototypes and data types
Topic 6: 32-Bit and 64-Bit Source Code Portability
Windows keeps two versions of each API, one for 32-bit and other for 64-bit. A 32-bit code can
be run on 64-bit hardware but will be unable to exploit some features of 64-bit like accessing
large disk space or using large pointer or 64-bit operation.
Latest versions of Windows support both 32 and 64-bit architectures by keeping two versions of
each API, one for 32-bit and other for 64-bit.
Interoperability of 32 and 64-bit: A single source code can be built for 32-bit as well as 64-bit
versions. To decide whether executable code of 32 or 64-bit is generated by the compiler at
runtime, it depends on its settings or configuration. Further, to decide which version of API is
used, it is also based on the compiler’s configuration.
A 32-bit code can run on 64-bit hardware successfully but will be unable to use some features
of 64-bit like large disk space, large pointer etc.
A source code developed for 64-bit architecture cannot easily run on a 32-bit machine. For this
purpose, re-compilation of the program is required and suitable configuration is made in the
compiler to generate a 32-bit executable code.
Windows provides a set of built-in APIs to perform I/O operations. A related API with specific
parameters is invoked for the concerned resource and I/O operation is performed.
Similarly, certain C/C++ standard functions are available to perform I/O operations. For example,
fopen(), fclose(), fread(), fwrite() etc. are C functions that can be used to perform I/O
operations related to files.
Standard C functions can be used inside the source code to run on Windows platform because
Windows has system calls at low level to support C/C++ functions for I/O operations.
In case, if portability is not focused and required to avail the advanced Windows features, then
it is preferred to use the Windows APIs.
if(argc!=3) {
printf(“Usage: cp file1 file2\n”);
return 1; }
inFile=fopen(argv[1], “rb”);
if(inFile==NULL) {
perror(argv[1]);
return 2;
}
outFile=fopen(argv[2], “wb”);
if(outFile==NULL) {
perror(argv[2]);
return 3; }
This program is used to copy one file to another using C standard functions. In this program, a
buffer of size 256 bytes is used in which the chunks of file are copied one by one.
The source file is opened in read binary mode and the destination file in write binary mode
using the C fopen() function.
If both are successfully opened, then a file is read inside a loop chunk by chunk using fread()
function and written onto the destination file using fwrite() function. After a few iterations, the
file will be written to the destination file and both files are closed.
if(argc !=3) {
fprintf(stderr, “Usage: cp file1 file2\n”);
return 1; }
lpwszFile1 = (LPTSTR)malloc(510);
lpwszFile2 = (LPTSTR)malloc(510);
iLen1 = MultiByteToWideChar(CP_ACP, 0, argv[1], -1, lpwszFile1, 510);
iLen2 = MultiByteToWideChar(CP_ACP, 0, argv[2], -1, lpwszFile2, 510);
hIn=CreateFile(lpwszFile1, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hIn==INVALID_HANDLE_VALUE) {
fprintf(stderr, “Cannot open input file. Error: %x\n”, GetLastError());
return 2; }
Numerous Windows functions are used to perform various tasks at low level. However,
Windows has a set of Convenience functions that combine several functions to perform a
common task. In most cases, these functions improve the performance because several tasks
are performed by a single function.
For example, CopyFile() is a convenience function that replaces the algorithms used for creating,
opening, reading and writing one file to another.
Program 1 - 3
#include<stdio.h>
#include<windows.h>
#define BUF_SIZE 256
LPWSTR lpwszFile1, lpwszFile2;
INT iLen1, iLen2;
CreateFile() API with a list of parameters is used to open or create a new file. Its return type is
HANDLE to an open file object in case of successful opening or creation. The parameters are:
dwDesiredAccess: It is a 32-bit double word which specifies the GENERIC_READ and WRITE
access.
Syntax:
● If the file is not opened in concurrent mode, then ReadFile() starts reading from the
current position.
● If the current location is End of File, then no Errors occur and *lpNumberOfBytesRead is
set to zero
● The function returns FALSE if it fails in case any of the parameter is invalid
Parameters
Syntax:
To write through the current size of file, the file must be opened with
FILE_FLAG_WRITE_THROUGH option
Lecture 15: Closing a File
After opening & using a file, it is required to close and invalidate the file handles in order to
release the system resources.
The CloseFile() API is used to close a file. A file handle is passed as parameter to this API as a
result of which the API will return True or False value. If the operation is successful, then it
returns True value. In case if the handle is already invalid, then it will return False value.
While writing a new code or enhancing an existing one, a programmer can adapt any of the
following strategies based on requirements.
<everything.h> includes all the header files required for typical Windows program
Example:
#include<everything.h>
VOID ReportError(LPCTSTR userMessage, DWORD exitCode, BOOL printErrorMessage)
{
DWORD eMsgLen, errNum,=GetLastError();
LPTSTR lpvSysMsg;
_ftprintf(stderr, _T(“%s\n”), userMessage)
if (printErrorMessage)
{
eMsgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FORM_SYSTEM, NULL, errNum, MAKELANGID(LANG_NEUTRAL,
SUBLANG_DEFAULT), (LPTSTR) &lpvSysMsg, 0, NULL);
if (eMsgLen > 0)
{
_fprintf(stderr, _T(“%s\n”), lpvSysMsg);
}
else
{
_ftprintf (stderr, _T(“Last Error Number; %d\n”), errNum);
}
if (lpvSysMsg != NULL)
LocalFree(lpvSysMsg);
}
if (exitCode>0)
{
ExitProcess(exitCode);
return;
}
}
Here this function ReportError() receives three parameters. Inside this function, the result of
GetLast() function is stored in the variable, errNum. A generic string variable “lpvSysMsg” is
declared for storing the error message.
If the user likes to print the error message, then the error code will first format and convert it
into a string using the FormatMessage() function.
If message length is greater than 0, then it will print the message, otherwise will print the error
code and memory is deallocated.
Topic 20:
● Enviornment.h
● Everything.h
#if(WIN32_WINNT>=0x600)
#else
#endif
#endif
#ifdef UNICODE
#define_UNICODE
#endif
#ifndef UNICODE
#undef_UNICODE
#endif
Everything.h includes all the header file that will be typically required for all the
subsequent window programs.
#include “ENVIORNMENT.h
#include<stdlib.h>
#include “support.h”
#include _MT
#endif
#include “Everything.h”
_ftprint(stderr,_t(“%s\n”},userMessage};
If (printErrorMessage)
eMsgLen= FormatMessage{FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMATE_MESSAGE_FROM_SYSTEM,
NULL,errNUM,MAKELANGID{LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&lpsSysMSg,0,NULL);
If(eMsgLen>0)
_ftprint(stder,T(“%s\n”|,lpvSysMsg));
Else
_ftprint{stderr,_T{“LastErrorNumber,%d\n”},errNum};
If (lpvSysMsg!=NULL) LOCALFree(lpvSysMsg);
If(exitCode>0)
ExitProcess(exitCode);
return;
}
Topic 21:- Standard IO devices:
Reading Material:
● Input
● Output
● Error
1. STD_INPUT_HANDLE
2. STD_OUPUT_HANDLE
3. STD_ERROR_HANDLE
Operating system have also the concept of redirection using the given API
It also return true if calls succeed and return false in case of fail.
Topic 22: Copying multiple files using windows API
Reading Content:
We will see in this module how to display files using cosonle APIs of windows,
in other words display file on screen is actually copying file on console. For this
we made utility function “options()”. We will also use this function further. This
function take a variable list of parameters and use to parse these variable list.
Basically we specify the list of parameters of a program on command prompt,
there are number of options in it. Option () is use to parse these options. It
identify the “-“ prefix and check all the possible options, and set the flag
against the set options.
#include “Everything.h”
#include <stdarg.h>
DWORD Options (int argc, LPCTSTR argv [], LPCTSTR OptStr, …) /*… show the
parameters list are variables */
Va_list pFlagList;
LPBOOL pFlag;
*pFlag= False;
For (iArg= 1; !(*(pFlag) && iArg <argc &&argv[iARG] [0]== _T(‘-‘); iArg++)
*pFlag = _memtchr (argv [iArg], OptStr [iFlag], _tcslen (argv [iArg]))!=
NULL
iFlag++;
Va_end (pFlagList);
For (iArg= 1; !(*(pFlag) && iArg <argc &&argv[iARG] [0]== _T(‘-‘); iArg++);
Retrun iArg;
CatFile Function:
Return;
}
Topic 23 : Encrypting files
Reading Content:
Encryption is a very old technique, and roman empire use to encrypt secret
conversation in war days and they use Ceasar Cipher algorithm to encrypt. In
this method an alphabet is substituted by another alphabet placed n positions
forward in circular manner. The text that is changed using encryption method is
called Cipher text.
The text that we are going to encrypt is called plain text so it is denoted by P
and after encrypt we present it with C.
● C = (P + n) mod 256
This technique is not exactly cipher but little bit similar to cipher. Following is
the code of encrypting file
#include "Everything.h"
#include <io.h>
BOOL cci_f (LPCTSTR, LPCTSTR, DWORD);
if (argc != 4)
return 0;
if (hOut == INVALID_HANDLE_VALUE) {
CloseHandle(hIn);
return FALSE;
}
while (writeOK && ReadFile (hIn, buffer, BUF_SIZE, &nIn, NULL) && nIn > 0) {
CloseHandle (hIn);
CloseHandle (hOut);
return writeOK;
Reading Content:
We will see in this module the types of API for file management. Windows
provides lots of function for file and directory management. These functions
are pretty straightforward and easy to use.
• Delete
• Copy
• Rename
Delete
Delete function will help to delete the file on a given path. For deleting file
the following API is used.
Returns TRUE if the file at the given valid file path is deleted
Copy
Hard Copy
Move
DWORD dwFlags );
We will discuss the functions which we can use for directory management. We
will do different directory operation like create directory , remove directory and
move directory.
LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
BOOL RemoveDirectory(
LPCSTR lpPathName
);
BOOL SetCurrentDirectory(
LPCTSTR lpPathName
);
DWORD GetCurrentDirectory(
DWORD nBufferLength,
LPTSTR lpBuffer
);
In the previous module we see the certain APIs , which perform input/output
operations on console, now we use these APIs. We create two types of utility
functions, one is help us to display string on console and other is pass some
message to user and also take input from users. Following are names and
description of these functions.
#include "Everything.h"
#include <stdarg.h>
LPCTSTR pMsg;
va_end (pMsgList);
return FALSE;
va_end (pMsgList);
return TRUE;
BOOL success;
if (success)
else
CloseHandle (hIn);
CloseHandle (hOut);
return success;
Topic 28:
In this module we will see a small code which will use get current directory.
#include "Everything.h"
DWORD lenCurDir;
return 0;
Topic 29:
If we see historically, there were some file system which was of 12-bit after that
we have 32-bit system and still somewhere 32-bit file system are used. FAT
based system allowed a maximum file size of 232 bytes which is 4GB. NTFS
theoretically provides the file size limit of 264 which is very huge.
Files of such proportion are called huge files. Although for most of the
application 32 bit file space is sufficient. However due to rapid technological
changes leading to increased disk spaces its useful to know how to deal with 64
bit huge file spaces and windows facilitate with some API’s that support 64-bit
file system.
Topic 30:
Whenever a file is opened using CreateFile() the file pointer is placed at the start
of file. The file pointer changes as ReadFile() or WriteFile() operations are
performed. Every subsequent read/write operation is performed at the current
file pointer position.
SetFilePointer()
DWORD SetFilePointer(
HANDLE hFile,
DWORD dwMoveMethod
);
hFile
lDistanceToMove
The low order 32-bits of a signed value that specifies the number of bytes to
move the file pointer.
lpDistanceToMoveHigh
A pointer to the high order 32-bits of the signed 64-bit distance to move.
dwMoveMethod
FILE_BEGIN // file pointer move number of bytes w.r.t the start of file
Topic 31:
For large files that may have size 264 , we need to understand the 64 bit
arithmetic. To facilitate 64-bit integer arithmetic windows provide a union
LARGE_INTEGER. This union has structure for dealing with lower and higher
double words Moreover it also has a field to deal with whole quadword of type
LONGLONG.
struct {
DWORD LowPart;
LONG HighPart;
};
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER;
Extension of SetFilePointerEx
BOOL SetFilePointerEx(
HANDLE hFile,
LARGE_INTEGER liDistanceToMove,
PLARGE_INTEGER lpNewFilePointer,
DWORD dwMoveMethod
);
hFile
A handle to the file. The file handle must have been created with the
GENERIC_READ or GENERIC_WRITE access right
liDistanceToMove
lpNewFilePointer
A pointer to a variable to receive the new file pointer.
dwMoveMethod
FILE_BEGIN
FILE_CURRENT
FILE_END
Topic 32:
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
Implementation:
…..
Topic 33:
One method of getting file size is already exist and that is to open a file first
using create file, once file is open the file pointer is pointing to the first byte
then we move file pointer to the end of file (eof). So file pointer is move from
starting to end of file is give use the size of the file. Windows also provides an
API to get file size GetFileSizeEx()
GetFileSizeEx()
BOOL GetFileSizeEx(
HANDLE hFile,
PLARGE_INTEGER lpFileSize
);
hFile
A handle to the file. The handle must have been created with the
FILE_READ_ATTRIBUTES access right
lpFileSize
Windows also give option to set the size. The file size can also be changed,
reducing the file size truncate data. Increasing the file size can be useful where
the size of file is expected to grow. We use SetEndOfFileEx() to change the file
size.
Topic 34:
In this topic we discuss the example of creates a file with a capacity of specified
records. The file has a header followed by equal size records. The feature of
this example is, user can modify any record randomly and get the total count of
records in the file.
#include "Everything.h"
SYSTEMTIME recordCreationTime;
SYSTEMTIME recordLastRefernceTime;
SYSTEMTIME recordUpdateTime;
TCHAR dataString[STRING_SIZE];
} RECORD;
DWORD numRecords;
DWORD numNonEmptyRecords;
} HEADER;
HANDLE hFile;
LARGE_INTEGER currentPtr;
RECORD record;
TCHAR string[STRING_SIZE], command, extra;
SYSTEMTIME currentTime;
if (argc < 2)
if (hFile == INVALID_HANDLE_VALUE)
header.numRecords = _ttoi(argv[2]);
currentPtr.QuadPart = (LONGLONG)sizeof(RECORD) *
_ttoi(argv[2]) + sizeof(HEADER);
if (!SetEndOfFile(hFile))
return 0;
while (TRUE) {
continue;
currentPtr.QuadPart = (LONGLONG)recNo *
sizeof(RECORD) + sizeof(HEADER);
ov.Offset = currentPtr.LowPart;
ov.OffsetHigh = currentPtr.HighPart;
record.recordLastRefernceTime = currentTime;
if (record.referenceCount == 0) {
if (prompt) _tprintf (_T("record
Number %d is empty.\n"), recNo);
continue;
} else {
recNo, record.referenceCount);
record.referenceCount = 0;
header.numNonEmptyRecords--;
headerChange = TRUE;
recordChange = TRUE;
} else if (command == _T('w')) { /* Write the record, even if for the first time */
if (record.referenceCount == 0) {
record.recordCreationTime =
currentTime;
header.numNonEmptyRecords++;
headerChange = TRUE;
record.recordUpdateTime = currentTime;
record.referenceCount++;
recordChange = TRUE;
} else {
if (headerChange) {
argv[1], header.numNonEmptyRecords,
header.numRecords);
CloseHandle (hFile);
return 0;
}
Topic 35:
Windows provide a certain set of APIs for search files/folders within the hierarchical
structure of Directories/folders. These APIs include:
FindFirstFile() API
Where lpFileName represents the directory or path, and the filename. The name can
include wildcard characters, for example, an asteristk (*) or a question mark (?).
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
CHAR cFileName[Max_Path];
CHAR cAlternateFileName[14];
DWORD dwFileType;
DWORD dwCreatorType;
DWORD WFinderFlags;
};
FindNextFile() API:
Where hFindFile represents the search handle returned by a previous call to the
FindFirstFile or FindFirstFileEx function &
FindClose() API
Topic 36:
Certain other APIs are also used for getting the file attributes but these API need to
have an open file handle rather than scan a directory or use a filename.
GetFileTime() API
lpCreationTime is a pointer to a FILETIME structure to receive the data and time the file
or directory was created.
lpLastAccessTime is a pointer to a FILETIME structure to receive the data and time the
file or directory was last accessed.
lpLastWriteTime is a pointer to a FILETIME structure to receive the data and time the
file or directory was last written to truncated or overwritten.
FileTimeToSystemTime() API
SystemTimeToFileTime() API
CompareFileTime() API
It compares file times of two files. It returns -1 if less, 0 if equal and +1 if greater.
SetFileTime() API
It sets the three time of file. NULL used if the file time is not to be changed.
GetFileType API
GetFileAttributes() API
DWORD GetFileAttributes(LPCTSTR lpFileName);
Where lpFileName is the name of a file or directory. Its return value is:
Topic 37:
Windows provide the facility of creating temporary files for storing the intermediate
results. These files are assigned unique names in a directory with extension .tmp.
Certain APIs are used for creating temporary files. These include:
GetTempFileName API
Where lpPathName represents the directory path for the filename. The string cannot be
longer than 14 characters.
Topic 38:
We can get the attributes of a file, listing of files and can traverse the directory
structure using certain windows APIs.
An application called lsW is used for showing files and listing their attributes. It uses
two option switched that is –l and –R where –l option is used to list the attributes of
files in a folder and –R is used for recursive traversal through subfolders.
This application or program will work with a relative pathname; it will not work with
absolute pathname.
#include<everything.h>
DWORD FileType(LPWIN32_FIND_DATA);
int i, fileIndex;
DWORD pathLength;
/* parse the search pattern into two parts: the parent and the filename or wild card
expression. The filename is the longest suffix not containing a slash. The parent is the
remaining prefix with a slash. This is performed for all command line search pattern. If
no file is specified, use * as the search pattern */
UNIX Touch command changes file access and changes the time to
current system time.
SetFileTime() Sets the date and time that the specified file or directory
was created, last accessed, or last modified.
BOOL LockFileEx(
HANDLE hFile,
DWORD dwFlags,
DWORD dwReserved,
DWORD nNumberOfBytesToLockLow,
DWORD nNumberOfBytesToLockHigh,
LPPVERLAPPED lpOverlapped
)
If a program does not release a lock or holds the lock longer, other
programs will not be able to proceed and their performance will be
negatively impacted.
The registry contains two basic elements: keys and values. Registry
keys are container objects similar to folders. Registry values are non-
container objects similar to files. Keys may contain values and sub-
keys. Keys are referenced with a syntax similar to Windows' path
names, using backslashes to indicate levels of hierarchy. Keys must
have a case insensitive name without backslashes.
The hierarchy of registry keys can only be accessed from a known root
key handle (which is anonymous but whose effective value is a
constant numeric handle) that is mapped to the content of a registry
key pre-loaded by the kernel from a stored "hive", or to the content of
a sub-key within another root key, or mapped to a registered service
or DLL that provides access to its contained sub-keys and values.
HKEY_LOCAL_MACHINE or HKLM
HKEY_CURRENT_CONFIG or HKCC
HKEY_CLASSES_ROOT or HKCR
HKEY_CURRENT_USER or HKCU
HKEY_USERS or HKU
HKEY_PERFORMANCE_DATA (only in Windows NT, but invisible in
the Windows Registry Editor)[5]
HKEY_DYN_DATA (only in Windows 9x, and visible in the Windows
Registry Editor)
Service Manager stores many settings in the registry. You seldom have
to edit the registry yourself, because most of those settings are
derived from entries that you make in day-to-day use. However, some
changes to settings might occasionally be required. Service Manager
stores most registry values in the following locations:
HKEY_CURRENT_USER\Software\Microsoft\System
Center<version>\Service Manager\Console
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\System
Center<version>
Topic 47: Listing Registry Keys
You can show all items directly within a registry key by using Get-
ChildItem. Add the optional Force parameter to display hidden or
system items. For example, this command displays the items directly
within PowerShell drive HKCU:, which corresponds to the
HKEY_CURRENT_USER registry hive:
PowerShell
Get-ChildItem -Path HKCU:\ | Select-Object Name
You can also specify this registry path by specifying the registry
provider's name, followed by ::. The registry provider's full name is
Microsoft.PowerShell.Core\Registry, but this can be shortened to just
Registry. Any of the following commands will list the contents directly
under HKCU:.
PowerShell
Get-ChildItem -Path Registry::HKEY_CURRENT_USERGet-ChildItem -
Path Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USERGet-
ChildItem -Path Registry::HKCUGet-ChildItem -Path
Microsoft.PowerShell.Core\Registry::HKCUGet-ChildItem HKCU:
It's a good idea to use a function call in the filter expression whenever
filter needs to do anything complex. Evaluating the expression causes
execution of the function, in this case, Eval_Exception.
For a DLL function to be useful for an application exporting it, it must be declared as exportable. This can
be done by using .DEF file or by using __declspec (dllexport) storage modifier.
The build process will generate a .LIB file and .DLL file. The .LIB files will a stub for function calls while
DLL file will hold the actual code. Similarly the calling program should use the __declspec (dllimport)
Storage modifier.
#else
#endif
If you are using Visual C++ compiler then this task is automatically performed by the compiler. When
building the calling program you need to specify the .LIB file. When executing the calling program the
.DLL file must be placed in the same directory as the application. Following is the default DLL search safe
order for explicit and implicit linking.
Explicit linking requires the program to explicitly a specific DLL to be loaded or freed. Once the DLL is
loaded then the program obtains the address of the specific entry point and uses that address as a
pointer in function call.
The function is not declared in the calling program. Rather a pointer to a function is declared. The
functions required are:
HANDLE hFile,
DWORD dwFlags );
File name need not mention the extension. The file path must be valid. See MSDN for details of dwFlags
Once the DLL is loaded. The programmer needs to obtain an entry point (procedure address) into the
DLL. This is done using:
hModule is the module handle obtained for the DLL. lpProcName cannot b UNICODE. It is the name of
the function whose entry point is to be obtained. Its return type is FARPROC. This is the far address of
the function. A C type function pointer declaration can be easily used to invoke the function in DLL and
pass its parameters.
Previously, we have studied many file conversion functions. Some used memory mapped IO, some used
file operations. Some performed file conversion some performed file encryption.
Now, we take a look at how we can encapsulate these functions in a DLL and invoke them explicitly.
HMODULE hDLL;
FARPROC pcci;
if (argc < 5)
if (hDLL == NULL)
if (pcci == NULL)
DLL Entry point can be specified optionally when you create a DLL. The code at entry point executes
whenever a process attaches to the DLL. In case of implicit linking the DLL attaches at the time of
process start and detaches when process ends. In case of explicit linking the process attaches when
LoadLibrary()/LoadLibraryEx() is invoked. Also the process detaches when FreeLibrary() is called.
LoadLibraryEx() can also suppress the execution of entry point. Further, entry point is also invoked
whenever a thread attaches to the DLL.
BOOL DllMain(
HINSTANCE hDll,
DWORD Reason,
LPVOID lpReserved)
hDll corresponds to the handle returned by LoadLibrary(). lpReserved if NULL, represent explicit
attachment else it represents implicit attachment. Reason will have on the four values
DLL_PROCESS_ATTACH
DLL_THREAD_ATTACH
DLL_THREAD_DETACH
DLL_PROCESS_DETACH
Based on the value of Reason the programmer can decide whether to initialize or free resources. All the
calls to DllMain are serialized by the system. Serialization is critically important as DllMain() is supposed
to perform initialization operations for each thread. There should be no blocking calls, IO calls or wait
functions as they will indefinitely block other threads. A call to other DLLs from DllMain() cannot be
performed except for few exceptions. LoadLibrary() and LoadLibraryEx() should never be called from
DllMain() as it will create more DLL entry points. DisableThreadLibraryCalls() can be used to disable
thread attach/detach calls for a specified instance.
Complications
Many times a DLL is upgraded to provide more features and new symbols. Also, multiple processes
share a single implementation of DLL. This strength of DLL may also lead to some complications.
If the new DLL has changed interfaces this render problems for older programs that have not
been updated for newer version.
Application that require newer updated functionality may sometime link with older DLL version.
Version Management
One intuitive resolution to the problem can be by using different directory for each version. But there
are several solutions. Use DLL version number as part of the .DLL and .LIB file names eg. Utils_4_0.DLL
and Utils_4_0.LIB. Applications requiring DLLs can determine the version requirements and
subsequently access files with distinct filename using implicit or explicit linking.
Microsoft has introduced a concept of side by side DLLs or assemblies. This solution requires application
to declare its DLL requirements using XML.
Microsoft DLLs support a callback function that version information and more regarding a DLL. This
callback function can be used dynamically to query the information regarding DLL. The works as follows:
DLLVERSION *pdvi
)
Information regarding the DLL is placed in the DLLVERSION structure. It contains a field cbSize which is
the size of the structure,
dwMajorVersion
dwMinorVersion
dwBuilderNumber
dwPlatformID
Multitasking Systems
Multiprocessor systems
Thread as a unit of execution
Resources of Processes
From programmer’s perspective each process has resources such as one or more threads distinct virtual
address space. Although, processes can share memory and files but the process itself lie in an individual
virtual memory space.
Resources of Threads
Each thread is a unit within the process. Nevertheless, it has resources of its own. Stack: for procedure
calls, interrupts, exception handling, and auto variables. Thread Local Storage (TLS): An array like
collection of pointers enabling a thread to allocate storage to create its unique data environment. An
argument on stack unique for the created thread. A structure containing the context (internal registers
status) of the thread.
Topic 88: Process Creation
The most fundamental process management function in windows is CreateProcess(). Windows does not
have any structure that keeps account of the parent-child processes. The process that creates a child
process is considered as parent process. CreateProcess() has 10 parameters. It does not returns a
HANDLE. Rather two handles, one for process and one for thread is returned in a parameter struct.
It creates a process with single primary thread. Windows does not have any structure that keeps
account of the parent-child processes. The process that creates a child process is considered as parent
process.
CreateProcess() has 10 parameters. It does not return a HANDLE. Rather two handles, one for process
and one for thread is returned in a parameter struct. One must be very careful while closing both these
handles when they are not needed. Closing the thread handle does not terminate the thread it only
deletes the reference to the thread within the process.
LPPROCESS_INFORMATION lpProcessInformation );
lpApplicationName and lpCommandLine specify the program name and the command line parameters.
lpProcessAttributes and lpThreadAttributes points to the process and thread’s security attribute
structure. bInheritHandles specifies whether the new process inherits copies of the calling process’s
inheritable handles. dwCreationFlags combines several flags
CREATE_SUSPENDED: indicates that the primary thread is a suspended thread and will continue if the
process invokes ResumeThread()
DETACHED_PROCESS and CREATE_NEW_CONSOLE are mutual exclusive, First flag creates a process
without console and second one creates a process with console
CREATE_NEW_PROCESS_GROUP : Indicates that the new process is the root of new process group. All
processes in the same root group receive the control signal if they share the same console.
lpEnvironment : points to an environment block for the new process. If NULL, the process uses the
parent’s environment.
lpCurDir Specifies the drive and directory of new process. If NULL parent working directory is used.
lpStartupInfo : specifies the main window appearance and standard device handles for new programs.
lpProcInfo is the pointer to the structure containing handles and id for process and thread.
IDs are unique to processes for their entire lifetime. ID is invalidated when a process is destroyed.
Although, it may be reused by other newly created processes. Alternately, a process can have many
handles with different security attributes.
Some process management functions require handles while others require IDs. Handles are required for
general purpose handle based functions. Just like file handles and handles for other resources the
process handles need to be closed when not required.
The process obtains environment and other information from CreateProcess() call. Once the process has
been created then changing this information may have no effect on the child. For example the parent
process may change its working directory but it will not have effect on child unless the child changes its
own working directory. Processes are entirely independent.
Topic 90: Specifying the Executable Image and the Command Line
Executable Image
Either lpApplicationName or lpCommandLine specifies the executable image. Usually lpCommandLine is
used, in which case lpApplicationName is set to NULL. However if lpApplicationName is specified there
are rules governing it.
lpApplicationName
lpApplicationName should not be NULL. It should specify the full name of the application including the
path. Or use the current path in which case current drive and directory will be used. Include the
extension i.e. .EXE or .BAT in the file name. For long names quotes within the string are not required.
lpCommandLine
lpApplicationName should be NULL. Tokens within string are delimited by spaces. First token is the
program image name. If the name does not contain the path of the image then the following search
sequence is followed.
Command Line
A process can obtain its command line from the usual argv mechanism. Alternately, in windows it can
call GetCommandLine(). It’s important to know that command line is not a constant string. A program
can change the command line. It’s advisable that the program makes changes on a copy of command
line.
Mostly, a child process requires access to a resource referenced in parent by a handle. If the handle is
inheritable then the child can directly receive a copy of open handles in parent. For example standard
input and output handles are shared in this pattern. This inheriting of handles is accomplished in this
manner. The bInheritHandles flag in the CreateProcess() call determines whether the child process will
inherit copies of parent open handles. Also it is necessary to make individual handles inheritable. Use
the SECURITY_ATTRIBUTES structure to specify this. It has a flag bInheritFlag which should be set to
TRUE. Also the nLength should be set to sizeof(SECURITY_ATTRIBUTES)
We have only made the handles inheritable. However, we need to pass the actual values of handles to
the child process. Either this is accomplished through InterProcess Communication (IPC) Or it is passed
on the child process by setting it up in the STARTUPINFO struct. The latter is a preferred policy as it
allows IO redirection and no changes are required in child process. Another approach is to convert the
file handles into text and pass them through the command line to the child process.
The handles if are already inheritable then they will readily accessible to the child. Inherited handles are
distinct copies. Parent and child might be accessing same file with different file pointer. Each process
should close handles.
Topic 70: Binary Search Using Heaps
The example is formulated using two heaps. One is a node heap and the other is a data heap.
Node heap is used to build a tree, while data heap is used to store keys. Recursive functions are
used for allocating nodes and scanning nodes as tree is a recursive structure. Data in file is read
in a record and the key is used to lexically build a tree. The tree only contains the key entries.
The file is ultimately sorted by an In-order traversal of the tree.
#include "Everything.h"
#define KEY_SIZE 8
/* Structure definition for a tree node. */
typedef struct _TREENODE {
struct _TREENODE *Left, *Right;
TCHAR key[KEY_SIZE];
LPTSTR pData;
} TREENODE, *LPTNODE, **LPPTNODE;
#define NODE_SIZE sizeof (TREENODE)
#define NODE_HEAP_ISIZE 0x8000
#define DATA_HEAP_ISIZE 0x8000
#define MAX_DATA_LEN 0x1000
#define TKEY_SIZE KEY_SIZE * sizeof (TCHAR)
#define STATUS_FILE_ERROR 0xE0000001 // Customer exception
LPTNODE FillTree (HANDLE, HANDLE, HANDLE);
BOOL Scan (LPTNODE);
int KeyCompare (LPCTSTR, LPCTSTR), iFile; /* for access in exception handler */
BOOL InsertTree (LPPTNODE, LPTNODE);
int _tmain (int argc, LPTSTR argv[])
{
HANDLE hIn = INVALID_HANDLE_VALUE, hNode = NULL, hData =
NULL;
LPTNODE pRoot;
BOOL noPrint;
CHAR errorMessage[256];
int iFirstFile = Options (argc, argv, _T ("n"), &noPrint, NULL);
if (argc <= iFirstFile)
ReportError (_T ("Usage: sortBT [options] files"), 1, FALSE);
/* Process all files on the command line. */
for (iFile = iFirstFile; iFile < argc; iFile++) __try {
/* Open the input file. */
hIn = CreateFile (argv[iFile], GENERIC_READ, 0, NULL,
OPEN_EXISTING, 0, NULL);
if (hIn == INVALID_HANDLE_VALUE)
RaiseException (STATUS_FILE_ERROR, 0, 0, NULL);
__try {
/* Allocate the two growable heaps. */
hNode = HeapCreate (
HEAP_GENERATE_EXCEPTIONS | HEAP_NO_SERIALIZE, NODE_HEAP_ISIZE, 0);
hData = HeapCreate (
HEAP_GENERATE_EXCEPTIONS | HEAP_NO_SERIALIZE, DATA_HEAP_ISIZE, 0);
/* Process the input file, creating the tree. */
pRoot = FillTree (hIn, hNode, hData);
/* Display the tree in key order. */
if (!noPrint) {
_tprintf (_T ("Sorted file: %s\n"), argv[iFile]);
Scan (pRoot);
}
} __finally { /* Heaps and file handle are always closed */
/* Destroy the two heaps and data structures. */
if (hNode != NULL) HeapDestroy (hNode);
if (hData != NULL) HeapDestroy (hData);
hNode = NULL; hData = NULL;
if (hIn != INVALID_HANDLE_VALUE) CloseHandle (hIn);
hIn = INVALID_HANDLE_VALUE;
}
} /* End of main file processing loop and try block. */
/* Handle the exceptions that we can expect - Namely, file open error or out of
memory. */
__except ( (GetExceptionCode() == STATUS_FILE_ERROR ||
GetExceptionCode() == STATUS_NO_MEMORY)
? EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
_stprintf (errorMessage, _T("\n%s %s"), _T("sortBT error on file:"),
argv[iFile]);
ReportError (errorMessage, 0, TRUE);
}
return 0;
}
LPTNODE FillTree (HANDLE hIn, HANDLE hNode, HANDLE hData)
/* Scan the input file, creating a binary search tree in the
hNode heap with data pointers to the hData heap. */
/* Use the calling program's exception handler. */
{
LPTNODE pRoot = NULL, pNode;
DWORD nRead, i;
BOOL atCR;
TCHAR dataHold[MAX_DATA_LEN];
LPTSTR pString;
/* Open the input file. */
while (TRUE) {
pNode = HeapAlloc (hNode, HEAP_ZERO_MEMORY, NODE_SIZE);
pNode->pData = NULL;
(pNode->Left) = pNode->Right = NULL;
/* Read the key. Return if done. */
if (!ReadFile (hIn, pNode->key, TKEY_SIZE,
&nRead, NULL) || nRead != TKEY_SIZE)
/* Assume end of file on error. All records
must be just the right size */
return pRoot; /* Read the data until the end of line. */
atCR = FALSE; /* Last character was not a CR. */
for (i = 0; i < MAX_DATA_LEN; i++) {
ReadFile (hIn, &dataHold[i], TSIZE, &nRead, NULL);
if (atCR && dataHold[i] == LF) break;
atCR = (dataHold[i] == CR);
}
dataHold[i - 1] = _T('\0');
/* dataHold contains the data without the key.
Combine the key and the Data. */
pString = HeapAlloc (hData, HEAP_ZERO_MEMORY,
(SIZE_T)(KEY_SIZE + _tcslen (dataHold) + 1) *
TSIZE);
memcpy (pString, pNode->key, TKEY_SIZE);
pString[KEY_SIZE] = _T('\0');
_tcscat (pString, dataHold);
pNode->pData = pString;
/* Insert the new node into the search tree. */
InsertTree (&pRoot, pNode);
} /* End of while (TRUE) loop */
return NULL; /* Failure */
}
BOOL InsertTree (LPPTNODE ppRoot, LPTNODE pNode)
/* Insert the new node, pNode, into the binary search tree, pRoot. */
{
if (*ppRoot == NULL) {
*ppRoot = pNode;
return TRUE;
}
if (KeyCompare (pNode->key, (*ppRoot)->key) < 0)
InsertTree (&((*ppRoot)->Left), pNode);
Else
InsertTree (&((*ppRoot)->Right), pNode);
return TRUE;
}
int KeyCompare (LPCTSTR pKey1, LPCTSTR pKey2)
/* Compare two records of generic characters.
The key position and length are global variables. */
{
return _tcsncmp (pKey1, pKey2, KEY_SIZE);
}
static BOOL Scan (LPTNODE pNode)
/* Scan and print the contents of a binary tree. */
{
if (pNode == NULL)
return TRUE;
Scan (pNode->Left);
_tprintf (_T ("%s\n"), pNode->pData);
Scan (pNode->Right);
return TRUE;
}
Memory Mapping
Dynamic memory is allocated from the paging file. The paging file is controlled by the
Operating System’s (OS) virtual memory management system. Also the OS controls the
mapping of virtual address onto physical memory. Memory mapped files help to directly map
virtual memory space onto a normal file.
There is no need to invoke direct file Input Output (IO) operations. Any data structure placed in
file will be available for later use as well. It is convenient and efficient to use in-memory
algorithms for sorting, searching etc. Large files could be processed as if they are placed in
memory. File processing is faster than ReadFile() and WriteFile(). There is no need to manage
buffers for repetitive operation on a file. This is more optimally done by OS. Multiple processes
can share memory space by mapping their virtual memory space onto a file. For file operations,
page file space is not needed.
Other considerations
Windows also use memory mapping while implementing Dynamic Link Libraries (DLLs) and
loading & executing executable (EXE) files. It is strongly recommended to use SHE exception
handling while dealing with memory mapped file to look for EXCEPTION_IN_PAGE_ERROR
exceptions.
In order to perform memory mapped file IO operations, file mapping objects need to be created.
This object uses the file handle of an open file. The open file or part of the file is mapped onto
the address space of the process. File mapping objects are assigned names so that they are also
available to other processes. Moreover, these mapping objects also require protection and
security attributes and a size. The API used for this purpose is CreateFileMapping().
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
hFile is the open handle to file compatible with protection flag dwProtect
PAGE_READONLY - It means that page can only be read within the mapped region. It can
neither be written nor executed. hFile must have GENERIC_READ access.
PAGE_READWRITE - Provides full access to object if the hFile has GENERIC_READ and
GENERIC_WRITE access.
PAGE_WRITECOPY - Means that when a mapped region changes, a private copy is written to
the paging file and not to the original file.
dwMaximumSizeHigh and dwMaximumSizeLow specify the size of the mapping object. If set to
0, then the current file size is used. Carefully specify this size in the following cases:
· If the file size is expected to grow, then use the expected file size.
· Do not map a region beyond this limit. Once the size is assigned the mapping region
cannot grow.
· The mapping size needs to be specified in the form of two 32-bit values rather than one
64-bit value.
· lpMapName is the name of the map that can also be used by other processes. Set this to
NULL if you do not mean to share the map.
Previously, we discussed that a file mapping can be assigned a shared name by using
CreateFileMapping(). This shared name can be used to open existing file maps using
OpenFileMapping(). A file map created by a certain process can be subsequently used by other
processes by referring to the object by name. The operation may fail if the name does not exist.
BOOL bInheritHandle,
LPCSTR lpMapName );
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap );
dwDesiredAccess should be compatible with access rights of file mapping object. The three flag
commonly used are:
FILE_MAP_WRITE
FILE_MAP_READ
FILE_MAP_ALL_ACCESS
dwFileOffsetHigh and dwFileOffsetLow give the starting address of the file from where the
mapping starts. To start the mapping from the start of a file, set both as zero. This value should
be specified in multiples of 64K.
dwNumberOfBytesToMap shows the number of bytes of file to map. Set as zero to map the
whole file.
If the function is successful it returns the base address of the mapped region. If the function fails,
the return value is NULL.
As it is necessary to release Heap blocks with HeapFree(), it is also necessary to unmap file
views. File views are unmapped using UnmapViewOfFile().
lpBaseAddress is the pointer to the base address of the mapped view. If the function fails, the
return value is zero.
Flushing File View
The file view can be flushed using the FlushViewOfFile() API. This will force the OS to
writeback the dirty pages of the file on to disk. In case two processes try to access a file at a time
such that one uses file mapping and other uses ReadFile() and WriteFile(). Then both processes
may not receive the same view. Changes made through file maps might still be in memory and
may not be accessible through ReadFile or WriteFile unless they are flushed. To get a uniform
view, it is necessary that all the processes use file maps.
Topic 75: More About File Making
In Win32, it is not possible to map files bigger than 2-3 GB. Also the entire 3GB might not be
available for merely file space. The above limitation is alleviated in Win64. File mapping cannot
be extended. You need to know the size of a map in advance. Customized functions would be
required to allocate memory within the mapped region.
The following minimum steps need to be taken while working with mapped files:
· If the file is new then set its length as some non-zero value using SetFilePointerEx()
followed by SetEndOfFile().
· In the end, unmap file view with UnmapViewOfFile() and use CloseHandle() to close map
and file handles.
Accessing a file through file mapping presents visible advantages. Although the setting up of
fileviews might be programmatically complex, the advantages are far bigger. The processing
time may reduce 3 folds as compared to conventional file operations while dealing with
sequential files. These advantages may only seem to disappear if the size of input and output
files is too large. The example is a simple Ceasar cipher application. It sequentially processes all
the characters with the file. It simply substitutes each character by shifting it a few places in the
ASCII set.
/* Chapter 5.
cci_fMM.c function: Memory Mapped implementation of the
simple Caeser cipher function. */
#include "Everything.h"
BOOL cci_f (LPCTSTR fIn, LPCTSTR fOut, DWORD shift)
/* Caesar cipher function.
* fIn: Source file pathname.
* fOut: Destination file pathname.
* shift: Numeric shift value */
{
BOOL complete = FALSE;
HANDLE hIn = INVALID_HANDLE_VALUE, hOut =
INVALID_HANDLE_VALUE;
HANDLE hInMap = NULL, hOutMap = NULL;
LPTSTR pIn = NULL, pInFile = NULL, pOut = NULL, pOutFile = NULL;
__try {
LARGE_INTEGER fileSize;
/* Open the input file. */
hIn = CreateFile (fIn, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hIn == INVALID_HANDLE_VALUE)
ReportException (_T ("Failure opening input file."), 1);
/* Get the input file size. */
if (!GetFileSizeEx (hIn, &fileSize))
ReportException (_T ("Failure getting file size."), 4);
/* This is a necessar, but NOT sufficient, test for mappability on 32-bit
systems S
* Also see the long comment a few lines below */
if (fileSize.HighPart > 0 && sizeof(SIZE_T) == 4)
ReportException (_T ("This file is too large to map on a Win32
system."), 4);
/* Create a file mapping object on the input file. Use the file size. */
hInMap = CreateFileMapping (hIn, NULL, PAGE_READONLY, 0, 0,
NULL);
if (hInMap == NULL)
ReportException (_T ("Failure Creating input map."), 2);
/* Map the input file */
/* Comment: This may fail for large files, especially on 32-bit systems
* where you have, at most, 3 GB to work with (of course, you have much
less
* in reality, and you need to map two files.
* This program works by mapping the input and output files in their
entirety.
* You could enhance this program by mapping one block at a time for
each file,
* much as blocks are used in the ReadFile/WriteFile implementations.
This would
* allow you to deal with very large files on 32-bit systems. I have not
taken
* this step and leave it as an exercise.
*/
pInFile = MapViewOfFile (hInMap, FILE_MAP_READ, 0, 0, 0);
if (pInFile == NULL)
ReportException (_T ("Failure Mapping input file."), 3);
/* Create/Open the output file. */
/* The output file MUST have Read/Write access for the mapping to
succeed. */
hOut = CreateFile (fOut, GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
NULL);
if (hOut == INVALID_HANDLE_VALUE) {
complete = TRUE; /* Do not delete an existing file. */
ReportException (_T ("Failure Opening output file."), 5);
}
/* Map the output file. CreateFileMapping will expand
the file if it is smaller than the mapping. */
hOutMap = CreateFileMapping (hOut, NULL, PAGE_READWRITE,
fileSize.HighPart, fileSize.LowPart, NULL);
if (hOutMap == NULL)
ReportException (_T ("Failure creating output map."), 7);
pOutFile = MapViewOfFile (hOutMap, FILE_MAP_WRITE, 0, 0,
(SIZE_T)fileSize.QuadPart);
if (pOutFile == NULL)
ReportException (_T ("Failure mapping output file."), 8);
/* Now move the input file to the output file, doing all the work in
memory. */
__try
{
CHAR cShift = (CHAR)shift;
pIn = pInFile;
pOut = pOutFile;
while (pIn < pInFile + fileSize.QuadPart) {
*pOut = (*pIn + cShift);
pIn++; pOut++;
}
complete = TRUE;
}
__except(GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
complete = FALSE;
ReportException(_T("Fatal Error accessing mapped file."), 9);
}
/* Close all views and handles. */
UnmapViewOfFile (pOutFile); UnmapViewOfFile (pInFile);
CloseHandle (hOutMap); CloseHandle (hInMap);
CloseHandle (hIn); CloseHandle (hOut);
return complete;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
if (pOutFile != NULL) UnmapViewOfFile (pOutFile); if (pInFile !=
NULL) UnmapViewOfFile (pInFile);
if (hOutMap != NULL) CloseHandle (hOutMap); if (hInMap != NULL)
CloseHandle (hInMap);
if (hIn != INVALID_HANDLE_VALUE) CloseHandle (hIn); if (hOut !=
INVALID_HANDLE_VALUE) CloseHandle (hOut);
/* Delete the output file if the operation did not complete successfully. */
if (!complete)
DeleteFile (fOut);
return FALSE; }
}
Content:
Another advantage of memory mapping is the ability to use convenient memory based
algorithms to process files. Sorting data in memory, for instance, is much easier than sorting
records in a file.
Program explained in topic 77 sorts a file with fixed-length records. This program, called sortFL,
is similar to Program explaining example of sorting with binary search tree that it assumes an 8-
byte sort key at the start of the record, but this example is restricted to fixed records.
The sorting is performed by the <stdlib.h> C library function qsort. Notice that qsort requires a
programmer-defined record comparison function.
This program structure is straightforward. Simply create the file mapping on a temporary copy of
the input file, create a single view of the file, and invoke qsort. There is no file I/O. Then the
sorted file is sent to standard output using _tprintf, although a null character is appended to the
file map.
Exception and error handling are omitted in the listing but are in the Examples solution on the
recommended book’s Website.
Content:
File maps are convenient, as the preceding examples demonstrate. Suppose, however, that the
program creates a data structure with pointers in a mapped file and expects to access that file in
the future. Pointers will all be relative to the virtual address returned from MapViewOfFile, and
they will be meaningless when mapping the file the next time. The solution is to use based
pointers, which are actually offsets relative to another pointer. The Microsoft C syntax, available
in Visual C++ and some other systems, is:
Notice that the syntax forces use of the *, a practice that is contrary to Windows convention but
which the programmer could easily fix with a typedef.
Using the address returned by MapViewOfFile() for maintaining indexes is meaningless as the
address is liable to change in each call to API. A simple methodology is to maintain an array of
record and then build an index for the records. And subsequently use the index to access records
directly.
The program uses record of varying sizes in a file. It uses the first field of each record as the key
of 8 characters. There are two file mapping. One mapping maps the original file and the other
maps the index file. Each record in index file contains a key and the pointer location into the
original file for the record containing that key. Once index file is created it can be easily used
later. Subsequently, index file records can be sorted for faster searching. The input file remains
unchanged. Pictorial Representation of the example is attached below;
We have previously seen the example use of memory mapped files in windows. This a
fundamental feature of windows. Windows itself uses this feature while working with Dynamic
Link Libraries (DLLs). DLLs are one of the most important components of windows on which
many high-level technologies depend like COM.
Static Linking
The conventional approach is to gather all the source code and library functions attach them and
encapsulate them into a single executable file. This approach is simple but has few
disadvantages.
The executable image will be large as it contains all library functions. Hence it will
consume more disk space and will require large physical memory to run.
If a library function updates the whole program will require recompilation.
There can be many programs that require a library function. Each program will have
static copy of its own. Hence, resources requirement will increase.
It will reduced portability as a program compiled with certain environment setting will
run same functions in different environment where some other version might be.
Using DLLs the library functions are not linked at compile time. They are linked at program load
time (implicit linking) or at run time (explicit linking).
As a result the size of executable package is smaller. DLLs can be easily used to create shared
libraries which can be used by multiple programs concurrently. Only a single copy of shared
DLLs is placed in memory. All the processes sharing the DLL map the DLL space onto their
program space. Each program will have its own copy of DLL global variables.
New versions or updates can be simply supported by just providing a new DLL without the need
of recompiling main code. The library runs in the same processes as the calling the program.
Importance of DLLs
DLLs are used in almost all modern operating systems. DLLs are most important in case of
windows as they are used to implement OS interfaces. The entire Windows API is supported by
a set of DLLs which are invoked to call kernel services. The DLL code can be shared by multiple
processes. DLL function when invoked by a process runs in process space. Therefore, it can use
resources of the calling process such as file handles and thread stack. DLLs should be written in
Thread-safe manner. A DLL exports variables as well as function entry points.
Implicit linking is the easier of the two techniques. Functions defined in a DLL are collected and
build as DLL. The build process builds a .LIB file which is a stub for actual code. The stub is
linked to the calling program at build time. It provides a place holder for each function in the
DLL.
The place holder/stub will call the original function in the DLL. This file should be placed in
common user library directory for the project. The build process also constructs the DLL that
contain the original binary image for the functions. This File is usually placed in the same
directory as the application.
Function interfaces defined in DLLs should be exported carefully.