West Wind Internet and Client Tools
Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
Gravatar is a globally recognized avatar based on your email address. Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Manni Borg
  All
  Aug 26, 2024 @ 04:59am

Dear experts,

I am currently trying to integrate a C# assembly with Visual FoxPro using wwDotNetBridge. My goal is to create an instance of List within an Author object. I am attempting to use the ComValue.SetValueFromCreateGenericInstance() method to achieve this, similar to the discussion here (https://support.west-wind.com/Thread5C40OHID8.wwt) .

The C# classes are defined as follows:

public class Author
{
    // Fields
    public string Name;
    public List<Institution> Institution;
}

public class Institution
{
    // Fields
    public string Name;
    public string Id;
}

How can I add an Institution to the Author object?

Here’s what I have done so far in VFP:


loAuthor = loBridge.CreateInstance("MyComInterop.Author")
loBridge.SetProperty(loAuthor , "Name", "Max")

loValue = loBridge.CreateComValue()

*** Create an array of strings for generic type parameters
loGenericTypes = loBridge.CreateArray("System.String")
loGenericTypes.AddItem("MyComInterop.Institution")

*** Create a new array of List Items to pass into ctor
loList = loBridge.CreateArray("MyComInterop.Institution")

loInstitution = loBridge.CreateInstance("MyComInterop.Institution")
loBridge.SetProperty(loInstitution, "Name", "John")
loBridge.SetProperty(loInstitution, "Id", "12345")
loList.AddItem(loInstitution)

*** Add the array as parameter to the constructor
loParms = loBridge.CreateArray("System.Object")
loParms.AddItem(loList)  && parameter to List<Institution>

*** Create List<Institution> on the ComValue structure
loValue.SetValueFromCreateGenericInstance("System.Collections.Generic.List", loGenericTypes, loParms)  && not working!!!

When trying to instantiate the List using SetValueFromCreateGenericInstance(), I am receiving a "Object reference not set to an instance of an object" error.

Where am I getting it wrong? Is there a much simpler way to achieve that?

Any help would be greatly appreciated!

Thank you very much, Manni

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Rick Strahl
  Manni Borg
  Aug 26, 2024 @ 08:05am

The problem is that your objects have Fields and not Properties. You can only assign to properties. So redefine:

public class Author
{
    // Properties!
    public string Name { get; set; }
    public List<Institution> Institution  { get; set; }
}

Also, since it appears you're in control of this code - I'd recommend you pre-initialize your properties with the objects which does away with the instantiation over COM interop:

public class Author
{
    // Properties!
    public string Name { get; set; }
    public List<Institution> Institutions  { get; set; } = new List<Institution>();
}

This not only makes it easier on your FoxPro code, but is also good practice in general as it avoids the constant having to check for null instances of the properties before use.

Doing the above pretty much makes all that code posted originally go away and only leaves:

*** Returns a ComArray
loAuthor = loBridge.CreateInstance("MyComInterop.Author")
loList = loBridge.GetProperty(loAuthor,"Instances")

loInstance = loBridge.CreateInstance("MyComInterop.Instance")  
loInstance.X = "y"
loList.Add(loInstance)

* ...

FWIW, I took another look at this API - I actually forgot that this is even supported in wwDotnetBridge as my original thought of failure was (before seeing your Fields) that the parameter list was invalidly configured.

I've made a change that makes this a little easier by allowing to pass in null for the parameter list since that's the most common use case.

Here's an updated example (note the null parameters won't actually work until I update - so the commented code can be used):

CLEAR
do wwDotNetBridge
LOCAL loBridge as wwDotNetBridge
loBridge = GetwwDotnetBridge()

lcGenericItemType = "Westwind.WebConnection.TestCustomer"

*** The generic types for the List<string>
loGenericTypes = loBridge.Createarray("System.String")
loGenericTypes.Add(lcGenericItemType)

*** No parameters so we can pass null (new as of v8.0.1)
loParameters = null

