Getting a class instance through the class name

576 views
Skip to first unread message

Jeff

unread,
Jul 18, 2004, 11:46:39 PM7/18/04
to
Hello,

in my Access 2002 project, I have several classes which I'd like to
instantiate through some kind of function that takes a string and
returns the newly instantiated object.

For example, if there's a class Cat:
// in Cat class module:
public sub meow()
msgbox "Meow"
end sub
// end of Cat class module

I'd like to be able to somehow create a function that returns a Cat
object like the following:

dim x as object
set x = getClassInstanceByClassName("Cat")
x.meow
set x = nothing


The trick is, I don't know what classes are going to be present in the
project (this code is part of a testing framework I'm designing) ...
if I did know, the function I want would be pretty easy:

public function getClassInstanceByClassName(byval s as string) as
object
' build a collection of the classes
dim c as Collection
set c = new Collection
c.add new Cat, "Cat"
' ... etc, add more

set getClassInstanceByClassName = c.Item(s)
end function


Is there some kind of API function I can call that does something like
the following?

public function getClassInstanceByClassName(byval s as string) as
object
' get memory allocation/instantiation function location for an
object
' of class name s
' do allocation of new object
set getClassInstanceByClassName = the_new_object
end function


The testing framework currently works through VBA's pseudo-reflection
(run time investigation of the classes in the project), but it has to
write code in the project (self-modifying) to build the collection of
classes. I don't want to have my project writing to itself ... while
it works, it just seems a bit too hairy. Also, I don't want
developers to have to go and add a line to the collection creation
routine manually whenever they have the class, since I want this
testing framework to be invisible to developers using it.


Thanks,

Jeff

Steve Gerrard

unread,
Jul 19, 2004, 1:39:25 AM7/19/04
to

"Jeff" <jeffz...@yahoo.com> wrote in message
news:7b01eaf3.04071...@posting.google.com...

I don't think that collection approach would work out well, since it
would contain exactly one instance of each class. Every time you asked
for a cat, you would get the same cat, and some users might want a whole
herd of them.

If the classes were full blown ActiveX classes, duly registered in the
registry with class IDs, you could use something like
Set NewObj = CreateObject("modulename.cat")
I don't think that classes created in Access can be ActiveX classes.
Even in VB6, it can be a pain to have to compile and install dlls to get
your all the classes publicly registered.

I'm not sure you can do better than to build a select case statement at
run time, with a
case "cat"
Set NewObj = New Cat
for each of the classes you find. You might be able to do that in the
user's project instead of in yours, so yours can be self contained.

In fact, I'd be interested in how you are retrieving the list of classes
at run time through VBA reflection. I have poked around just a little in
VB extensibility, and it has a VBComponents collection available at
design time which contains the defined classes, but I don't think it is
available at run time.


Jeff

unread,
Jul 19, 2004, 10:49:58 PM7/19/04
to
Hey Steve, my responses are weaved in to the message:

> I don't think that collection approach would work out well, since it
> would contain exactly one instance of each class. Every time you asked

> for a cat, you would get the same cat ...

In my case this is the desired outcome, since the collection contains
only testing classes, and the developer will only want to use each
testing class once (each testing class can contain several test
methods). The framework follows the ideas of the XUnit testing
frameworks (as described in "JUnit: a cook's tour") -- I used an
implementation of the testing framework by Rob Harwood (see
http://c2.com/cgi/wiki?VbaUnit) as a starting point.


> If the classes were full blown ActiveX classes, duly registered in the
> registry with class IDs, you could use something like
> Set NewObj = CreateObject("modulename.cat")
> I don't think that classes created in Access can be ActiveX classes.

Well that sucks.


> In fact, I'd be interested in how you are retrieving the list of classes
> at run time through VBA reflection. I have poked around just a little in
> VB extensibility, and it has a VBComponents collection available at
> design time which contains the defined classes, but I don't think it is
> available at run time.

Here's the idea:

I have a TestClassCatalog_CodeWriter that loops through all the code
modules. If the module name ends in "_Tester", a line is added to the
module:

//////////////////////////////////////////

(a public method calls the getLoadCode() function and then rewrites
the TestClassCatalog function that creates the collection of Tester
objects) ...

Private Function getLoadCode() As String
Dim ret As String

' Loop through all code modules, if it's a Tester object add a
line of code for it
Dim c As VBIDE.VBComponent
For Each c In Application.VBE.ActiveVBProject.VBComponents
If isClassModule(c.Type) And isTestClassName(c.Name) Then
ret = ret _
& " call " & ADD_TEST_ROUTINE & " (new " & c.Name &
")" & vbCrLf
End If
Next

getLoadCode = ret
End Function


Private Function isTestClassName(component_name As String) As Boolean
Dim ret As Boolean
ret = (Len(component_name) > 6) And (Right(component_name, 6) =
"Tester")
isTestClassName = ret
End Function

Private Function isClassModule(component_type As
VBIDE.vbext_ComponentType) As Boolean
isClassModule = (component_type = VBIDE.vbext_ct_ClassModule)
End Function


//////////////////////////////////////////////////


Later in the same project, all the public methods in a _Tester object
starting with "Public Sub Test" are pulled out into a collection,
which is then used to set up the final TestCases:


Private Function getTestMethods(class_name As String) As Collection
Dim m As VBIDE.CodeModule
Set m = Application.VBE.ActiveVBProject.VBComponents(class_name).CodeModule

Dim ret As Collection
Set ret = New Collection

Dim line_num As Integer
For line_num = 1 To m.CountOfLines
If isTestMethodLine(m.Lines(line_num, 1)) Then
ret.Add m.ProcOfLine(line_num, vbext_pk_Proc)
End If
Next

If ret.count() = 0 Then
Err.Raise vbObjectError, , "WARNING: no test methods in class
" & class_name & "!"
End If

Set getTestMethods = ret
Set ret = Nothing
End Function


Private Function isTestMethodLine(Line As String) As Boolean
isTestMethodLine = (UCase(Left(Trim(Line), 15)) = "PUBLIC SUB
TEST")
End Function


/////////////////////////////////////////////////////


As for it being available at design/runtime, I didn't think this was
an issue. After all, the code is to help developers (and me) with
test-driven design, and so I'm constantly running the tests through
the debug window by typing:

vbaUnitMain.RunTests

(or, cuz I'm lazy):
rt


I'm going to contact Rob through e-mail right after I post this
message, and see if he can help me release this framework to a wider
audience. I've found it very useful.

Jeff

Reply all
Reply to author
Forward
0 new messages