Online Documentation Server
Net technology
Web technology
Data bases
Other docs



Вся предоставленная на этом сервере информация собрана нами из разных источников. Если Вам кажется, что публикация каких-то документов нарушает чьи-либо авторские права, сообщите нам об этом.

Chapter 9

Portability Issues with Windows and Perl


This chapter covers some of the issues concerning working with Perl in the Microsoft Windows NT and Windows 95 environments. After reading this chapter, you should be able to see where Perl can be used on Windows platforms.

Perl for Windows

The Win32 port of Perl refers to both the Windows NT and Windows 95 environments. Actually, Windows 95 is not really a 32-bit environment, though Perl 5 can still be used on it. The real work for the porting process began with Windows 3.1 and has concentrated on Windows NT 3.51. The work now includes limited support for Windows 95. Taken collectively, the ported software is referred to as the Win32 port of Perl 5 even though Window 95 is not a true 32-bit operating system.

In any event, the Win32 version of Perl has lagged behind the UNIX versions of Perl. For example, at the time this chapter was written, Perl was already in 5.002b, whereas the Win32 versions were at the 5.001m level. This is likely to be the case in the near future because Perl has been developed primarily in the UNIX domain and other platforms have been supported only as an afterthought. Given the differences in operating systems and Perl's reliance on certain system features, there exist some inconsistencies in the versions of Perl 5 that run on UNIX and Windows NT. For example, the Perl 5 for the Win32 port for NT is very different from the UNIX version of this port because the Win32 version has several module extensions that are not available in UNIX.

The port of Perl 5 to the Windows platform has even been blessed by Microsoft to allow users to bring existing Perl scripts over to NT servers. There is one major point to remember about the Win32 port: the source for the extension is copyrighted by Microsoft Corporation. However, the source is distributed under the Artistic License agreement that comes with and governs the distribution of Perl.

For more information, you can subscribe to the Perl-Win32, Perl-Win32-users, and Perl-Win32_announce mailing lists. The Perl-Win32 mailing list is for specific topics on using Perl and for differences in scripts running on UNIX and NT systems. For other topics, especially for newcomers, try the Perl-Win32-users list. Join the announcement list if you want to keep abreast of any new ports. To join the discussions, send a mail message to and in the body of the message add the single line subscribe Perl-Win32, subscribe Perl-Win32_announce, or subscribe Perl-Win32-users, depending on which mailing list you want to subscribe to.

There is also a WWW home page for Win32 Perl. This site contains the latest archived information for Win32 Perl, including a FAQ list. The URL for the Web site is Finally, check the CPAN archives for the WINNT package for more information on interfacing with the NT system.

Getting the Distribution

The Perl for Win32 distribution can be retrieved from Check out the latest version in the /ntperl directory. Both zipped source and binary files are available in the /ntperl/perl5.001m/CurrentBuild directory.

The file with the binary version of Perl is; the source (should you want to look at it) is in Version 107 was dated December 1995 and might be out of date by the time you read this, so use the latest version you can find. The release notes in the rel107.txt file highlight significant improvements with every release. Always read the release notes to see what functionality is available for the port when compared with those in UNIX versions of Perl. For example, it's only recently that some Perl on Windows 95 has been supported.

Once you have the zip file, use unzip to extract the files. Do not use pkunzip because the names of the files you extract are longer than what pkzip supports. If you cannot find unzip, try using WinZip. Do not forget to specify that you want to create subdirectory trees as you extract.

Read the INSTALL.BAT file to get an idea of how it all installs. Just run INSTALL.BAT and answer yes twice. When you unzip the Perl archive, remember to use the option to preserve the directory structure. If you do not unzip the directory tree, the install.bat file will fail with an error message stating that it cannot find Perl. Also, there is a source code file,, that you do not need unless you make modifications to Perl or compile in extensions. The source files will compile under gcc for DOS or Microsoft's Visual C++ 2.5 or later.

Differences in Perl on NT and UNIX

There are many important issues you must be aware of when writing Perl scripts that must run under NT and UNIX. First of all, there is the issue of pathnames. In DOS, Windows NT, and Windows 95 you specify pathnames with a backslash operator, whereas in UNIX you use the forward slash operator. The Win32 Perl port allows either forward slashes or backslashes. A Perl script using forward slashes in pathnames is more likely to be portable than a script using backslashes. Besides that, Perl scripts will have to use the backslash twice in strings to escape one instance of a backslash from the Perl interpreter. Drive letters do not have to change.

