Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Passing Arrays between FoxPro and .NET with COM Interop


:P
On this page:

I’ve been struggling with a complex Web Service interface and trying to get this thing to work over COM interop and slowly but surely I’ve worked through each one of these issues.

 

The hardest issue which is a more general issue for COM Interop with Visual FoxPro however is the way VFP deals with arrays passed over COM. This has given me endless amount of problems in the past and in this complex service most of the structure are actually expressed as arrays. The .NET Web Service proxy imports any type definitions that are list based like collections or hashtables into arrays in the proxy definitions so arrays are actually pretty common in proxy interfaces.

 

The problem with VFP is that you can easily enough read existing array objects and the child items as well as modifying existing items. However, as far as I know I’ve never found a direct way to create new array items in Visual FoxPro and assign them directly to the original type.

 

For example this code doesn’t work:

 

LOCAL loProvider as OmnicellProvider.OmniCellRequest

loProvider = CREATEOBJECT("OmniCellProvider.OmniCellRequest")

 

loMsg = loProvider.CreateMessageObject()

 

loRecip = CREATEOBJECT("OmniCellProvider.OmniConnectService.RecipientAgent")

loRecip.Email="rickstrahl@hotmail.com"

loRecip.PartnerID = "001./123"

 

 

laRecipients = loMsg.OMNIConnectMessage.BulkMessageHeader.RecipientList

? laRecipients[1].Email && Works

laRecipients[1].Email = "NewEmail"   && Works and updates .NET object

 

*** Add a new item by redim’ing the array

DIMENSION laRecipients[2]

laRecipients[2] = loRecip

 

*** Doesn't work

loMsg.OMNIConnectMessage.BulkMessageHeader.RecipientList = laRecipients

 

*** Having updated the array this also doesn't work:

loRecipients = loMsg.OMNIConnectMessage.BulkMessageHeader.RecipientList

loRecipients[2].Email && Invalid subscript error

 

It is possible to pass an array back to .NET in a method call, so if there was a method this would work:

 

loMsg.OMNIConnectMessage.BulkMessageHeader.SetRecipientList(@laRecipientList)

 

Unfortunately it’s unlikely that objects you deal with will have a method for doing this.

 

So, assigning and updating properties is tricky if the array needs to be resized basically. The solution I came up with is to create a couple of wrapper .NET methods that use Reflection to update the .NET array inside of .NET.  The idea is that .NET has no problem manipulating the array – it’s VFP that’s not doing the right thing. We can READ and even edit existing data just fine, so all that really needs special attention is the Adding/Removing/Resizing/Creation of arrays.

 

What I ended up with are two global methods on the main message object (or you can stick it into a Utility class, but then you have to instantiate it separately):

 

public bool AddArrayItem(object baseObject, string arrayObject, object Item)

{

    Array ar  = baseObject.GetType().InvokeMember(arrayObject,

                          BindingFlags.Instance | BindingFlags.Public |

                          BindingFlags.GetProperty | BindingFlags.GetField,

                          null, baseObject, null) as Array;

 

    // *** Null assignments are not allowed because we may have to create the array

    // *** and a type is required for that. If necessary create an empty instance

    if (Item == null)

        return false;

 

    Type ItemType = Item.GetType();

 

    // *** This may be ambigous - could mean no property or array exists and is null

    if (ar == null)

    {

        ar = Array.CreateInstance( ItemType,1);

        if (ar == null)

            return false;

 

        ar.SetValue(Item, 0);

        return true;

    }

 

    int Size = ar.GetLength(0);

    Array TempArray = Array.CreateInstance(  ItemType, Size + 1);

    ar.CopyTo(TempArray, 0);

 

    ar = TempArray;

    ar.SetValue(Item, Size);

 

    baseObject.GetType().InvokeMember(arrayObject,

                          BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.SetField,

                           null, baseObject, new object[1] { ar } );

   

 

    return true;

}

 

public bool RemoveArrayItem(object baseObject, string arrayObject, int Index)

{

    // *** USe Reflection to get a reference to the array Property

    Array ar = baseObject.GetType().InvokeMember(arrayObject,

              BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.GetField,

               null, baseObject, null) as Array;

 

    // *** This may be ambigous - could mean no property or array exists and is null

    if (ar == null)

        return false;

 

    int arSize = ar.GetLength(0);

 

    if (arSize < 1)

        return false;

 

    Type arType = ar.GetValue(0).GetType();

 

    Array NewArray = Array.CreateInstance(arType, arSize - 1);

 

    // *** Manually copy the array

    int NewCount = 0;

    for (int i = 0; i < arSize ; i++)

    {

        if (i != Index)

        {

            NewArray.SetValue(ar.GetValue(i), NewCount);

            NewCount++;

        }

    }

 

    baseObject.GetType().InvokeMember(arrayObject,

                          BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.SetField,

                           null, baseObject, new object[1] { NewArray });

 

    return true;

}

 

The methods are generic so they should with any one dimensional, single type array. In the client code this looks something like this then:

 

LOCAL loProvider as OmnicellProvider.OmniCellRequest

loProvider = CREATEOBJECT("OmniCellProvider.OmniCellRequest")

 

loMsg = loProvider.CreateMessageObject()

 

loRecip = CREATEOBJECT("OmniCellProvider.OmniConnectService.RecipientAgent")

loRecip.Email="rickstrahl@hotmail.com"

loRecip.PartnerID = "001./123"

 

? loProvider.AddArrayItem( loMsg.OMNIConnectMessage.BulkMessageHeader,

                           "RecipientList",loRecip)

 

loRecipients = loMsg.OMNIConnectMessage.BulkMessageHeader.RecipientList

loRecip = loRecipients[1]

 

? loRecip.Email

loRecip.Email = "NewEmail"

 

*** Added item

? loRecipients[2].Email

 

Note that you have to pass the name of the base object which is the object that the array lives on and then pass the array as string value. The code will then use Reflection to get an actual reference to the array and then add the item on your behalf. You then have to re-load the reference in your code to see the change, but this definitely does allow you to pass the data between the FoxPro application and the .NET application and back.

 

It’s messy as hell, and if you have any choice at all in COM interop stay away from plain Array objects. Use Collections instead which actually work because they operate through method interfaces that you can call. But of course you don't always have a choice in the matter. For example, many .NET objects natively return arrays and that's always been problematic for VFP to deal with. A good example are the Reflection APIs which return all members and then sub-properites and extension interfaces as arrays.  The above should make this code accessible, although I'd argue if the interface is complex enough you'll probably be better off creating wrapper classes.

 

 


The Voices of Reason


 

Rick Strahl's Web Log
October 15, 2006

# Passing Arrays to a .NET COM object from Visual FoxPro - Rick Strahl's Web Log

Passing arrays from VFP over COM is always a tricky affair because VFP arrays require some special marshalling hints in order to properly translate. This entry shows how to pass VFP arrays to .NET and retrieve array results from .NET COM objects.

Greg
January 09, 2008

# re: Passing Arrays between FoxPro and .NET with COM Interop

How are you doing this without using COMARRAY(). Im having additional difficulty readying 2 dimensional string arrays in C# when passed from VFP.

Rick Strahl
January 09, 2008

# re: Passing Arrays between FoxPro and .NET with COM Interop

COMARRAY is not required because effectively the array manipulation doesn't occur in FoxPro but in .NET. AddArrayItem simply takes a value from FoxPro and .NET adds the value to the array or collection.

I've not had any luck making .NET arrays work natively in FoxPro if you need to update or modify the array. Using the above approach just works...

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024