Object-Oriented Programming
5.5 Using Class Objects in Programs
5.5.1 Examples using Access Attributes
In the preceding section, an illegal attempt was made to assign the value 0 to data memberBaseAddress, as now shown again:
In that program, BaseAddress was declared as a private data member, which prevented the main() function from gaining access to change its value.
Instead of declaring BaseAddress as a private member, it can be declared as a public member. This being the case, it can be accessed by any function in the program and the compilation error will no longer appear. Listing 5-8 shows how this is done. Note that if you do not have a second parallel port (LPT2:) in your computer, the address 0x3BC does not exist, and the program will not function. However, it will compile error-free. Also, ensure the data cable that connects with the parallel port is plugged into the D25 connector of the correct port.
Listing 5-8 Declaring BaseAddress as a public data member.
/***************************************************** Note that the member variable BaseAddress is now
declared under the public access attribute. Therefore it can be changed from within the main function and there will be NO compilation errors.
If your computer DOES NOT have a second parallel port, attempting to use the port address 0x3BC will FAIL! *****************************************************/ #include <dos.h>
class ParallelPort {
public: // Private access attribute has been
// changed to public.
unsigned int BaseAddress; public:
ParallelPort();
ParallelPort(int baseaddress);
void WritePort0(unsigned char data); }; ParallelPort::ParallelPort() { BaseAddress = 0x378; } ParallelPort::ParallelPort(int baseaddress) { BaseAddress = baseaddress; }
void ParallelPort::WritePort0(unsigned char data) { outportb(BaseAddress,data); } void main() { ParallelPort OurPort;
OurPort.BaseAddress = 0x3BC; // Will NOT cause a // compilation error OurPort.WritePort0(255);
}
Declaring member data as public is considered poor programming practice. The main purpose of using object-oriented programming is to form encapsulated objects, that are to some extent protected against misuse. This will not be the case if member data are declared to be public.
If a member variable needs to be changed, then it can be changed through a member function specifically designed for that purpose. This concept is demonstrated by the ChangeAddress() function shown in Listing 5-9.
Listing 5-9 The acceptable way to change a data member of an object.
/***************************************************** WRITING TO A PORT (Adding a member function to change the private member data).
Note: attempting to use the port address 0x3BC will fail if your computer doesn’t have a second parallel port. *****************************************************/ #include <dos.h>
class ParallelPort {
private: // Access attribute has been changed
// back to private.
unsigned int BaseAddress; public:
ParallelPort();
void WritePort0(unsigned char data); // New public member function added.
void ChangeAddress(unsigned int newaddress); }; ParallelPort::ParallelPort() { BaseAddress = 0x378; } ParallelPort::ParallelPort(int baseaddress) { BaseAddress = baseaddress; }
void ParallelPort::WritePort0(unsigned char data) {
outportb(BaseAddress,data); }
// New public member function defined.
void ParallelPort::ChangeAddress(unsigned int newaddress) { BaseAddress = newaddress; } void main() { ParallelPort OurPort;
// The correct way to manipulate a private data member OurPort.ChangeAddress(0x3BC);
OurPort.WritePort0(255); }
The program statement shown below from the previous listing will legally change the value of the private data member BaseAddress to 0x3BC:
OurPort.ChangeAddress(0x3BC);
In this illustrative example we have shown how a private data member can be changed using a public member function. The public member function ChangeAddress() has complete access to the private data member BaseAddress since it is a member function of that same object class. We will not use the ChangeAddress() function in our proper ParallelPort class.
Instead, the user can pass the desired value for the base address to the constructor so the BaseAddress can be set to a different value than its default value of 0x378 (set by the default constructor).
5.6 Parallel Port Class – Stage II
In the first stage we created an object class named ParallelPort which provided the required functionality to use the port associated with address BASE. We now need this object to also use the port at address BASE+1. Note that the port at address BASE is used as an output port and the port at address BASE+1 is an input port. The new object’s intended functionality is:
x Ability to specify the base address of the parallel port.
x Send data through port at address BASE.
x Receive data through port at address BASE+1.
Adding further functionality to an existing object is an ideal situation for using class derivation. However, there is no justification to develop a hierarchy of classes for each part of the parallel port, since there is no great use of parts of the parallel port. It is most appropriate to develop the entire parallel port as one object. Therefore, in this second stage we will add the extra functionality to the ParallelPort class so it can also use the port at address BASE+1.
The class definition for the new ParallelPort class is given in Listing 5-10, with additions shown in bold text. It contains the declarations for the member data and the member functions. All data members of the class ParallelPort are declared as private. All the member functions are declared as public. As before, BaseAddress is one of the data members and the function WritePort0()is included so that data can be sent out to port at address BASE.
Listing 5-10 New class definition for the object ParallelPort. class ParallelPort
{
private:
unsigned int BaseAddress; unsigned char InDataPort1; public:
ParallelPort(); // default constructor
ParallelPort(int baseaddress); // constructor void WritePort0(unsigned char data);
unsigned char ReadPort1(); };
The port at address BASE+1 is an input port (data into the PC). The function ReadPort1() has been introduced to the ParallelPort class to read data through this port. The private data member InDataPort1 is declared to store the data read from this port. The number assignments used for all the members of the class represent the offsets from the base address. For example, WritePort0() function will be writing to an address with offset 0 with respect to the base address – in this case BASE+0, being the BASE address. Similarly, the ReadPort1() function will read from an address with offset 1. Therefore, it will read the port at addressBASE+1.
The definitions of all the functions belonging to this expanded class are contained in Listing 5-11.
Listing 5-11 Function definitions of the ParallelPort object.
ParallelPort::ParallelPort() // default constructor {
BaseAddress = 0x378; InDataPort1 = 0; }
ParallelPort::ParallelPort(int baseaddress) // constructor {
BaseAddress = baseaddress; InDataPort1 = 0;
}
void ParallelPort::WritePort0(unsigned char data) {
outportb(BaseAddress,data); }
unsigned char ParallelPort::ReadPort1() {
InDataPort1 = inportb(BaseAddress+1);
// Invert most significant bit to compensate for // internal inversion by printer port hardware.
InDataPort1 ^= 0x80;
// Filter to clear unused data bits D0, D1 and D2 to zero. InDataPort1 &= 0xF8;
return InDataPort1;
}
The only change to the constructors is the extra statement that initialises the data member of the BASE+1 address, InDataPort1 to 0. If InDataPort1 is not
initialised it will store some unknown value. However, initialising this variable to 0 is not essential. It may be initialised to any other value or left un-initialised provided precautions are taken to prevent its use until InDataPort1 holds an actual value read from the port.
The function ReadPort1(), reads the port at address BASE+1 and returns a value of type unsigned char. This requires the body of this function to have a return statement, which is return InDataPort1. Therefore, while this function stores the results of input operations in the data member InDataPort1, at the same time it provides an interface to other functions outside of the class to receive the value of this data member. This will enhance the flexibility of the object. In the coming chapters the ParallelPort object will be used when writing many programs. It is advantageous to have full flexibility in the ParallelPort object so that it can be used to write good and efficient programs.
The function inportb() is called within ReadPort1() and carries out the task of reading the data from the port at the specified address, in this case BASE+1. Note that only bits 3 to 7 are free to be read through this port. Also, bit 7 is internally inverted by the parallel port hardware. The ReadPort1() function is coded to compensate for the inversion (explained in Section 3.6) and also clear the unused bits D0-D2 to zero by using the logical AND operator (&). The hexadecimal number F8 represents a bit pattern of 1111 1000 and will clear bits D0-D2 of any number it is ANDed with. The value produced from this correcting operation will be stored in the data member InDataPort1. The last line of the ReadPort1() function contains the return statement which returns the value ofInDataPort1.
The complete program is shown in Listing 5-12. Check operation of the program by connecting your interface board to the PC according to Table 3-1 and Table 3-2.
Listing 5-12 Write data to port at BASE and read data from port at BASE+1.
/***************************************************** The fundamental object class ParallelPort is expanded to include the input port at address BASE+1. The combined object is still named ParallelPort and is used to write to the port at address BASE and to read data from the port at address BASE+1. *****************************************************/ #include <stdio.h> #include <dos.h> class ParallelPort { private:
unsigned int BaseAddress; unsigned char InDataPort1; public:
ParallelPort();
ParallelPort(int baseaddress);
void WritePort0(unsigned char data); unsigned char ReadPort1();
}; ParallelPort::ParallelPort() { BaseAddress = 0x378; InDataPort1 = 0; } ParallelPort::ParallelPort(int baseaddress) { BaseAddress = baseaddress; InDataPort1 = 0; }
void ParallelPort::WritePort0(unsigned char data) {
outportb(BaseAddress,data); }
unsigned char ParallelPort::ReadPort1() {
InDataPort1 = inportb(BaseAddress+1); // Invert most significant bit to compensate
// for internal inversion by printer port hardware. InDataPort1 ^= 0x80;
// Filter to clear unused data bits D0, D1 and D2 to zero. InDataPort1 &= 0xF8;
return InDataPort1;
}
void main() {
unsigned char BASE1Data;
ParallelPort OurPort;
OurPort.WritePort0(255); BASE1Data = OurPort.ReadPort1();
printf("\nData Read from Port at BASE+1 %2X\n",BASE1Data); }
Listing 5-10 and Listing 5-11 (explained earlier) are incorporated unchanged in Listing 5-12 which has a main() function added.
The first line in the main() function is:
unsigned char BASE1Data;
This line declares a variable named BASE1Data to store data of type unsigned char. In strict C++ terms, this line has instantiated an object of type unsigned char and given it the name BASE1Data. As such, BASE1Data will now reside in memory. The purpose of BASE1Data is to store the value read from the port at addressBASE+1. How this is done will become clear as we work through the rest of the statements of the main() function.
The next line in the main() function is:
ParallelPort OurPort;
This line instantiates the OurPort object, which is of type ParallelPort. Therefore, Ourport will have two data members, namely BaseAddress and InDataPort1. When the above line is executed, the default constructor will be called (no argument used for the base address). As a result the variable BaseAddress will be set to 0x378 and the variable InDataPort1 will be set to 0.
The two member functions are called in the next two lines:
OurPort.WritePort0(255);
BASE1Data = OurPort.ReadPort1();
The first line writes a byte of data (255 in this case) to the port at address BASE. This will cause all eight LEDs to light. The second line will read the port at address BASE+1 and compensate for inverted bit D7. ReadPort1() stores this result for later retrieval in the data member InDataPort1 and returns the value of InDataPort1 to the main() function. This value received by the main() function is stored into its variable BASE1Data.
The last line of the main() function displays the value of BASE1Data on the screen in hexadecimal format with a field width of 2. A carriage return and line feed is inserted before the value is displayed by using the new line character combination\n. In this example main() function, its variable BASE1Data was assigned the value returned from the ReadPort1() function. Our future programs will not always be programmed to do operate this way. For these cases where the main() function
Also note we need to have the data member InDataPort1 so the value read from the port can be stored in our object. Without having such a storage variable,
the program must rely on the main() function’s variable BASE1Data to be assigned the value returned from the ReadPort1() function. It will not always be desirable for our future programs to be programmed to use a main() function variable in this way. In these cases, if the ParallelPort object did not have the data member InDataPort1 to store the value returned from ReadPort1(), then this value would be lost once ReadPort1() completes its execution.
5.7 Parallel Port Class – Stage III
In this final stage we will further develop the object class ParallelPort to encompass all input/output functionality of the parallel port of the PC - with one exception. This being the absence of input through BASE+2 as it can be unreliable on some computers. This class will output data through the port at address BASE, input data through the port at address BASE+1, and output data through the port at addressBASE+2. It will also compensate for internal inversions that occur within the parallel port hardware.
5.7.1 Full function Object Class ParallelPort
The functionality required for the final object class ParallelPort is:
x Ability to specify the BASE address of the parallel port.
x Output data through the port at address BASE.
x Input data through the port at address BASE+1. x Output data to the port at address BASE+2.
The definition for the final ParallelPort class is shown in Listing 5-13.
Listing 5-13 The definition for the ParallelPort class.
class ParallelPort {
private:
unsigned int BaseAddress; unsigned char InDataPort1; public:
ParallelPort();
ParallelPort(int baseaddress);
void WritePort0(unsigned char data); void WritePort2(unsigned char data); unsigned char ReadPort1();
In the class definition, a function is included for each of the requirements in the list. The definitions of the member functions are given in Listing 5-14. Additions made to the earlier ParallelPort object class are shown in bold font in Listing 5-13 and Listing 5-14.
Listing 5-14 Definitions of member functions of the class ParallelPort. ParallelPort::ParallelPort() { BaseAddress = 0x378; InDataPort1 = 0; } ParallelPort::ParallelPort(int baseaddress) { BaseAddress = baseaddress; InDataPort1 = 0; }
void ParallelPort::WritePort0(unsigned char data) {
outportb(BaseAddress,data); }
void ParallelPort::WritePort2(unsigned char data) {
// Invert bits 0, 1 and 3 to compensate for // internal inversions by printer port hardware.
outportb(BaseAddress+2, data ^ 0x0B); }
unsigned char ParallelPort::ReadPort1() {
InDataPort1 = inportb(BaseAddress+1);
// Invert most significant bit to compensate for // internal inversion by printer port hardware.
InDataPort1 ^= 0x80;
// Filter to clear unused data bits D0, D1 and D2 to zero. InDataPort1 &= 0xF8;
return InDataPort1;
TheParallelPort object class is used in the program shown in Listing 5-15 to carry out data transfer operations using all three ports of the parallel port of your PC. The operation of the program can be checked with the interface board. The connections to be made on the interface board are those given in Table 3-1 and Table 3-2. Note that before stepping through the program to test the operation of the port at address BASE+2, remove connections from the BASE address outputs to the LED Driver IC and reconnect the LED Driver IC to the BASE+2 address outputs as per Table 3-3.
Listing 5-15 Input and Output operations using ParallelPort class. /***************************************************** The object class created to use ports at addresses BASE and BASE+1 has been expanded to include output through the port at address BASE+2. The combined object class is still named ParallelPort.
*****************************************************/ #include <dos.h> #include <conio.h> #include <stdio.h> class ParallelPort { private:
unsigned int BaseAddress; unsigned char InDataPort1; public:
ParallelPort();
ParallelPort(int baseaddress);
void WritePort0(unsigned char data); void WritePort2(unsigned char data); unsigned char ReadPort1();
}; ParallelPort::ParallelPort() { BaseAddress = 0x378; InDataPort1 = 0; } ParallelPort::ParallelPort(int baseaddress) { BaseAddress = baseaddress;
InDataPort1 = 0; }
void ParallelPort::WritePort0(unsigned char data) {
outportb(BaseAddress,data); }
void ParallelPort::WritePort2(unsigned char data) {
outportb(BaseAddress+2,data ^ 0x0B); }
unsigned char ParallelPort::ReadPort1() {
InDataPort1 = inportb(BaseAddress+1);
// Inverting Most significant bit to compensate // for internal inversion by printer port hardware.
InDataPort1 ^= 0x80;
// Filter to clear unused data bits D0, D1 and D2 to zero. InDataPort1 &= 0xF8;
return InDataPort1;
}
void main() {
unsigned char BASE1Data;
ParallelPort OurPort;
OurPort.WritePort0(0x55);
printf("\n\nData sent to Port at BASE\n"); getch();
BASE1Data = OurPort.ReadPort1();
printf("\nData read from Port at BASE+1: %2X\n", BASE1Data); getch();
OurPort.WritePort2(0x00);
printf("\nData sent to Port at BASE+2\n"); getch();
}
The first line of the main() function’s body instantiates one object of type unsigned char named BASE1Data used to store data read from the port at
address BASE+1. The next line calls the default constructor of the object class ParallelPort to instantiate the object named OurPort. This object has member data and member functions to write or read data to and from all three ports of the parallel port.
The remaining statements of the main() function carry out a number of output and input operations. The getch() functions are used to make the program wait for a key press to allow the user to read the screen. If the getch() statements were omitted, the screen would scroll up or revert back to the IDE before the user could see the results. The getch() function is not a member function of the objectOurPort. Therefore, it is not attached to this object and as such is called as a normal function.
5.8 Summary
At the start of this chapter we developed an object class with the name ParallelPort. This class contained only sufficient data members and member functions to give us basic use of the port. We applied particular access attributes to the class members and explained the importance of making proper use of these access attributes.
Several programs were used to explain the relationship between multiple constructors and the default constructor. The ParallelPort class was then expanded to include use of the BASE+1 and BASE+2 addresses. The operation of objects instantiated from this expanded class was demonstrated using a program which transferred data to and from the interface board. Now that we have a fully functioningParallelPort class, we will be able to use it extensively in future chapters.
5.9 Bibliography
Borland,Borland C++ Getting Started, Borland International, 1991. Winston, P.H., On to C++, Addison Wesley, 1994.
Lipman, S.B., C++ Primer, Addison Wesley, 1991.
Dench, D. and B. Prior, Introduction to C++, Chapman and Hall, 1994. Etter, D.M., Introduction to C++ - For Engineers and Scientists, Prentice Hall, 1997.