For example, the following pathname can be set using either of the following:

$myFile = "E:/users/default/datafile.txt";

$myFile = "E:\\users\\default\\datafile.txt";

Second, Perl scripts often rely heavily on escaped shell commands. For example, the command to copy a file in UNIX is cp, but under NT the command is COPY. A portable Perl script will have to either avoid relying on escaped commands or use system calls instead. Even commands that have the same name behave differently on UNIX and NT. For example, the date command in UNIX returns the current date and time of day, whereas there are two separate commands for DATE and TIME, respectively, in NT. Here's one way to set up commands for different operating environments based on the value of the variable $usingUNIX:

if ($usingUNIX)
    $directory = `ls`;   #UNIX version.
    $copyCommand = `cp`;   #UNIX version.
    $directory = `dir`;  #Win32 version.
    $copyCommand = `COPY`;  #Win32 version.

Command-line Perl scripts are not directly supported with the Win32 Perl package. Perl scripts have to be in files on disk to be executed. There is a utility called pl2bat.exe that wraps a batch file (.bat) around a Perl script. After you run pl2bat on your Perl script, you simply execute the newly created batch file to execute your script.

Perl scripts written under UNIX terminate lines with a carriage return (CR, 0x0D). Windows NT, however, terminates text lines with two characters: a carriage return and a line feed (CR-LF or 0x0D + 0x0A). When porting Perl scripts over to your Windows NT machine from UNIX or vice versa, you may have some work to do in order to keep the CR-LF and CR-only outputs consistent.

Finally, the dynamic loader in Perl, DynaLoader, is not currently implemented in the NT version of Perl. The major consequence of this is that in NT you cannot run any Perl extensions that require dynamic loading.

The UNIX #!/usr/bin/perl notation does not work with Perl scripts on NT.

Listing 9.1 presents a sample Perl script that sends mail on an NT machine. You can send mail messages on NT using Perl if you communicate using one-way sockets. (Use the SMTP server on NT to receive mail.) Any error messages from the SMTP server when sending messages are for acknowledgments sent back to the sender. These error messages can be ignored.

Listing 9.1. Using Perl on Windows NT.
 1 require "";
 3 $yonder = '';
 4 $port = 25;        # Use standard port
 5 $AF_INET = 2;
 6 $SOCK_STREAM = 1;
 7 $sockaddr = 'S n a4 x9';
 9 # -------------------------------------------------------------
10 chop($hostname = `hostname`);
12 ($name,$aliases,$proto) = getprotobyname('tcp');
13 ($name,$aliases,$port) = getservbyname($port,'tcp') unless $port =~/^\d+$/;;
14 ($name,$aliases,$type,$len,$myAddress) = gethostbyname($hostname);
15 ($name,$aliases,$type,$len,$recvAddress) = gethostbyname($yonder);
17 $mySelf = pack($sockaddr, $AF_INET, 0, $myAddress);
18 $receiver = pack($sockaddr, $AF_INET, $port, $recvAddress);
20 # -------------------------------------------------------------
21 # Make the socket filehandle
22 # -------------------------------------------------------------
24 socket(MYSOCKET, $AF_INET, $SOCK_STREAM, $proto) || die "\n Cannot open $!";
25 bind(MYSOCKET, $mySelf) || die "\n Cannot bind" ;
26 connect(MYSOCKET, $receiver) || die "\n Cannot connect";
28 #
29 # Any print S statements go to the socket
30 # Any other print statements go back to the Web browser
31 #
32 select MYSOCKET;
34 ReadParse (@in);
35 print "HELO $hostname\n";
36 print "MAIL FROM: <",$in{'From'},">\n";
37 print "RCPT TO: <",$in{'To'},">\n";
38 print "DATA\n";
39 print "Subject: ",$in{'Subject'},"\n\n";
41 print "====================\n";
42 print "From: ",$in{'From'},"\n";
43 print "To: ",$in{'To'},"\n";
44 print "Subject: ",$in{'Subject'},"\n";
46 foreach $key (keys(%in)) {
47      if (($key eq 'Data') || ($key eq 'Body') || ($key eq 'Address') ||
48     ($key eq 'data') || ($key eq 'body') || ($key eq 'address')) {
49           print $key,":\n",$in{$key},"\n";
50      }
51      else {
52           if (($key ne 'From') && ($key ne 'To') && ($key ne 'Subject') &&
53 ($key ne 'from') && ($key ne 'to') && ($key ne 'subject')) {
54                print $key,": ",$in{$key},"\n";
55           }
56      }
57 }
59 print ".\n";
60 print "QUIT\n";

