By Jose Mojica
Price: $34.95 USD
£24.95 GBP
Cover | Table of Contents | Colophon
GoSub 1000. The code
at line 1000 would then execute and, after it was done, it would
issue the Return command. So programmers would end
up with code such as the following:
100 REM This is my GWBasic program 110 Dim A As Integer, VL As Integer, VH As Integer, bC As Integer 120 Print "Welcome to Widgets USA Order System" 130 Print "Select from the menu options below..." 140 Print "1. Enter Order" 150 Print "2. List Orders" 160 Print "3. Delete Order" 170 Print "" 180 Input "Enter number 1-3",A 190 REM Let's check that the input was valid 200 VL = 1 : VH = 3: Gosub 1000 210 If bC = 1 Then Goto 500 220 Print "Incorrect menu option, please try again" 230 Goto 180 500 REM Continue executing program 999 END 1000 REM This piece of code checks that we have a valid entry 1010 If A >=VL or A<=VH Then bC=1 Else bC = 0 1020 Return
bC = 1 if the
entry was valid or bC = 0 if the entry was
invalid.
IAccount. The IAccount
class will have the following code:
' Class IAccount Public Property Get Balance( ) As Currency End Property Public Sub MakeDeposit(ByVal Amount As Currency) End Sub
2 -
PublicNotCreatable. (The Instancing property is available
only when creating an ActiveX EXE, ActiveX DLL, or ActiveX Control
project).
Dim Acct As IAccount
IAccount does not
implement the functions Balance and MakeDeposit. It simply provides a
definition for the functions. Therefore, we must set the Acct
variable to point to an object that implements the functions of
' Class CSavings
Option Explicit
Implements IAccount
Private m_cBalance As Currency
Private m_dInterest As Double
Private Property Get IAccount_Balance( ) As Currency
IAccount_Balance = m_cBalance
End Property
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
m_cBalance = m_cBalance + Amount + (Amount * m_dInterest)
End Sub
IAccount.
Both the CChecking class and the CSavings classes implement the
IAccount interface. What may not be readily
apparent is that to use the CSavings class, all you need to do is
change the line that creates an instance of the object, as follows:
Dim Acct As IAccount Set Acct = New CSavings Dim balance As Currency balance = Acct.Balance
ISaveToDisk. The purpose of this second interface
is to save the balance information to a file. To define this new
interface, you would add another class to your project and name it
ISaveToDisk. Then you would change the instancing
property of this class to 2 -
PublicNotCreatable and add a function to your
class without any code, as follows:
' Class ISaveToDisk Option Explicit Public Sub Save( ) End Sub
' Class CChecking
Option Explicit
Implements IAccount
Implements ISaveToDisk
Private m_cBalance As Currency
Private Property Get IAccount_Balance( ) As Currency
IAccount_Balance = m_cBalance
End Property
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
m_cBalance = m_cBalance + Amount
End Sub
Private Sub ISaveToDisk_Save( )
'the implementation of this function is left as an exercise
'for the reader
End Sub
If TypeOf [object] Is [interface] Then End If
PublicNotCreatable.
Implements
statement. Once you have used the
Implements statement, the code window will list
the interface class as an object in the object drop-down list. Select
it and then select each function of the interface in the function
list drop-down. Once you have added each property and function in the
interface to your implementation class, then you write code for each
of the methods of the interface. This is all that needs to be done by
the server.
TypeOf...Is operator to find
out if a class supports a certain interface.
2. Then, you
implement both the old version of the interface and the new version
of the interface.
Enum AccountTypeConstants
Checking = 1
Savings = 2
End Enum
Type Account
AccountType As AccountTypeConstants
Active As Boolean
Balance As Currency
End Type
AccountTypeConstants, defined above the UDT. To
use this UDT, all that a user has to do is use the
Dim
statement and
then set the members of the UDT, as shown in the following code
fragment:
Dim Acct As Account Acct.AccountType = Checking Acct.Active = True Acct.Balance = 1000
New
keyword. This is because VB allocates
the memory for the UDT at the time that it encounters the
Dim statement.
Enum AccountTypeConstants
Checking = 1
Savings = 2
End Enum
Type Account
AccountType As AccountTypeConstants
Active As Boolean
Balance As Currency
End Type
AccountTypeConstants, defined above the UDT. To
use this UDT, all that a user has to do is use the
Dim
statement and
then set the members of the UDT, as shown in the following code
fragment:
Dim Acct As Account Acct.AccountType = Checking Acct.Active = True Acct.Balance = 1000
New
keyword. This is because VB allocates
the memory for the UDT at the time that it encounters the
Dim statement.
Len(Acct) returns 14 bytes. The 14 bytes come from
4 bytes for the AccountType member, 2 bytes for the Active member,
and 8 bytes for the Balance member. However, because of VB's
4-byte alignment, we need to adjust
the Active member to 4 bytes (each member needs to occupy memory in
multiples of 4 bytes). This means that the structure really requires
16 bytes of memory. In fact, VB's LenB
function returns the exact number of
bytes (16) that the UDT requires.
' Class CChecking
Option Explicit
Private m_cBalance As Currency
Public Property Get Balance( ) As Currency
Balance = m_cBalance
End Property
Public Sub MakeDeposit(ByVal Amount As Currency)
m_cBalance = m_cBalance + Amount
End Sub
Dim Acct As CChecking Set Acct = New CChecking Call Acct.MakeDeposit(5000) Set Acct = Nothing
Dim Acct As
CChecking. In
the next line, VB does a couple of things. Although it appears that
the New
keyword causes VB to allocate the memory
for the object on the client side, you have learned that this is not
the case with the COM mechanism (in fact, this is not a good
technique when it comes to upgrading objects where there might be two
different definitions, one in the client and one in the server).
Therefore, this code causes the server to allocate memory for the
object. At that point, VB creates a vtable for the CChecking class.
The entries in the vtable are pointers to the public functions of the
class. The memory layout of the _CChecking
interface contains the vptr in the first 4 bytes, which tells the
object where in memory the vtable exists. The memory address of the
object itself is saved in the 4 bytes allocated for the Acct
variable. At this point, the Acct variable points to the
_CChecking interface in memory. The VB compiler
knows that the Acct variable refers to a COM object and that,
therefore, the variable points to a vptr to a vtable.
IAccount, and implementing the interface in the
object. The following code shows how a developer might do this:
' Class IAccount
Option Explicit
Public Property Get Balance( ) As Currency
End Property
Public Sub MakeDeposit(ByVal Amount As Currency)
End Sub
' Class CChecking
Option Explicit
Implements IAccount
Private m_cBalance As Currency
Private Property Get IAccount_Balance( ) As Currency
Balance = m_cBalance
End Property
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
m_cBalance = m_cBalance + Amount
End Sub
Dim Acct As IAccount Set Acct = new CChecking Call Acct.MakeDeposit(5000) Set Acct = Nothing
_CChecking default interface. As it is right now,
there are no public functions in CChecking. Nonetheless, if there
were, then they would be part of a vtable. When VB sees the
Implements statement in the server code, VB
allocates a separate vtable to contain the public functions for the
IAccount interface. This is the vtable that has
the Balance and MakeDeposit
function entries. The addresses of the functions in this vtable point
(in essence) to the implementation code in the CChecking class. There
are now two vptrs floating around in memory: one for the
_CChecking vtable and one for the
IAccount vtable. Now it is not so simple to map
the client variable to the correct vtable. It takes a little
teamwork.
IUnknown interface. In fact, the true definition
of a COM object is an object that implements the
IUnknown interface. IUnknown
enables us to do two main tasks. First, it gives us a mechanism by
which we can accomplish polymorphism (through
QueryInterface). Second, it gives us two methods
(AddRef and Release) by which we can do reference counting.
IDispatch. IDispatch enables a
client to ask the server to execute a method on its behalf using its
name. A client using IDispatch first asks for a
DispID for a certain method using the
GetIDsOfNames function. Then, it calls the
Invoke function to execute the method. VB
automatically adds support for IDispatch to every
COM object. In fact, every interface created in VB is a dual
interface. A dual interface is one that is derived from
IDispatch, instead of directly from
IUnknown.
IUnknown interface (henceforth referred to as
QI ).
Most important was the idea that all
interfaces are created equal. In other words, the memory layout of an
interface in any language is basically the same—it is a virtual
table pointer (vptr) pointing to a virtual table (vtable). A vtable
is nothing more than an array of pointers to the addresses of
functions in memory. COM rules state that the first three functions
in the vtable of a COM interface must be the methods of
IUnknown: QueryInterface,
AddRef, and Release. You
learned from Chapter 3 that Visual Basic built a
little object to manage the IUnknown
implementation for the entire object. You also learned some of the
COM rules for allocating memory in the last chapter. One rule
discussed in the chapter was that memory for COM objects must be
allocated by the server code. You also learned that the server
provides the definitions of its COM objects for the client through a
file called a type library.
Dim Account As IAccount Set Account = New CChecking Call Account.MakeDeposit(5000)
Set Account = New CChecking.
However, before we go into too much detail on the activation process,
let's see how it is that COM components are packaged and used
from a client application at a high level.
IAccount, and add the following code:
Option Explicit Public Property Get Balance( ) As Currency End Property Public Sub MakeDeposit(ByVal Amount As Currency) End Sub
2 -
PublicNotCreatable. Add a second class module to your
project using the Project → Add Class Module menu option. Change
the name of the class to CChecking and enter the following code in
the module:
Option Explicit
Implements IAccount
Private m_balance As Currency
Private Property Get IAccount_Balance( ) As Currency
IAccount_Balance = m_balance
End Property
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
m_balance = m_balance + Amount
End Sub
Implements
IAccountinput parameter. The second parameter is an
out parameter returned by the object with a
pointer to the vptr of the requested interface.
IAccount is not unique enough for
QI to work for every interface ever defined.
Think about how many companies writing COM-based banking applications
may use the name IAccount for their primary
interface definition.
QueryInterface
would not know if you were asking for company A's
IAccount or company B's
IAccount. Therefore, Microsoft decided to use
another mechanism for naming interfaces. Instead of using the string
name, COM identifies each interface by a number—a very big
number
known
as a globally unique identifier, or GUID.
HKEY_CLASSES_ROOT,
HKEY_CLASSES_ROOT\CLSID,
HKEY_CLASSES_ROOT\TypeLib,
and
HKEY_CLASSES_ROOT\Interface. If you look at the
registry with
Regedit.exe,
you will notice that the registry hierarchy is divided into four main
trees. Microsoft has suggested that the
HKEY_CLASSES_ROOT tree in the registry (normally
abbreviated as HKCR) should be used for storing
information about COM components. Microsoft provides an API function
called
New,
VB translates the New command into a call to
CoCreateInstanceEx, the function responsible for
creating instances of a class.
Sub Main, if there is one. In fact, as you
will see later, Sub
Main is a
procedure that Visual Basic runs for every thread that it launches.