Wiki Home

When Fox Objects Are Not Really Fox Objects


Namespace: WIN_COM_API
VFP object references are VFP object references. Right?

Well, not always. Sometimes VFP objects are presented as COM Proxies.

References provided by the _VFP system variable

_VFP.Forms[n], for example. Using the _VFP.Forms collection assuming this gives you a vanilla fox object reference may yield OLE IDispatch errors. The _VFP system memory variable is typically used for COM automation of Visual FoxPro, so this is no surprise.

In FOR...EACH enumerations of VFP8 collections

( Take note: This is fixed in VFP9 with the FOXOBJECT Keyword addition to the FOR EACH command.)


Here's a sample provided by Randy Pearson which shows that objects enumerated with FOR...EACH don't behave as expected. This, apparently, is by design, so it is not a bug.
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 IN THIS.MyApplications, the that is provided is not a true object reference to the underlying VFP object, but rather some sort of COM proxy. When you instead iterate, as in the second run, using the (implied) Item method, the object references are as expected. The following differences between 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 properties 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 properties). 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 (based on Collection.Count). 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

I just ran into this with a XmlAdapter/XmlTable which should consume a Diffgram.
&& This doesn't work
WITH CREATEOBJECT("XMLAdapter")
  .loadXml(lcDiffgram)
  FOR each o_Table in .Tables
    o_Table.ApplyDiffgram("crsTest", o_CursorAdapter, .T.)
  ENDFOR
ENDWITH

The resulting error is:
"OLE IDispatch exception code 901 from Visual FoxPro for
Windows: Function argument value, type, or count is invalid..."
&& this is ok!
WITH CREATEOBJECT("XMLAdapter")
  .loadXml(lcDiffgram)
  FOR i = 1 to .Tables.Count
    o_Table = .Tables(i)
    o_Table.ApplyDiffgram("crsTest", o_CursorAdapter, .T.)
  ENDFOR
ENDWITH

This problem only arises in combination with XmlAdapter/XmlTable and Cursor Adapter.
-- Kurt
Another problem:
FOR EACH loRec in loRecCollection
    APPEND BLANK
    Gather Name loRec
END FOR

doesn't work. You have to use the FOXOBJECT-Clause. This works:
FOR EACH loRec in loRecCollection FOXOBJECT
    APPEND BLANK
    Gather Name loRec
END FOR



-- Kurt
Another issue with the COM object proxies is that they apparently do not support the VFP Date data type. This is an extremely simplified example of some code I was using to write new values into the properties of objects stored in a VFP collection. The first loop worked fine for me with other data types, but when I tried to store a Date value in a property, it was converted to a Date Time. I didn't think FOXOBJECT was necessary if the objects were just Empty classes or created via SCATTER NAME, but it is.
LOCAL loCollection AS Collection, ;
  loObject

loObject = CREATEOBJECT( "Empty" )
ADDPROPERTY( m.loObject, "MyProp1", .NULL. )
ADDPROPERTY( m.loObject, "MyProp2", .NULL. )

loCollection = CREATEOBJECT( "Collection" )
loCollection.Add( m.loObject )

loObject = .NULL.

*-- In this loop, any attempt to store a value as a Date will result
*-- in a DateTime value in the property.
FOR EACH loObject IN m.loCollection
  loObject.MyProp1 = DATE()
  ASSERT .F. ;
    MESSAGE "loObject.MyProp1 = " + TRANSFORM( m.loObject.MyProp1 ) + CHR(13) ;
    + "VARTYPE() = " + VARTYPE( m.loObject.MyProp1 )
ENDFOR

loObject = .NULL.

*-- Add FOXOBJECT to ensure that Date values are stored in the object.
FOR EACH loObject IN m.loCollection FOXOBJECT
  loObject.MyProp2 = DATE()
  ASSERT .F. ;
    MESSAGE "loObject.MyProp2 = " + TRANSFORM( m.loObject.MyProp2 ) + CHR(13) ;
    + "VARTYPE() = " + VARTYPE( m.loObject.MyProp2 )
ENDFOR

-- Mike Potjer

FOXOBJECT is always necessary when dealing with native objects. Without it, VFP will always create COM objects. -- Tamar Granor
Category VFP Troubleshooting

( Topic last updated: 2013.09.18 01:24:31 PM )