Unsupported Functions

The difference between NT and UNIX in operating system behavior is apparent when you have to port Perl applications. Some of the UNIX versions of Perl system functions have no equivalent functions in NT. Similarly, NT functions that deal with the Registry have no equivalent functions in UNIX.

Most UNIX system calls are not available in NT. Some of these functions use an error message to warn you that they are not implemented. Most cause the script to fail on unresolved references. (The warning capability for all functions requires modifications to the main Perl 5 distribution and has not been undertaken.) The most up-to-date list is at The following functions were supported at the time of writing:

Network Routines
System V-Compatible Ipc Routines
I/O Routines
select($w, $x, $y, $z)*
Filesystem Routines
Security-Related Routines
Process-Related Routines
Miscellaneous Routines

Perl scripts that make or rely heavily on system() calls are generally not portable from UNIX to NT.

You might consider writing stub routines for system() and alarm() and include these lines in any Perl code you port from UNIX to NT. This prevents annoying compiler errors while you debug other sections of the code. Keep in mind, though, that stubbing out these functions is only an interim solution because the Perl script you are porting over might actually use the return values from system calls.

Having a null return value from a fake system stub function most likely will work only in the simplest programs.

Extensions to Perl 5 for Windows NT

Extensions for NT include support for working with the NT Registry as well as event logging and OLE. (These extensions are meant for NT only and most probably will not work in Windows 95.) The OLE extensions require a deeper knowledge of OLE programming concepts, which are well beyond the scope of this text and are not discussed here. Check the site for a short Web page with information about using OLE in Perl scripts on NT. Use the Registry and event logging extensions whenever possible instead of attempting to write your own modules to do the same thing.

Here's a sample script in NT. To run this script, you have to type "perl scriptName". Note how the #! is absent in this script:

print &NTLoginName , "\n" ;
print &NTDomainName , "\n" ;
print &NTNodeName , "\n" ;
print &NTFsType , "\n" ;

The utility functions provided under Windows NT Perl 5 and used in this script are defined as follows:

NTLoginName Returns the ID of the user who's logged on
NTDomainName Returns the domain name
NTNodeName Returns the node name
NTFsType Returns the name of the file system type

Event Log Functions

The Win32 API allows for querying the event log in NT. Using the EventLog extensions, your Perl script can access these API functions. For details on the API calls themselves, check the NT API reference manual. Here is a summary of the available functions:

  • NTOpenEventLog HANDLE, $server, $source
  • This function opens the log specified by $source on the machine with name $server. The file handle of the opened log, HANDLE, is used in subsequent operations. Use NULL if the server is the local machine.
  • NTCloseEventLog HANDLE
  • This function closes the event log associated with the supplied HANDLE.
  • NTGetNumberofEventLogRecords HANDLE, $number
  • sing the HANDLE of a log opened with NTOpenEventLog, this function returns the number of records in $number.

It's easy to read each entry in the event log with a call to the NTReadEventLog function:

NTReadEventLog HANDLE,

Given the HANDLE of a log opened the NTOpenEventLog function attempts to read an entry from the log according to the directives supplied in $flags and $record. The $flags is a bit flag that can be any combination of the following values:


The $record number specifies the index to seek to and is numbered from 1 and up.

After the call is returned, you can look at the contents of the rest of the arguments. The Event Record header ($EVTheader) is a binary structure. Use the unpack() routine to get its contents. Here's an example:

( $length, $reserved, $recordnumber, $timegenerated, $timewritten, $eventid,
          $eventtype, $numstrings, $eventcategory, $reservedflags,
          $closingrecordnumber, $stringoffset, $usersidlength, $usersidoffset,
          $datalength, $dataoffset ) = unpack( 'l6s4l6', $header );

The $source variable is set to the name of the application that generated the event record. $computer specifies the Unc name of the machine that generated the event record.

