(Reprinted from FoxPro Advisor, February 1999)
■ Visual FoxPro 6.0
Q: Here’s some strange behavior I ran across.The code below is a simple example of what I’m seeing. Run it from the Command Window, and you’ll be fine. Stick it in a project, build it into a COM .DLL, and change the CreateObject() call to use the .DLL. Run the code. Error: “Property not found”. What’s going on?
foo = CreateObject("FirstClass") ? foo.First.Name ? foo.First.Test ? foo.Second.Name ? foo.Second.Test DEFINE CLASS FirstClass AS Custom olepublic name="MainContainer" FUNCTION Init this.AddObject("First","SecondClass") this.AddObject("Second","SecondClass") ENDFUNC ENDDEFINE DEFINE CLASS SecondClass AS Custom name="Second" test="teststring" ENDDEFINE
—Dan Freeman, Chicago, Illinois
A: The problem has to do with the time at which the First and Second objects are bound to the interface of the MainContainer object. In Visual FoxPro, these objects can be
added as part of the class definition of the MainContainer, or can be added at a later time. Within Visual FoxPro, either technique works fine. The only significant difference between the
two techniques is that the objects added can’t be manipulated until they’re added.
The COM interface is a different matter. When the objects are compiled into a COM .DLL, a type library is produced that defines the outer interface of the .DLL. This type library is used by other languages such as Visual Basic, and development environments like Visual InterDev to display the interface using IntelliSense. The problem with this early binding of the interface is that it means the interface can’t be changed at runtime. When you attempt to reference newly-added objects in your COM .DLL, you have changed the interface in a way the controlling program can’t detect—it only has the static type library to go by.
One workaround is to define the interfaces as part of the compilation process so the type library properly reflects the interfaces you’ll be addressing. Two options exist for this. If the contained controls will always be the same ones, add them to the definition of the container with code like this:
DEFINE CLASS FirstClass AS Custom olepublic ADD OBJECT First as SecondClass ADD OBJECT Second as SecondClass name="MainContainer" ENDDEFINE
The other alternative is just to fake out the type library generation by adding new properties to the outer container, and use them to hold references to the newly-created objects:
DEFINE CLASS FirstClass AS Custom olepublic First = .NULL. Second = .NULL. name="MainContainer" FUNCTION Init THIS.First = CreateObject("SecondClass") THIS.Second = CreateObject("SecondClass") ENDFUNC ENDDEFINE
This technique gives you added flexibility in that you can decide which type of object to add to the container at runtime. The cost associated with this technique is that you lose the native containership model. That means you can’t address THIS.Parent in the added object, or use the Controls collection of the container object to access the First and Second objects. I prefer the second solution. I pass THIS as a parameter in the
CreateObject() for the new object, then store the reference in the contained object to give it a callback reference to the “parent.”
As long as one of the contained objects has a reference to the “parent,” the container can’t be released. So, be sure to set the First and Second properties to .NULL. in the Destroy event of the container, thereby releasing these objects before the container is released.
—Ted