FoxPro Programming
Re: Possible VFP 8 Bug Involving Collections
05/07/2003
02:22:03 PM
0Y40USMEN Show this entire thread in new window
From:
Randy Pearson
To:
Randy Pearson
Attachments:
None
OK. Here's the deal.

TITLE: "FOR EACH Enumeration of VFP Collections Does Not Provide True Object References"

SEVERITY: High

IMPORTANCE: Moderate (workaround available)

STEPS TO REPRODUCE: Run the sample code that appears below. It creates an object with a collection member and adds two items to the collection. Then it iterates in two ways: first using FOR EACH, second using using the implied Item() method. Results of several checks are written to _screen.

OBSERVED: When iterating, as in the first run, using FOR EACH <obj> IN THIS.MyApplications, the <obj> that is provided is not a true object reference to the underlying VFP object, but rather some sort of COM proxy. When you isntead iterate, as in the second run, using the (implied) Item method, the object references are as expected. The following differences between the the two runs points this out:

a) A COMPOBJ() check between the object reference provided by the enumerator and the actual original object itself returns .F., while it returns .T. in the second case.

b) Calling AMEMBERS() on the object reference provided by the iterator provides the wrong result in two ways: many properties are missing, and some *methods* are included in what should be a property list.

c) As you happen to reference additional proeprties directly in your code, the result of AMEMBERS grows, indicating that some discovery, via COM, seems to go on.

d) If you reference an invalid property name, instead of the expected error 1734 ("property is not known"), you get a 1426 COM error ("Unknown name").

e) NOTE: If you expand the object reference in the Watch/Local window, you do see all the properties, so something deeper than AMEMBERS is used internally there.

EXPECTED: To get the true object reference that was placed into the collection in the first place.

DANGERS: This is a particular problem when using AMEMBERS. Aside from getting the wrong result, AMEMBERS calls are often followed up by TYPE() checks of each property (such as when testing for array proeprties). If the result from AMEMBERS includes some *method* names, using TYPE() causes these methods to get *run*. In the instance where I discovered this bug, that running created recursion that eventually blew up VFP with "DO nesting too deep".

WORKAROUND: Avoid using FOR EACH with the VFP Collection base class, preferring the second form of iteration shown in the example code. This is mandatory if using AMEMBERS() or COMPOBJ().

SAMPLE CODE:

_SCREEN.FontName = "Courier New" CLEAR PRIVATE poAppManager poAppManager = CREATEOBJECT("TheApplicationManager") poAppManager.ProcessRequest() RELEASE poAppManager poAppManager = CREATEOBJECT("TheApplicationManager") poAppManager.ProcessRequest2() DEFINE CLASS TheApplicationManager AS Custom ADD OBJECT MyApplications AS Collection FUNCTION init this.Setup() ENDFUNC FUNCTION Setup this.AddApplication(CREATEOBJECT("AnApplication"), "App_1") this.AddApplication(CREATEOBJECT("AnApplication"), "App_2") ENDFUNC FUNCTION ProcessRequest() PRIVATE goL7App FOR EACH goL7App IN THIS.MyApplications IF m.goL7App.IsMyItem() goL7App.ProcessItem("Iteration using FOR EACH.") EXIT ENDIF ENDFOR ENDFUNC FUNCTION ProcessRequest2() PRIVATE goL7App LOCAL ii FOR ii = 1 TO THIS.MyApplications.Count goL7App = THIS.MyApplications[m.ii] IF m.goL7App.IsMyItem() goL7App.ProcessItem("Iteration using Count property.") EXIT ENDIF ENDFOR ENDFUNC FUNCTION AddApplication(loApp, lcKey) this.MyApplications.Add(loApp, lcKey) ENDFUNC ENDDEFINE DEFINE CLASS AnApplication AS Session Prop1 = "ABC" Prop2 = 3.14 Prop3 = .T. FUNCTION INIT ENDFUNC FUNCTION IsMyItem RETURN .t. && try a break point here also ENDFUNC FUNCTION ProcessItem(lcMsg) THIS.CheckConsistency(m.lcMsg) ENDFUNC FUNCTION CheckConsistency(lcMsg) LOCAL aaa[1], lcProps, ii, jj ? m.lcMsg ? REPLICATE("=", LEN(m.lcMsg)) ? " COMPOBJ(THIS, m.goL7App):", COMPOBJ(THIS, m.goL7App) ? " True property count:", TRANSFORM(AMEMBERS(aaa, THIS)) FOR jj = 1 TO 2 ? "Properties count per AMEMBERS:", TRANSFORM(AMEMBERS(aaa, m.goL7App)) lcProps = "" FOR ii = 1 TO ALEN(aaa) lcProps = lcProps + aaa[m.ii] + CHR(13) + CHR(10) ENDFOR ? " Properties reported:", m.lcProps FUNCTION "V30" ? " Check value of Prop2:", TRANSFORM(m.goL7App.Prop2) ENDFOR TRY = goL7App.Unknown CATCH TO loExc ? " Error on unknown property:", TRANSFORM(loExc.ErrorNo), loExc.Message ENDTRY ? ENDFUNC ENDDEFINE

-- Randy

J. Randy Pearson, Cycla Corp.
West Wind MVP


West-Wind MVP

Co-Author of "WebRAD: Building Database Websites
with Visual FoxPro and Web Connection"