The $SID variable stores a security identifier structure as defined in the Win32 API documentation. An array of message strings is returned in $strings as one long array of strings, each terminated with the \0 character. The array is terminated with an empty string. (That is, look for two \0 characters to terminate the array.) The $data vatiable is set to any binary information associated with the returned event record.

To add events to the log, use the NTWriteEventLog function. Here's the syntax for the NTWriteEventLog function:


This function writes an event log entry to the given event log $source on the named $computer. The $eventType variable can have any one of these values:


$category specifies the event category and is entirely determined by the calling application, as is $eventID. The RESERVED value must be set to NULL. $data contains any binary data that is to be associated with the event log record. @Strings is an array of strings that has the same format for the event record that you read back.

NT Registry Routines

In Windows NT, all initialization (*.INI) files are now replaced with the Windows Registry. The Registry is simply a database of the all the symbols, definitions, and settings that used to exist in the INI files.

Although Perl lets you mess around with the Registry, it's important to remember that a wrong entry can mess up your NT system (not to mention your entire day). While tinkering with the Registry, you can easily render an NT machine useless, requiring a complete reinstall! Be careful.

The Win32 Perl routines do not support Unicode characters with the Win32 API. Therefore, any Unicode strings required by the mapped functions will be converted to 8-bit ANSI character strings.

The following functions are mapped directly to the Win32 API functions. All functions return true on success and false on failure. The action of these functions in an NT environment is beyond the scope of this book. Please consult the Win32 API for more information concerning what the called functions do:


The RegCloseKey function releases a given key handle. The syntax for this call is
NTRegCloseKey $hkeyHandle
$hkeyHandle for a specified key is rendered invalid and therefore cannot be used after it has been closed. Key handles should be closed as soon as possible. Also note that due to caching, the information might not be written to disk immediately. Use the NTRegFlushKey call to force writing the key to disk. The NTRegFlush key is a system resource hog and should be used with care.
The syntax for this function is
NTRegConnectRegistry $computer, $hkey, $result
NTRegConnectRegistry establishes a connection to a predefined Registry handle $hkey on another computer specified in $computer. The returned handle is stored in $result. Use a value of NULL for $computer if connecting locally. Only the constants $hkey_LOCAL_MAchINE and $hkey_USERS are valid for $hkey. Use the call to NTRegCloseKey when you are done with the handle in $result.

The value in $hkey for most of the functions listed in this section can be replaced by a call to NTRegConnectRegistry to get a name from a remote computer.

This function creates a subkey under a given handle and is provided for older programs. Use the NTRegCreateKeyEx function for future portability. The syntax for this function is
NTRegCreateKey $hkey, $subkey, $result
The call to this function creates the key named in $subkey under the already open key handle $hkey. The returned handle is stored in $result. If the key already exists in the Registry, the function opens it and returns the handle in $result.
The $hkey can have one of the following values:
  • $hkey_CLASSES_ROOT
  • $hkey_CURRENT_USER
  • $hkey_LOCAL_MAchINE
  • $hkey_USERS
The NTRegCreateKeyEx function is the preferred way of creating keys. The syntax for this function is
NTRegCreateKeyEx $hkey, $subkey, NULL, $class,
        $options, $accessMode, $security,
        $result, $disposition
