Deeper into Libraries

A Little History

I started using a library when I’d finally worked up enough reusable code to make it worth doing and enough applications to care about using that code in multiple places.  I have since gone through several iterations of them.

As I’ve mentioned in previous posts, I consider what I build more than “just a library”– though, of course, it is indeed a library.  The reason I say that is that I consider much of my effort to be directed towards a framework.  I first ran into a framework in the late 80s when I purchased the toolboxes from Borland to go with their Turbo Pascal.  They had a graphics toolbox, a database toolbox, etc.  I had not yet run across the term framework, at least in programming terms; but, in fact, that is what those were.  I later ran into a framework in visual FoxPro around 1996, the name of which escapes me.

A framework is more than just a bunch of modules from which you call functions.  That is classically thought of as a library.  A framework is a set of interrelated objects that work together to form a whole, and which provide some sort of higher functionality once they work together.  Think about the clsFrm which set up many instances of clsCtlTxt and clsCtlCbo so that with just a few lines of code in a form, you suddenly had apparently magical behaviors happening in the form.

From the perspective of OOP, a framework may be considered “bad”– i.e., each object works with and depends on other objects; whereas with OOP, we tend to think of each object as “needing as much as possible to stand alone”.  In fact, I still strive for that; but each object is generally only useful in the context of the whole thing.

My first stab at building a framework was before I understood VBA classes — in fact, probably before I even really knew they existed.  Remember that I had grown up in Turbo Pascal and Turbo C, so I had already experienced OOP and a framework.  However, I got sucked in to Access in the early 90s, I think 1.x or maybe a brand-new 2.0 version.  What I had never done prior was event-driven programming, and I spent the next few years learning Event Driven.  Coming from “pick a menu item and the program goes until it is done”, I had a very difficult time wrapping my mind around the concept of a button “interrupting my program” whenever a user wanted to click a button.  How could I ever possibly control stuff with users wreaking havoc with the program flow?

Anyway, somewhere along the way I got pretty good at the event driven thing, but I just didn’t even see the class thing.  Before I discovered classes, I built an entire framework using collections of collections of objects.  But the objects in the collections were usually data values or controls I was working on, not class instances.  I wrote code that I still wonder how it ever worked.  And it was really hard to understand or maintain.  And it took a lot of collections, all kept in sync to emulate what I can easily do in a class.

In fact, it was my good friend Shamil Salakhetdinov who, in 1999 or so, pushed me gently towards classes and event handling in classes.  However, it took several passes over about a year’s time, studying his code posted on his personal site, before the light bulb finally clicked and I realized that, mixed metaphors aside, there was something to this class thing.  You see, Shamil was one of the pioneers in wrapping Access controls and forms in classes and sinking the events, then doing something with it.  Unfortunately, the technology at the time wasn’t up to what he wanted to accomplish.  VBA classes and event sinks and all that was really buggy, and Shamil finally gave up in disgust and moved on to DotNet.

However, I had the good fortune to finally “get it” just as Microsoft rolled out the fixes that enabled the technology to work.  Access 2000 classes and events mostly worked, and Access 2002 completely worked.  Today, as long as you don’t have to be backwards-compatible with Access 2000, you can use Classes and WithEvents and RaiseEvent without ever experiencing the problems to be found in those earlier versions.

Down to work

OK, enough history.  Today I want to discuss how to place a class in a library and still get at it.

The problem is that classes by default are not visible outside of the Access container they are in.  There are two solutions to this problem:

  1. Build a wrapper function to instantiate a class and return a pointer to the class instance, and do not define the return type for the function.  I try very hard not to use this solution for the simple reason that  the return type is not defined.  Access returns it as type object, and therefore intellisense doesn’t work.  We can’t get the public methods and properties displayed for us as we type.  That is a critical shortcoming, in my book.  I will demonstrate how to use this method, but I do not suggest it.
  2. The second method of making a class visible outside of the container is to export it to a text file, manually modify it in a text editor, delete the original, then import the modified file back in.  While this is not a difficult procedure, it is tedious to have to do for 50 different classes in a library.  I will demonstrate this method as well and discuss it in detail since, aside from the tedium, it is nonetheless the preferred method, in my opinion.
Let’s set this thing up.  We need a library containing a few classes and a test FE to reference the library.  In the last post we created the library and named it MyFrameworkLib.MDA.  We didn’t really need to do anything to “turn it into a library”, but we did change the extension to .MDA to keep peace with Microsoft and designate that we intended this thing to be a library.  Now, in the same directory that contains MyFrameworkLib.MDA, create a new Access container and call it LibTest.mdb.  This will be an application container specifically to test our library and learn how to use a library.
Open LibTest and reference MyFrameworkLib.MDA.  OK, so you should be able to see stuff in the library, right?  And so you can by using the object explorer.
The first thing you must understand is that if you open any database that references a library, the referenced  library is immediately locked for new edits, or saves of edits in progress, for that matter.  This can cause issues with development, since we can’t simply copy new and exciting code from an FE referencing a library into the library while that FE is open.  Just remember that.
So close LibTest so that we can edit MyFrameworkLib.MDA.  Open MyFrameworkLib.MDA and create a new module and save it immediately as basSeeFromOutside.  Of course, what you call it really doesn’t matter, but I like that name.  In basSeeFromOutside insert the following code:
Option Compare Database
Option Explicit
Public IntCanBeSeen As Integer
Private intCannotBeSeenFromOutside As Integer
Public Function fCanBeSeenFromOutside() As String
    MsgBox “Hi”