*** Optional CTOR Parameters - CANNOT BE NULL prior to v8.0.1 (or method won't resolve via Reflection)
*!* loParameters = loBridge.CreateArray("System.Object")  && Empty CTOR parameter list

*** Now create a ComValue to hold the Generic List
loValue = loBridge.CreateComValue()
loValue.Value = loValue

*** Create the list instance
loValue.SetValueFromCreateGenericInstance("System.Collections.Generic.List",loGenericTypes, null)

*** Now add items - first get a COMARRAY list
loList = loValue.GetValue()

*** Create instances and add
loCust = loBridge.CreateInstance(lcGenericItemType)
loCust.Name = "Rick"
loList.Add(loCust)

loCust = loBridge.CreateInstance(lcGenericItemType)
loCust.Name = "Janet"
loList.Add(loCust)

*** List of the ComValue reflects added items (commercial version only)
? loBridge.ToJson(loValue,.T.)

Note that this code does not try to set the list in the CTOR parameter so that list is not initialized. This code does it after the list has been created. Both work, but especially with the new NULL parameter for the instance removes one more item that can go wrong in the initial and more complex creation call.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Manni Borg
  Rick Strahl
  Aug 27, 2024 @ 02:42am

Rick, thank you very much for your reply!

Of course, working with Properties instead of Fields and also pre-initializing the properties is much easier to work with from VFP. Unfortunately, I don't have control over the .Net Assembly's code and they are always using Fields instead of Properties.

For example with Arrays it's not a problem:

public class Person
{
    public string Name;
    public Profession[] Professions;
}

public class Profession
{
    public string Text;
}
loPerson = loBridge.CreateInstance("MyAssembly.Person")
loPerson.Name = "John Doe"

loArrProfessions = loBridge.CreateArray("MyAssembly.Profession")

loProfession = loBridge.CreateInstance("MyAssembly.Profession")
loProfession.Text = "CEO"

loArrProfessions.AddItem(loProfession)

loBridge.SetProperty(loPerson, "Professions", loArrProfessions)
? loBridge.GetProperty(loPerson, "Professions[0].Text")  && "CEO"

But the same situation with a List Professions I don't know how to create the list and add it to the Person.

public class Person
{
    public string Name;
    public List<Profession> Professions;
}

public class Profession
{
    public string Text;
}

Do I understand it right, that with the new version of wwDotnetBridge it would be possible to achieve this by passing null for the parameter list (this is where I get an error at the moment)?

I'm not sure if I'm understanding correctly how to apply your code example to the above example with Person and Profession.

loPerson = loBridge.CreateInstance("MyAssembly.Person")
loPerson.Name = "John Doe"

lcGenericItemType = "MyAssembly.Profession"

*** The generic types for the List<string>
loGenericTypes = loBridge.Createarray("System.String")
loGenericTypes.Add(lcGenericItemType)

*** No parameters so we can pass null (new as of v8.0.1)
loParameters = null

*** Now create a ComValue to hold the Generic List
loValue = loBridge.CreateComValue()
loValue.Value = loValue

*** Create the list instance
loValue.SetValueFromCreateGenericInstance("System.Collections.Generic.List",loGenericTypes, null)

*** Now add items - first get a COMARRAY list
loList = loValue.GetValue()

*** Create instances and add
loProfession = loBridge.CreateInstance(lcGenericItemType)
loProfession.Text = "CEO"
loList.Add(loProfession)

*** List of the ComValue reflects added items (commercial version only)
? loBridge.ToJson(loValue,.T.)

Would this code work so far and how do I continue to add the List of professions to the Person?

Thanks, Manni

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Rick Strahl
  Manni Borg
  Aug 27, 2024 @ 10:14am

Setting Fields is problematic and in the case of the generic functions it's not possible. It's only set to work with properties.

Whoever owns that API should really change to Properties - it's just bad practice especially if you're using interop. I'm surprised it works as far as you got it to work. Many of the APIs don't work with Fields so there's likely other stuff that can break.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Rick Strahl
  Manni Borg
  Aug 27, 2024 @ 10:46am

Would this code work so far and how do I continue to add the List of professions to the Person?

Once you have the loValue object of the generic list you can then assign it.

loBridge.SetProperty(loAuthor,"Institutions",loValue)

I think that should actually work with a field because SetProperty and GetProperty have binding flags to access Fields and Properties.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Manni Borg
  Rick Strahl
  Aug 27, 2024 @ 10:54pm

Thanks, Rick!

I didn't know that Fields in an assembly could be such a deal breaker as everything was working fine so far except the List<> generic type.

After what you've explained I'll reach out to the manufacturer of the assembly and try to get the Fields changed to Properties. Anyway, it's cool that the bridge is able to even support those cases! πŸ˜ƒ

Manni

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Rick Strahl
  Manni Borg
  Aug 28, 2024 @ 07:29am

Sure, but did you try the code I posted? I don't see why the SetProperty() code with the ComValue assigned generic list shouldn't work even with fields.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Manni Borg
  Rick Strahl
  Aug 28, 2024 @ 12:06pm

Rick, I've created this small test assembly to try the code

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace InteropTest
{
    [ComVisible(true)]
    [Guid("D62D1DC1-79A6-49D1-95D4-834D3ABCF4A5")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class Person
    {
        public string Name;
        public List<Profession> Profession;
    }

    [ComVisible(true)]
    [Guid("2E9A4C1A-3F7D-4E1F-AEB1-F0DAF8FA3E5C")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class Profession
    {
        public string Text;
    }
}

and the VFP code:

DO wwDotnetBridge
InitializeDotnetVersion("V4")
loBridge = CreateObject("wwDotNetBridge","V4")
loBridge.LoadAssembly("InteropTest.dll")

loPerson = loBridge.CreateInstance('InteropTest.Person')
loBridge.SetProperty(loPerson, "Name", "John")

lcGenericItemType = "InteropTest.Profession"

*** The generic types for the List<string>
loGenericTypes = loBridge.Createarray("System.String")
loGenericTypes.AddItem(lcGenericItemType)

*** No parameters so we can pass null (new as of v8.0.1)
* loParameters = null
loParameters = loBridge.CreateArray("System.Object")  && Empty CTOR parameter list (don't have v8.0.1)
 
*** Now create a ComValue to hold the Generic List
loValue = loBridge.CreateComValue()
loValue.Value = loValue

*** Create the list instance
loValue.SetValueFromCreateGenericInstance("System.Collections.Generic.List", loGenericTypes, loParameters)  && error: value can't be null (don't have v8.0.1)

*** Now add items - first get a COMARRAY list
loList = loValue.GetValue()

*** Create Professions and add
loProfession1 = loBridge.CreateInstance(lcGenericItemType)
loProfession1.Text = "CEO"
loList.Add(loProfession1)

loProfession2 = loBridge.CreateInstance(lcGenericItemType)
loProfession2.Text = "Programmer"
loList.Add(loProfession2)

*** List of the ComValue reflects added items (commercial version only)
? loBridge.ToJson(loValue,.T.)

loBridge.SetProperty(loPerson, "Profession", loValue)

I can't test further than the line

loValue.SetValueFromCreateGenericInstance("System.Collections.Generic.List", loGenericTypes, loParameters)

because I don't have the commercial/updated version of wwDotnetBridge yet.

I was planning on getting the Client tools a long time ago - the bridge alone saved me many times for sure!

Thanks, Manni

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Rick Strahl
  Manni Borg
  Aug 28, 2024 @ 09:22pm

Ok so I got this to work - the problem was not the use of fields (although that's another set of issues), but rather the fact that generic type resolution was not working. It worked for me because my test classes were inside of the wwDotnetBridge assembly and so could resolve, but if the generic types live in the external assembly they just didn't resolve. So basically ComValue.SetValueFromCreateGenericInstance() pretty much would have failed on just about any generic type.

I've fixed this issue along with better error reporting if the type creation fails.

The following code works but you need an update of wwDotnetBridge.dll from here:

https://west-wind.com/files/WebConnectionExperimental.zip

The updated code is this:

using System;
using System.Collections.Generic;

namespace GenericTest
{
    public class Person
    {
        public string Name ;

        public List<Address> Addresses 
    }

    public class Address
    {
        public string Street ;
        public string City ;
        public string State;
        public string Zip ;

    }
}

I'm using fields as per your example.

In FoxPro:

do wwDotNetBridge
LOCAL loBridge as wwDotNetBridge
loBridge = GetwwDotnetBridge()

? loBridge.LoadAssembly(".\bin\GenericTest.dll")

lcGenericItemType = "GenericTest.Address"

loAddress = loBridge.CreateInstance(lcGenericItemType)
loAddress.Street = "123 Highland"

*** The generic types for the List<string>
loGenericTypes = loBridge.Createarray("System.String")
loGenericTypes.Add(lcGenericItemType)

*** No parameters so we can pass null (new as of v8.0.1)
loParameters = null

*** Optional CTOR Parameters - CANNOT BE NULL prior to v8.0.1 (or method won't resolve via Reflection)
* loParameters = loBridge.CreateArray("System.Object")  && Empty CTOR parameter list

*** Now create a ComValue to hold the Generic List
loValue = loBridge.CreateComValue()

*** Create the list instance
loValue.SetValueFromCreateGenericInstance("System.Collections.Generic.List", loGenericTypes, loParameters)

*** Now add items - first get a COMARRAY list
loList = loValue.GetValue()

*** Create instances and add to Address List
loAddress = loBridge.CreateInstance(lcGenericItemType)
loAddress.Street = "123 Highland"
loList.Add(loAddress)

loCust = loBridge.CreateInstance(lcGenericItemType)
loAddress.Street = "123 Lowland"
loList.Add(loAddress)

*** Create the person to attach addresses to
loPerson = loBridge.CreateInstance("GenericTest.Person")
loPerson.Name = "James Mayer"

*** IMPORTANT: You have to use SetPropertyEx() for field assignment
loBridge.SetPropertyEx(loPerson, "Addresses", loValue)

*** List of the ComValue reflects added items (commercial version only)
? loBridge.ToJson(loPerson,.T.)

*** IMPORTANT: You have to use GetPropertyEx() for field access
loAddresses = loBridge.GetPropertyEx(loPerson,"Addresses")
? loBridge.ToJson(loAddresses,.T.)

This all works with fields, but you need to use the Ex methods to read and write fields and that only works for these two methods. Many other wwDotnetBridge methods that work with properties - even though less frequently used - work only on properties. It's a fluke (and a legacy feature) that the Ex methods work with fields as that functionality was long ago removed by default (due to performance - lots of overhead in looking up both fields and properties). Fields are meant to be the private interface to a class, with Properties making up the public or protected interface - .NET automatically optimized non-code properties so there's no overhead in using properties when there are no getters or setters, so there's no reason to prefer fields for public members.

Let me know how it goes.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Manni Borg
  Rick Strahl
  Aug 30, 2024 @ 04:05pm

Hi Rick,

thank you very much for the update and the example!

I'm glad you could identify the reason and that wwDotnetBridge can now even work with generic types! I tested you code and it's now working!

As Fields are used heavily in the assembly that I'm working with, I needed to update my code to always use GetPropertyEx/SetPropertyEx instead of GetProperty/SetProperty, because the latter isn't working anymore in the experimental version. So far everything seems to work fine. I hope the use of Fields won't cause any problems in the future in some functions I might have overseen.

I agree with you about best pratices of Fields vs. Properties, in that Fields are best suited for the private interface of a class, while properties should be used for the public or protected interface. On the other hand, I can also understand that a C# developer wants to avoid adding get; set; every time when you can foresee that a certain logic in the getter/setter will never be needed, especially coming from VFP where this doesn't need to be done πŸ˜ƒ

Will you make an update of wwDotnetBridge also available on https://github.com/RickStrahl/wwDotnetBridge? If yes, maybe it would be good to add a warning that SetPropertyEx/ExGetPropertyEx will now be needed for Fields, because I can imagine I'm not the only one relying on Fields, although it was a fluke that it even worked to begin with πŸ˜ƒ

Thanks, Manni

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Rick Strahl
  Manni Borg
  Aug 31, 2024 @ 10:09am

Using public fields is extremely uncommon in .NET - usually something only non-.NET developers will use so it's not common. And like a I said - the name of the methods are SetProperty and GetProperty points to it right in the name.

The property only functionality has been in place for quite some time in wwDotnetBridge including the open source version (I think v6 is when that was changed).

The open source version will get updated at some point with these latest changes, but it's low priority at the moment.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Creating a List<Institution> using ComValue.SetValueFromCreateGenericInstance() from wwDotnetBridge
  Manni Borg
  Rick Strahl
  Aug 31, 2024 @ 12:25pm

Rick, as it turns out, I was still using Version 7.9.0.0, where SetProperty/GetProperty could be used for Fields, so I didn't realize this wasn't possible for a long time. My bad, time is passing fast and I need to remember to update more frequently.

Thanks again for making the impossible possible! πŸ˜ƒ

Manni

© 1996-2024