This function creates the subkey named in $subkey and returns the handle of the newly opened key in $result. If the key already exists in the Registry, the function opens it and returns its handle instead. The $class string specifies the object-type for the key.
The value in $options can be REG_OPTION_VOLATILE or the value of REG_OPTION_NON_VOLATILE. Volatile keys are stored in memory only and are not preserved in between Windows NT sessions. Nonvolatile keys are saved to disk and preserved when the system is restarted. Use volatile options when testing the access mode in which the key is specified in the file in the source code for the Win32 Perl distribution. The $security descriptor can be NULL or set as described in the Win32 API documentation.
The newly created key that the NTRegCreateKeyEx function creates is not set to a default value. Use the NTRegSetValue or NTRegSetValueEx function to set the value of this key.
An application cannot create a key under $hkey_USERS or $hkey_LOCAL_MAchINE.
The $disposition variable is set to either REG_CREATED_NEW_KEY or REG_OPENED_EXISTING_KEY. If a new key is opened, the current process owns this key and locks access to it until the key is released. A returned value of the existing key specifies that another process owns the lock to the returned key.
The NTRegDeleteKey function deletes the named $subkey under the $hkey. This function cannot delete a key that has any existing subkeys. The subkey to be deleted must be a subkey of the key identified by $hkey. The syntax for this function is
NTRegDeleteKey $hkey, $subkey
The key must already exist before it can be deleted.
This NTRegDeleteValue function resets the value of an existing key. The syntax is
NTRegDeleteValue $hkey, VALUE
The key must already exist with a value before it can be reset.
The NTRegEnumKey function is an old function for retrieving names of subkeys. For newer applications, use the NTRegEnumKeyEx call. The syntax for this old call is
NTRegEnumKey $hkey, $index, $keyname
In the Registry, subkeys are not ordered. Therefore, the returned value can be in any order, and you must sort the returned values yourself. To use this function, call the NTRegEnumKey function with the $index set to zero:
$index = 0;
while(&NTRegEnum($hkey,$index,$keyname) != false) {
    printf " [%d] = %s \n", $index++, $keyname;
The last key in the list is retrieved with a call to NTRegQueryInfoKey. Do not change the value of or create new keys while you are iterating through the list of keys because you may change the order in which the keys are listed. To open the key in $hkey, use the RegCreateKeyEx or RegOpenKeyEx function with KEY_ENUMERATE_SUB_KEYS access.
The syntax for this function is
NTRegEnumKeyEx $hkey, $index, $subKeyName, NULL, $class, $lastWrite
The NTRegEnumKeyEx function enumerates subkeys of the open key $hkey, returning the $subKeyName, $class, and time of $lastWrite for the key indexed by ISUBKEY. The function retrieves information about one subkey each time it is called. Unlike the NTRegEnumKey function, NTRegEnumKeyEx retrieves the class name of the subkey and the time it was last modified.
The calling convention for running through the keys is the same as that for NTRegEnumKey.
The NTRegEnumValue function enumerates the values for the key specified in $hkey. The function takes one value in $name and the $data block. It can also take types in $data as listed in the file. The index for the $data type is in $integerValue. The syntax for this call is
NTRegEnumValue $hkey, $integerValue, $name, $type, NULL, $data
NTRegFlushKey $hkey
The RegFlushKey function writes all the contents and parameters of the open key $hkey into the Registry. The function returns after the data has been completely written to disk. The syntax for this command is
NTRegFlushKey $hkey
Registry changes are flushed automatically to disk by the Registry using a lazy write mechanism at shutdown time. Call this function to write any modifications immediately to disk. An important point to remember is that this function may also flush contents of other keys.
This function gets a copy of the security descriptor protecting the open Registry key $hkey. The requested security information is specified in $securityInfo, and the returned descriptor is stored in $descriptor. The syntax for this function is
NTRegGetKeySecurity $hkey, $descriptor, $securityInfo
The syntax for the NTRegLoadKey function is
NTRegLoadKey $hkey, $subkey, FILE
The NTRegLoadKey function creates a $subkey under $hkey_USER or $hkey_LOCAL_MAchINE and stores registration information from a specified FILE into that $subkey.
The NTRegOpenKey function opens the $subkey under the key $hkey and returns the handle of the opened key in $result. This is an old function that will be replaced by the NTRegOpenKeyEx function. The syntax for this function is
NTRegOpenKey $hkey, $subkey, $result
The NTRegOpenKeyEx key function is a later version of NTRegOpenKey. Its syntax is
NTRegOpenKeyEx $hkey, $subkey, NULL, $sam, $result
The NTRegOpenKeyEx function opens $subkey under $hkey with the system access mode $sam, returning the handle of the opened key in $result. Unlike the NTRegCreateKeyEx function, the NTRegOpenKeyEx function will not create the specified key if the key does not exist in the Registry.
The NTRegQueryInfoKey function retrieves information about a key. Its syntax is
NTRegQueryInfoKey $hkey,
    $class, NULL, $nsubkeys, $maxsubkey, $maxclass,
    $nvalues, $maxvaluename, $maxvaluedata, $security_descriptor,
Here are the output parameters for $hkey:

$CLASS The key class
$NSUBKEYS The number of subkeys
$MAXSUBKEY The longest subkey name
$MAXCLASS The longest class name
$NVALUES The number of values
$MAXVALUENAME The longest value name
$MAXVALUEDATA The longest value data block
$LASTWRITETIME The time when modified last

The NTRegQueryValue is an obsolete version of NTRegQueryValueEx and is provided only for compatibility with older scripts. The syntax is
The NTRegQueryValue $hkey, $subkey, $data
The NTRegQueryValueEx function is used to get the $type and $data for a specified $valueName associated with the open Registry key $hkey. The syntax for this command is
NTRegQueryValueEx $hkey, $valueName, NULL, $type, $data
The NTRegReplaceKey function replaces the file backing a key and all its subkeys with another file. You need to restart the system for the change to take effect. The syntax for this call is
NTRegReplaceKey $hkey, $subkey, $newFile, $backupFile
The file specified by the $newFile parameter stays open until the system is restarted. This function is a privileged call in NT and the calling process must be of the SE_RESTORE_NAME privilege.
The syntax for the NTRegRestoreKey function is
NTRegRestoreKey $hkey, $filename, $flags
The NTRegRestoreKey function reads the Registry information in a specified file and copies it over the specified key. This Registry information may be in the form of a key and multiple levels of subkeys.
The NTRegSaveKey function saves the specified key $hkey and all of its subkeys and values to a new file called $filename, with security settings specified by $securityAttributes. The syntax for this function is
NTRegSaveKey $hkey, $filename, $securityAttributes
The calling script must be with the SetBackupPrivilege security privilege. The NTRegSaveKey function will save only the nonvolatile keys. It will not save volatile keys. A key is made volatile or nonvolatile when created with the underlying NT system call to RegCreateKeyEx. In most cases, you would be using the security field in a compiled C program for security reasons and not in an interpreted, hence modifiable, Perl program.
The NTRegSetKeySecurity function sets the security of an open Registry key. The syntax for this call is
NTRegSetKeySecurity $hkey, $securityInformation, $descriptor
Security issues in NT are well beyond the scope of this text. Please refer to the Win32 API for more detailed information. One thing to keep in mind is that the security information is not changed until you open the key in $hkey. It's easier to close and reopen the key for the new security attributes to work.
The NTRegSetValue function associates a value with $subkey under the key $hkey. This value must be of type REG_SZ and cannot have a name. This function is an older version of NTRegSetValueEx and is only provided for backward compatibility. Its syntax is
NTRegSetValue $hkey, $subkey, $type, $data
The NTRegSetValueEx function stores the value-data pair {$valueName,$data} in the named key $hkey in $valueName. The syntax is
NTRegSetValueEx $hkey, $valueName, NULL, $type, $data
The length of the $value parameter must be limited to 2,048 bytes or less. Longer values such as bitmaps, icons, data files, and so on can be stored in files whose pathnames can be recorded in the Registry. The key in the $hkey parameter must have been opened with KEY_SET_VALUE access in one of the two functions: NTRegCreateKeyEx or NTRegOpenKeyEx. The $type variable can be set to REG_SZ, REG_MULTI_SZ, or REG_EXPAND_SZ.
The NTRegUnLoadKey unloads the key and its children specified by $subkey under $hkey from the Registry in memory but does not modify the underlying file. The syntax is
NTRegUnLoadKey $hkey, $subkey

Problems with Windows 95

The command interpreter in Windows 95 is still 16-bit. Perl is destined to run in 32 bits and is more suitable for NT. A number of Perl functions that would work under NT and UNIX will break in the Windows 95 environment. Some of these problems are fixed by using the 32-bit command interpreter wrapper that's supplied with Win32 Perl in the bin subdirectory as cmd32.exe. You can also configure Perl to use another shell such as MKS Inc.'s Korn shell for Windows 95.

To change the shell setting you must edit the Registry. Change the following string in the Registry to the full path of your shell:


Here are some notable problems in the Windows 95 environment:

  • Unreliable exception handling through signals and traps.
  • Pipes do not work.
  • Return codes from fork() may not be reliable.
  • There is no stderr redirection in For example, exec "doit 2>output.txt" will not work.


This has been a very brief introduction to using Perl 5 on Windows NT and Windows 95 machines. The process of porting Perl to these platforms is not complete and is prone to some bugs. However, general Perl scripts that do not deal with system calls will be able to run on NT and UNIX machines without many problems.

Previous chapter Chapter contents Contents Next chapter

With any suggestions or questions please feel free to contact us