Stuff About Objects, Types, and Data Structures

Code reuse is mostly about object orientation—the effective packaging of components to suit some plan or design. This section examines the mechanisms that exist in Visual Basic to effectively bring about code reuse. In particular, we'll look at how we can extend the type system in Visual Basic.

Visual Basic as an Object-Oriented Language

People often say that Visual Basic is not properly object oriented. I would answer that if you were comparing it with C++, you're both right and wrong. Yes, it isn't C++; it's Visual Basic!

C++ is a generic, cross-platform programming language designed around a particular programming paradigm that is, subjectively, object oriented. It is based on and influenced by other programming languages such as Simula, C, and C with Objects2.

Visual Basic has evolved—and has been influenced, too—according to system considerations, not primarily to different languages. It was designed to be platform specific; there's not an implementation for the VAX computer, for example. Visual Basic's object orientation, then, is not primarily language based. Its object-oriented language constructs are not there to implement object orientation directly but rather to best utilize the object-oriented features of the operating system—in Windows, of course, this means ActiveX.

ActiveX itself, however, is not a language definition but a systems-level object technology built directly into a specific range of operating systems. It is not subject to committees either, although you might consider this to be a negative point. Additionally, I think I'd call ActiveX and Visual Basic "commercial," whereas I'd probably call C++ "academic." I have nothing against C++ or any other programming language. Indeed, I'm a proponent of mixed-language programming and use C++ almost daily. What I am against, however, is a comparison of Visual Basic with C++. These two languages are as different as COBOL and FORTRAN, and both were designed to solve different problems and to cater to different markets. This all said, I'm still keen to model the world realistically in terms of objects, and I also want to encourage both high cohesion and loose coupling between and within those objects. (Here's a quick tip for the latter: Use ByVal—it helps!) Whether or not I achieve this cohesion and coupling with Visual Basic and ActiveX is the real question.

Cohesion and coupling

cohesion and coupling. A component is said to be cohesive if it exhibits a high degree of functional relatedness with other related components. These related components (routines typically) should form cohesive program units (modules and classes). Every routine in a module should, for example, be essential for that module to accomplish its purpose. Generally, there are seven recognized levels of cohesion (none of which I'll cover here). Coupling is an indication of the strength of the interconnections and interactions exhibited by different program components. If components are strongly coupled, they obviously have a dependency on each other—neither can typically work without the other, and if you break one of the components, you'll invariably break the others that are dependent upon it. In Visual Basic, tight coupling typically comes about through the overuse and sharing of public symbols (variables, constants, properties, and routines exported by other units).

What are your intentions?

Having an object implies intention; that is, you're about to do something with the object. This intention should, in turn, define the object's behavior and its interfaces. Indeed, a strong type system implies that you know what you'll do with an object when you acquire one. After all, you know what you can do with a hammer when you pick one up! A sense of encapsulation, identity, and meaning is an obvious requirement. To add both external and procedural meaning to an object, you need to be able to add desirable qualities such as methods and properties. What does all this boil down to in Visual Basic? The class, the interface, and the object variable.

Classes are essentially Visual Basic's way of wrapping both method, which is ideally the interface, and state—that is, providing real type extensions (or as good as it gets currently). A real type is more than a description of mere data (state); it also describes the set of operations that can be applied to that state (method). Unfortunately, methods are currently nonsymbolic. One feature that C++ has that I'd love to have in Visual Basic is the ability to define symbolic methods that relate directly to a set of operators. With this ability, I could, for example, define what it means to literally add a deposit to an account using the addition operator (+). After all, the plus sign is already overloaded (defined for) in Visual Basic. For example the String type supports addition. Also, a solution will have nothing to do with ActiveX; the ability to define symbolic methods is a mere language feature.

Visual Basic "inheritance"

Visual Basic lacks an inheritance mechanism (definitely more of an ActiveX constraint) that is comparable with that of C++. To reuse another object's properties in Visual Basic, you must use something else—either composition or association (composition meaning aggregation—like in a UDT; association meaning, in Visual Basic-speak, an object reference). Historically, association is "late" composition—as a result it also invalidates strong typing. A Visual Basic object cannot, in the C++ sense, be a superclass of another type. In other words, you cannot describe a PC, say, as being either a specialization of or composed of a CPU.

NOTE
By the way, C++ type inheritance is often used badly; that is, an object that inherits from other objects exports their public interfaces. The result is that top-level objects are often bloated because they are the sum of all the public interfaces of the objects they are derived from—a sort of overprivileged and overfed upper class!

Components that are highly cohesive, yet loosely coupled, are more easily shared—if code reuse is an issue, consider rigorously promoting both of these simple philosophies.

Object polymorphism

Polymorphism is a characteristic by which an object is able to respond to stimuli in accordance with its underlying object type rather than in accordance with the type of reference used to apply the stimulus. The Implements statement, discussed in the next section, is Visual Basic's way of extending polymorphic types beyond that allowed by the Object type. Polymorphic types might be assigned (or "set") to point to and use one another, and they're useful when you know that one type has the same interface as another type (one you'd like to treat it as). The really important thing is that with polymorphism, an object responds according to its type rather than the type of reference you have to it.

Let me give you an example using two constructs that are probably familiar to you, the Object type and a window handle.

What is the result of this code?

 Dim o As Object Set o = frmMainForm MsgBox o.Caption 

You can see here that the Caption property evaluation is applied to what the pointer/object reference o points to rather than according to what Object.Caption means. That is, that object to which we bind the Caption access to is decided in the context of what o is set to when o.Caption is executed. (We call this behavior late binding, by the way.) Notice that the code doesn't error with a message that says, "The Object class doesn't support this property or method." Polymorphism says that an object responds as the type of object it is rather than according to the type of the reference we have to it. Again, notice that I didn't have to cast the reference, (using a fabricated cast operator that you might take at first glance to be an array index), like this one, for instance:

CForm(o).Caption

The object o knows what it is (currently) and responds accordingly. Obviously, we can alter what o points to:

 Dim o As Object If blnUseForm = True Then     Set o = frmMainForm Else     Set o = cmdMainButton End If MsgBox o.Caption 

Again, o.Caption works in either case because of polymorphism.

The second example is a window handle. This window handle is something like the object reference we used above, meaning it's basically a pointer that's bound to an object at run time. The object, of course, is a Windows' window—a data structure maintained by the operating system, an abstract type. You can treat an hWnd in a more consistent fashion than you can something declared As Object, however. Basically you can apply any method call to hWnd and it'll be safe. You're right in thinking that windows are sent messages, but the message value denotes some action in the underlying hWnd. Therefore, we can think of a call to SendMessage not as sending a window a message but rather as invoking some method, meaning that we can treat SendMessage(Me.hWnd, WM_NULL, 0, 0) as something like hWnd.WM_NULL 0, 0. The WM_NULL (a message you're not meant to respond to) is the method name, and the 0, 0 are the method's parameters. All totally polymorphic—an hWnd value is a particular window and it will respond accordingly.

Another similarity between the window handle and the Object type is that a window is truly an abstract type (meaning that you cannot create one without being specific), and so is the Object type. You can declare something As Object— though now it seems what I've just said is not true—but what you've done, in fact, is not create an Object instance but an instance of a pointer to any specific object (an uninitialized object reference). It's like defining a void pointer in C. The pointer has potential to point somewhere but has no implementation and therefore no sense in itself. It's only meaningful when it's actually pointing to something!

I hope you can see that polymorphism is great for extending the type system and for being able to treat objects generically while having them respond according to what they actually are. Treating objects as generically as possible is good; specialize only when you really need to.

OK, so what's the catch? Well, the problem with As-Object polymorphism is that it isn't typesafe. What would happen in my earlier example if, once I'd set o to point to the cmdMainButton command button, I tried to access the WindowState property instead of the Caption property? We'd get an error, obviously, and that's bad news all around. What we really need is a more typesafe version of Object, with which we can be more certain about what we might find at the other end of an object reference (but at the same time keeping the polymorphic behavior that we all want). Enter Implements.

Using Implements

The Implements statement provides a form of interface inheritance. A class that uses the Implements statement inherits the interface that follows the Implements keyword but not, by some magic, an implementation of that interface. A class is free to define code for the inherited interface methods, or the class can choose to leave the interface methods as blank stubs. On the other side of the Implements equation, we can define an interface that other classes can inherit but have nothing to do with a particular implementation.

When you inherit an interface by using the Implements statement, you're providing methods and properties with certain names on an object. Now, when your containing class is initialized, it should create a suitable implementation of these promised methods and properties. This can happen in one of two ways:

  1. Pass method and property invocations on to the underlying implementation (by creating and maintaining a local copy of an object that actually implements the methods and properties). This mechanism is often called forwarding.

  2. Handle the invocation entirely by itself.

How does this differ from As-Object polymorphism? Basically, when you set a typed object reference to point to an object, the object instance to which you set it must implement the interfaces specified by the type of the object reference you use in the declaration. This adds an element of type safety to the dynamic typecast, which is what you're implicitly doing, of course.

When you choose not to implement an interface other than by forwarding requests to the underlying base type (the thing you've said you implement) you can get these problems:

  1. The derived class now acts like a base class—you've just provided a pass-through mechanism.

  2. The base object reference is made public in the derived class (by accident), and because you cannot declare Const references, the reference to your implementation might be reassigned. (Actually, it's just as easy to do this via composition in Visual Basic.)

A consequence of the second problem is that you can accidentally "negate" the reference to the base object. Say, for argument's sake, that you set it to point to a Form; clearly the derived class no longer implements a base but a Form. Better hope your method names are different in a base and a Form or you're in for a surprise!

It's important to realize that Implements can be used with forwarding or via composition. The only difference in Visual Basic is the keyword New—in fact, it's even grayer than that. In class Derived, does the following code mean we're using forwarding or composition?

Implements Base Private o As New Base 

Do we contain a Base object or do we simply have a variable reference to a Base instance? Yes, it's the latter. In Visual Basic you cannot compose an object from others because you cannot really define an object—only an object reference.

Let me summarize this statement and the meaning of Implements. When you're given an object reference you have to consider two types: the type of the reference and the type of the object referenced (the "reference" and the "referent"). Implements ensures that the type of the referent must be "at least as derived as" the type of the reference. In other words, if a Set works, you have at least a reference type referent object attached to the reference. This leads us to the following guarantee: If the class of the reference has the indicated method or property (Reference_Type.Reference_Method), then the object reference—the referent—will have it, too.

Delegating objects

Delegating objects consists of two parts. Part one deals with who responds—through my interface I might get an actual implementor of the interface (the object type I have said that I implement) to respond (possibly before I respond), or I might elect to generate the whole response entirely by myself. Part two deals with who is responsible for an object; this is used in conjunction with association. Two containers might deal with a single provider object at various times. This use of association raises the question of object ownership (which container should clean up the object, reinitialize and repair it, and so forth).

Object orientation is modeling the requirements. Defining the requirements therefore dictates the object model and implementation method you'll employ. You can build effective sets of objects in Visual Basic, but you cannot do today all of what is possible in C++. As I said earlier, Visual Basic and C++ are two different languages, and you should learn to adapt to and utilize the strengths of each as appropriate.

Using Collections to Extend the Type System

You can also extend the type system ("type" meaning mere data at this point). At TMS, we often use a Collection object to represent objects that are entirely entities of state; that is, they have no methods. (You cannot append a method to a collection.) See Chapter 1 for more on building type-safe versions of Visual Basic's intrinsic types (called Smarties).

Dim KindaForm As New Collection      Const pHeight As String = "1" Const pWidth  As String = "2" Const pName   As String = "3" ' ... With KindaForm     .Add Key:=pHeight, Item:=Me.Height     .Add Key:=pWidth, Item:=Me.Width     .Add Key:=pName, Item:=Me.Name End With ' ... With KindaForm     Print .Item(pHeight)     Print .Item(pWidth)     Print .Item(pName) End With 

Here we have an object named KindaForm that has the "properties" pHeight, pWidth, and pName. In other words, an existing Visual Basic type (with both properties and method) is being used to create a generic state-only object. If you're using classes to do this, you might want to consider using Collection objects as shown here instead.

You can add functional members to a Collection object with just one level of indirection by adding an object variable to the collection that is set to point to an object that has the necessary functionality defined in it. Such methods can act on the state in the other members of the collection.

So what's the difference between using a collection and creating a user-defined type (UDT)? Well, a collection is more flexible (not always an advantage) and has support for constructs such as For Each:

 For Each v In KindaForm     Print v Next 

The advantage of UDTs is that they have a known mapping. For example, they can be used as parameters to APIs, sent around a network and passed between mainframe and PC systems—they are just byte arrays. (See Chapter 4 for more on UDTs—they're one of Jon Burns's favorite things!) Obviously, a state-only Collection object doesn't mean much to a mainframe system, and passing KindaForm as "the thing" itself will result in your only passing an object pointer to a system that cannot interpret it. (Even if it could, the object would not be available because it's not transmitted with its address.)

Adding to VarType

Another "byte array" way to extend the type system is to add in new Variant types. In Visual Basic 5, the following subtypes were available via the Variant:

Visual Basic Name VarType Description
vbEmpty 0 Uninitialized (default)
vbNull 1 Contains no valid data
vbInteger 2 Integer
vbLong 3 Long integer
vbSingle 4 Single-precision floating-point number
vbDouble 5 Double-precision floating-point number
vbCurrency 6 Currency
vbDate 7 Date
vbString 8 String
vbObject 9 Automation object
vbError 10 Error
vbBoolean 11 Boolean
vbVariant 12 Variant (used only for arrays of Variants)
vbDataObject 13 Data access object
vbDecimal 14 Decimal
vbByte 17 Byte
vbArray 8192 Array

In Visual Basic 6, we have a new addition (and a great deal of scope for adding more—a lot of gaps!):

   vbUserDefinedType 36 User-defined type 

With some limitations, we can add to this list. For example, we could, with only a small amount of effort, add a new Variant subtype of 42 to represent some new entity by compiling this C code to a DLL named NEWTYPE.DLL:

#include "windows.h" #include "ole2.h" #include "oleauto.h" #include <time.h> typedef VARIANT * PVARIANT; VARIANT  __stdcall CVNewType(PVARIANT v) {     // If the passed Variant is not set yet...     if (0 == v->vt)     {         // Create new type.         v->vt = 42;         // Set other Variant members to be meaningful          // for this new type...         // You do this here!     }     // Return the Variant, initialized/used Variants      // unaffected by this routine.     return *v; } int  __stdcall EX_CInt(PVARIANT v) {     // Sanity check - convert only new Variant types!     if (42 != v->vt)     {         return 0;     }     else     {         // Integer conversion - get our data and convert it as          // necessary.         // Return just a random value in this example.         srand((unsigned)time(NULL));         return rand();     } } 

This code provides us with two routines: CVNewType creates, given an already created but empty Variant (it was easier), a Variant of subtype 42; EX_CInt converts a Variant of subtype 42 into an integer value (but doesn't convert the Variant to a new Variant type). "Converts" here means "evaluates" or "yields". Obviously, the implementation above is minimal. We're not putting any real value into this new Variant type, and when we convert one all we're doing is returning a random integer. Nevertheless, it is possible! Here's some code to test the theory:

Dim v As Variant      v = CVNewType(v) Me.Print VarType(v)     Me.Print EX_CInt(v) 

This code will output 42 and then some random number when executed against the DLL. The necessary DLL declarations are as follows:

Private Declare Function CVNewType Lib "NEWTYPE.DLL" _     (ByRef v As Variant) As Variant Private Declare Function EX_CInt   Lib "NEWTYPE.DLL" _     (ByRef v As Variant) As Integer 

Again, we cannot override Visual Basic's CInt , and so I've had to call my routine something other than what I wanted to in this case, EX_CInt for "external" CInt. I could, of course, have overloaded Val:

Public Function Val(ByRef exp As Variant) As Variant     Select Case VarType(exp)                  Case 42:   Val = EX_CInt(exp)         Case Else: Val = VBA.Conversion.Val(exp)          End Select End Function 

Here, if the passed Variant is of subtype 42, I know that the "real" Val won't be able to convert it—it doesn't know what it holds after all—so I convert it myself using EX_CInt. If, however, it contains an old Variant subtype, I simply pass it on to VBA to convert using the real Val routine.

Visual Basic has also been built, starting with version 4, to expect the sudden arrival of Variant subtypes about which nothing is known. This assertion must be true because Visual Basic 4 can be used to build ActiveX servers that have methods. In turn, these can be passed Variants as parameters. A Visual Basic 5 client (or server) can be utilized by a Visual Basic 6 client! In other words, because a Visual Basic 6 executable can pass in a Variant of subtype 14, Visual Basic must be built to expect unknown Variant types, given that the number of Variant subtypes is likely to grow at every release. You might want to consider testing for this in your Visual Basic 4 code.

Having said all this and having explained how it could work, I'm not sure of the real value currently of creating a new Variant subtype. This is especially true when, through what we must call a feature of Visual Basic, not all the conversion routines are available for subclassing. Why not use a UDT, or better still, a class to hold your new type instead of extending the Variant system?

Another limitation to creating a new Variant subtype is because of the way we cannot override operators or define them for our new types. We have to be careful that, unlike an old Variant, our new Variant is not used in certain expressions. For example, consider what might happen if we executed Me.Print 10 + v. Because v is a Variant, it needs to be converted to a numeric type to be added to the integer constant 10. When this happens, Visual Basic must logically apply VarType to v to see what internal routine it should call to convert it to a numeric value. Obviously, it's not going to like our new Variant subtype! To write expressions such as this, we'd need to do something like Me.Print 10 + Val(v). This is also the reason why, in the Val substitute earlier, I had to pass exp by reference. I couldn't let Visual Basic evaluate it, even though it's received as a Variant.

Variants also might need destructing correctly. When they go out of scope and are destroyed, you might have to tidy up any memory they might have previously allocated. If what they represent is, say, a more complex type, we might have to allocate memory to hold the representation.

Microsoft does not encourage extending the Variant type scheme. For example, 42 might be free today, but who knows what it might represent in Visual Basic 7. We would need to bear this in mind whenever we created new Variant subtypes and make sure that we could change their VarType values almost arbitrarily—added complexity that is, again, less than optimal!

All in all, creating new Variant subtypes is not really a solution at the moment. If we get operator overloading and proper access to VBA's conversion routines, however, all of this is a little more attractive.

NOTE
The code to create Variant subtypes needs to be written in a language such as C. The main reason is that Visual Basic is too type safe and simply won't allow us to treat a Variant like we're doing in the DLL. In other words, accessing a Variant in Visual Basic accesses the subtype's value and storage transparently through the VARIANT structure. To access its internals, it's necessary to change the meaning of Variant access from one of value to one of representation.

Pointers

A common criticism of Visual Basic is that it doesn't have a pointer type. It cannot therefore be used for modeling elaborate data types such as linked lists. Well, of course, Visual Basic has pointers—an object variable can be treated as a pointer. Just as you can have linked lists in C, so you can have them in Visual Basic.

Creating a linked list

Let's look at an example of a circular doubly linked list where each node has a pointer to the previous and next elements in the list, as shown in Figure 7-2. Notice in the code that we have a "notional" starting point, pHead, which initially points to the head of the list.

Figure 7-2 A node in the list

The Node Class

Option Explicit ' "Pointers" to previous and next nodes. Public pNext As Node Public pPrev As Node ' Something interesting in each node -  ' the creation number (of the node)! Public nAttribute As Integer Private Sub Class_Initialize()     Set pNext = Nothing     Set pPrev = Nothing End Sub Private Sub Class_Terminate()     ' When an object terminates, it will already have     ' had to set these two members to Nothing:     ' this code, then, is slightly redundant.     Set pNext = Nothing     Set pPrev = Nothing End Sub 

The Test Form

Option Explicit Private pHead   As New Node Private pV      As Node Public Sub CreateCircularLinkedList()     Dim p           As Node     Dim nLoop       As Integer     Static pLast    As Node    ' Points to last node created                                 ' pHead if first node.          pHead.nAttribute = 0     Set pLast = pHead     ' 501 objects in list - the pHead object exists     ' until killed in DeleteList.     For nLoop = 1 To 501                  Set p = New Node                  p.nAttribute = nLoop                  Set pLast.pNext = p         Set p.pPrev = pLast                  Set pLast = p              Next          ' Decrement reference count on object.     Set pLast = Nothing          ' Join the two ends of the list, making a circle.     Set p.pNext = pHead     Set pHead.pPrev = p          Exit Sub End Sub Public Sub PrintList()     Debug.Print "Forwards"     Set pV = pHead          Do         Debug.Print pV.nAttribute              Set pV = pV.pNext              Loop While Not pV Is pHead               Debug.Print "Backwards"          Set pV = pHead.pPrev          Do         Debug.Print pV.nAttribute              Set pV = pV.pPrev              Loop While Not pV Is pHead.pPrev End Sub Public Sub DeleteList()          Dim p As Node          Set pV = pHead          Do         Set pV = pV.pNext         Set p = pV.pPrev                  If Not p Is Nothing Then             Set p.pNext = Nothing             Set p.pPrev = Nothing         End If                  Set p = Nothing          Loop While Not pV.pNext Is Nothing     ' Both of these point to pHead at the end.         Set pV = Nothing     Set pHead = Nothing End Sub 

The routines CreateCircularLinkedList, PrintList, and DeleteList should be called in that order. I have omitted building in any protection against deleting an empty list. To keep the example as short as possible, I've also excluded some other obvious routines, such as -InsertIntoList.

In Visual Basic, a node will continue to exist as long as an object variable is pointing to it (because a set object variable becomes the thing that the node is set to). For example, if two object variables point to the same thing, an equality check of one against the other (using Is) will evaluate to True (an equivalence operator). It follows, then, that for a given object all object variables that are set to point to it have to be set to Nothing for it to be destroyed. Also, even though a node is deleted, if the deleted node had valid pointers to other nodes, it might continue to allow other nodes to exist. In other words, setting a node pointer, p, to Nothing has no effect on the thing pointed to by p if another object variable, say, p1, is also pointing to the thing that p is pointing to. This means that to delete a node we have to set the following to Nothing: its pPrev object's pNext pointer, its pNext object's pPrev pointer, and its own pNext and pPrev pointers (to allow other nodes to be deleted later). And don't forget the object variable we have pointing to p to access all the other pointers and objects. Not what you might expect!

It's obvious that an object variable can be thought of as a pointer to something and also as the thing to which it points. Remember that Is should be used to compare references, not =. This is why we need Set to have the variable point to something else; that is, trying to change the object variable using assignment semantically means changing the value of the thing to which it points, whereas Set means changing the object variable to point elsewhere. In fact nearly any evaluation of an object variable yields the thing to which the object variable is pointing to. An exception is when an object variable is passed to a routine as a parameter, in which case the pointer is passed, not the value (the object) that it's pointing to. (The object also has an AddRef applied to it.)

Linked lists that are created using objects appear to be very efficient. They are fast to create and manipulate and are as flexible as anything that can be created in C.

Visual Basic 6 (VBA) is also able to yield real pointers, or addresses. Three undocumented VBA methods—VarPtr, ObjPtr, and StrPtr (which are just three different VBA type library aliases pointing to the same entry point in the run-time DLL)—are used to create these pointers. You can turn an object into a pointer value using l = ObjPtr(o), where o is the object whose address you want and l is a long integer in which the address of the object is put. Just resolving an object's address doesn't AddRef the object, however. You can pass this value around and get back to the object by memory copying l into a dummy object variable and then setting another object variable to this dummy (thus adding a reference to the underlying object).

Call CopyMemory(oDummy, l, 4) Set oThing = oDummy 

CopyMemory should be defined like this:

Private Declare Sub CopyMemory Lib "kernel32" _     Alias "RtlMoveMemory" (pDest As Any, pSource As Any, _     ByVal ByteLen As Long) 

The really neat thing here is that setting l doesn't add a reference to the object referenced by the argument of ObjPtr. Normally, when you set an object variable to point to an object, the object to which you point it (attach it, really) has its reference count incremented, meaning that the object can't be destroyed, because there are now two references to it. (This incrementing also happens if you pass the object as a parameter to a routine.) For an example of how this can hinder your cleanup of objects, see the discussion of the linked list example.

By using VarPtr (which yields the address of variables and UDTs), StrPtr (which yields the address of strings), and ObjPtr, you can create very real and very powerful and complex data structures.

Here's the short piece of code I used to discover that VarPtr, ObjPtr, and StrPtr are all pretty much the same thing (that is, the same function in a DLL):

Private Sub Form_Load()     ' VB code to dump or match an external     ' server method with a DLL entry point. Here it's     ' used to dump the methods of the "_HiddenModule".     ' Add a reference to 'TypeLib Information' (TLBINF32.DLL),     ' which gives you TLI before running this code.     Dim tTLInfo  As TypeLibInfo     Dim tMemInfo As MemberInfo     Dim sDLL     As String     Dim sOrdinal As Integer     Set tTLInfo = _         TLI.TLIApplication.TypeLibInfoFromFile("MSVBVM50.DLL")     For Each tMemInfo In _         tTLInfo.TypeInfos.NamedItem("_HiddenModule").Members         With tMemInfo             tMemInfo.GetDllEntry sDLL, "", sOrdinal             ' labDump is the label on the form where the             ' output will be printed.             labDump.Caption = labDump.Caption & _                     .Name & _                     " is in " & _                     sDLL & _                     " at ordinal reference " & sOrdinal & _                     vbCrLf         End With     Next End Sub 

The code uses TLBINF32.DLL, which can interrogate type libraries (very handy). Here I'm dumping some information on all the methods of a module (in type library parlance) named _HiddenModule. You'll see that this is the module that contains VarPtr, ObjPtr, and StrPtr, which you can discover using OLEVIEW.EXE to view MSVBVM60.DLL:

module _HiddenModule {         [entry(0x60000000), vararg, helpcontext(0x000f6c9d)]         VARIANT _stdcall Array([in] SAFEARRAY(VARIANT)* ArgList);         [entry(0x60000001), helpcontext(0x000f735f)]         BSTR _stdcall _B_str_InputB(                         [in] long Number,                          [in] short FileNumber);         [entry(0x60000002), helpcontext(0x000f735f)]         VARIANT _stdcall _B_var_InputB(                         [in] long Number,                          [in] short FileNumber);         [entry(0x60000003), helpcontext(0x000f735f)]         BSTR _stdcall _B_str_Input(                         [in] long Number,                          [in] short FileNumber);         [entry(0x60000004), helpcontext(0x000f735f)]         VARIANT _stdcall _B_var_Input(                         [in] long Number,                          [in] short FileNumber);         [entry(0x60000005), helpcontext(0x000f65a4)]         void _stdcall Width(                         [in] short FileNumber,                          [in] short Width);         [entry(0x60000006), hidden]         long _stdcall VarPtr([in] void* Ptr);         [entry(0x60000007), hidden]         long _stdcall StrPtr([in] BSTR Ptr);         [entry(0x60000008), hidden]         long _stdcall ObjPtr([in] IUnknown* Ptr);     }; 

When you run the Visual Basic code, you'll see this output:

Label1Array   is in VBA5.DLL at ordinal reference 601 _B_str_InputB is in VBA5.DLL at ordinal reference 566 _B_var_InputB is in VBA5.DLL at ordinal reference 567 _B_str_Input  is in VBA5.DLL at ordinal reference 620 _B_var_Input  is in VBA5.DLL at ordinal reference 621 Width         is in VBA5.DLL at ordinal reference 565 VarPtr        is in VBA5.DLL at ordinal reference 644 StrPtr        is in VBA5.DLL at ordinal reference 644 ObjPtr        is in VBA5.DLL at ordinal reference 644 

This output shows the method name together with the DLL and ordinal reference (into the DLL) that implements its functionality. If you use DUMPBIN /EXPORTS on MSVBVM60.DLL like this:

dumpbin /exports msvbvm60.dll > dump 

and then examine the dump file, you'll see that the routine at ordinal 644 is in fact VarPtr. In other words, VarPtr, ObjPtr, and StrPtr all do their stuff in the MSVBVM60.DLL routine VarPtr!

Matching the code output to the dump, we see this:

Method Name     DLL Routine Name Label1Array     rtcArray _B_str_InputB   rtcInputCount _B_var_InputB   rtcInputCountVar _B_str_Input    rtcInputCharCount _B_var_Input    rtcInputCharCountVar Width           rtcFileWidth VarPtr          VarPtr StrPtr          VarPtr ObjPtr          VarPtr 

I haven't explained what the other routines do—you can discover that for yourself.



Ltd Mandelbrot Set International Advanced Microsoft Visual Basics 6. 0
Advanced Microsoft Visual Basic (Mps)
ISBN: 1572318937
EAN: 2147483647
Year: 1997
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net