End Function
Private Function fCannotBeSeenFromOutside() As String
    MsgBox “not so fast”
End Function
Close the library and open LibTest.  In LibTest create a module called Module1 (cool name again) and place the following code in it:
Option Compare Database
Option Explicit
Function mTestWhatCanBeSeen()
    IntCanBeSeen = 1
    intCannotBeSeenFromOutside = 1
End Function
Try to compile.  Notice that the first two lines of the function body compile just fine, but the second two do not.  That is to be expected, since they are private and they wouldn’t be visible even if basSeeFromOutside  were inside of LibTest.  The odd thing to me is that if you open the debug window and run the following code (put your cursor on each line and hit return) . . .
IntCanBeSeen = 1
intCannotBeSeenFromOutside = 1
. . . pretty much everything is visible.  Notice that  fCannotBeSeenFromOutside does not execute, but the compiler doesn’t compain, either.  The private variable intCannotBeSeenFromOutside is, in fact, visible from the debug window, but not from the function in the code module. Pretty darned odd, if you ask me, and not what I expected when I tested this stuff as I write.  I haven’t a clue why the debug window gets special privileges; in fact, this is the first time I discovered this. But it does, so just be aware.
In the header of Module1 put the following code:
Dim cCtlCbo As clsCtlCbo

Try to compile.  Notice that you immediately get a compile error on that line.  The reason is that the class is not ‘visible’ outside of the library.  Notice that if you have project Explorer open you can see the classes; however, they cannot be compiled into code outside of the library container.

Export and edit class method – has intellisense

What I am going to do now is demonstrate editing a class in a text editor to make it visible outside of the library.
Close LibTest and open MyFrameworkLib.  Get to the modules (how varies from 2003 to 2007, so I am just going to say that from now on).  Right click on clsFrm.  We are going to start with that to demonstrate something.
  • Click Export from the right click context menu.
  • Navigate to the directory that contains MyFrameworkLib.
  • Create a class subdirectory.
  • Select ‘Text files’ in the ‘Save as’ combo box.
  • The file name clsFrm should magically appear in the ‘File name’ combo.
  • Click Export button to save the class out to a text file.
  • Using Windows explorer, navigate to the new class directory you created above.
  • Open the file in Notepad (NOT IN WORD!).
You should see the following in the header of the text file:
  MultiUse = -1  ‘True
Attribute VB_Name = “clsFrm”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Compare Database
Option Explicit
The rest of the class will be below, but this is the important stuff that we need to edit.  Edit the header to look like:
Attribute VB_Name = “clsFrm”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Notice that I took out the stuff at the top and the Option statements and set two of the Attributes to True.  Again, don’t worry about the body of the class below this stuff; just make the indicated edits. Save and close the text file.
Back in Access:
  • Delete clsFrm from the modules tab.
  • Click ‘Insert / Class Module’.
  • Click ‘Insert / File’.
  • Navigate back to the ‘Classes’ directory.
  • Select clsFrm from the directory and click OK.
  • Immediately save.  Notice that Access will know the name of the class.  Just accept that name.
  • Debug/Compile.
Close MyFrameworkLib.MDA and open TestLib.  Open Module1 and paste in the following code:
Option Compare Database
Option Explicit
Dim cFrm As clsFrm
Function mTestWhatCanBeSeen() As clsFrm
    Set cFrm = New clsFrm
End Function
Compile.  You should get no complaints from the compiler.  Now open the debug window and insert the following code:
Now place your cursor behind mTestWhatCanBeSeen and type a . (period), and you should see something like:
You now have intellisense.  As it happens, mInit() is the only public method in the class; but you can see that we have intellisense and that we can, in fact, reference frmClass out in the library.
So in this post we have covered a bunch of meaningless but interesting (to me) history; but, more importantly, we have seen how to expose a class outside of the library container so that we can use it.  Now we have to do the same thing for the rest of the classes.  Before we do that, I promised to show you how to get a pointer to a class without going through this edit.

Wrap the class in a function method – no intellisense

Close LibTest and open MyFrameworkLib.MDA.  Create a module and name it basExposedByFunction.  In that module insert the following code:
Option Compare Database
Option Explicit
Function fExposeClsCtlCbo() As Object
    Dim cCtlCbo As clsCtlCbo
    Set cCtlCbo = New clsCtlCbo
    Set fExposeClsCtlCbo = cCtlCbo
End Function
You can now use this function to instantiate and return a pointer to an instance of clsCtlCbo from outside of the library.  The problem, as I mentioned above, is that because we declared a return type of Object, the code that calls the function cannot know the methods and properties of the object returned.  YOU must know that; and, if you happen to use the right syntax you can, in fact, access the object just as if it were returned as a real class type.  But the onus is on you to remember / look up the objects.  For me, once I have intellisense, it is tough to go back.
I am done for the night.  Tomorrow I will continue this discussion about libraries.

Leave a Reply

Your email address will not be published. Required fields are marked *