469,323 Members | 1,560 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,323 developers. It's quick & easy.

Looping through VB6 Structure members in VB.NET

Frinavale
9,735 Expert Mod 8TB
I'm using a VB6 library in my VB.NET application.

This library has a function that retrieves report records (as structures).

One of the structures in this library contains 105 public Boolean members that indicate to the function which "types" of records should be included while running a report.

The report record structures retrieved by the function contain this report type structure in order to indicate the"type" of record retrieved. You are able to tell which type of record it is by finding the single Boolean member is set to True in the record type structure.

Is there a way to loop through the VB6 Structure members?

Please note that the 105 Booleans are not Properties...they are simply publicly exposed Booleans. (So the System.Reflection PropertyInfo won't work)

Thanks for your time,

-Frinny
Nov 18 '08 #1
18 6364
balabaster
797 Expert 512MB
Hey Frinny...

Have you tried looking at the FieldInfo?
VB
Expand|Select|Wrap|Line Numbers
  1. Dim t As Type = GetType(MyType)
  2. Dim f As FieldInfo() = t.GetFields(BindingFlags.ExactBinding
C#
Expand|Select|Wrap|Line Numbers
  1. type t = GetType(MyType);
  2. FieldInfo[] f = t.GetFields(BindingFlags.ExactBinding);
I can't be 100% sure this will work with VB6 structures, however, you can reference fields in regular .NET classes (the same as you can with properties). I haven't tried it with any VB6 classes - and I don't have VB6 any more so I can't even knock up a dummy library to test the theory sadly...
Nov 18 '08 #2
balabaster
797 Expert 512MB
Does this help any?

Expand|Select|Wrap|Line Numbers
  1. public struct MyStruct
  2. {
  3.         public bool item1;
  4.         public bool item2;
  5.         public bool item3;
  6.         public bool item4;
  7.         public bool item5;
  8.  
  9.         public MyStruct(bool item1, bool item2, bool item3, bool item4, bool item5)
  10.         {
  11.             this.item1 = item1;
  12.             this.item2 = item2;
  13.             this.item3 = item3;
  14.             this.item4 = item4;
  15.             this.item5 = item5;
  16.         }
  17.  
  18. }
  19.  
  20. class Program
  21. {
  22.         static void Main(string[] args)
  23.         {
  24.             MyStruct m = new MyStruct(true, true, false, false, true);
  25.  
  26.             Type t = typeof(MyStruct);
  27.             FieldInfo[] f = t.GetFields();
  28.             FieldInfo[] selected = Array.FindAll(f, item => (bool)item.GetValue(m) );
  29.  
  30.             foreach (FieldInfo item in selected)
  31.             {
  32.                 Console.WriteLine(String.Format("{0}: {1}", item.Name, item.GetValue(m)));
  33.             }
  34.  
  35.             Console.ReadKey();
  36.         }
  37. }
Nov 18 '08 #3
Frinavale
9,735 Expert Mod 8TB
What is item in the following code?
Expand|Select|Wrap|Line Numbers
  1.  
  2.             FieldInfo[] selected = Array.FindAll(f, item => (bool)item.GetValue(m) == true );
  3.  
  4.  
Nov 18 '08 #4
Frinavale
9,735 Expert Mod 8TB
Ok, hold on a second....I see...I'm looking up Lambda expressions / functions.

:)
Nov 18 '08 #5
balabaster
797 Expert 512MB
What is item in the following code?
Expand|Select|Wrap|Line Numbers
  1.  
  2.             FieldInfo[] selected = Array.FindAll(f, item => (bool)item.GetValue(m) == true );
  3.  
  4.  
Okay, this uses lambda functions... a new concept that was introduced in .NET 3.0/3.5.

I don't know of your understanding of delegates, function pointers or callbacks but basically it's declared in a somewhat similar manner as a callback...

So what's going on here is FindAll grabs "f" which is our array of FieldInfo and for every item in the array it runs the lambda function defined on the right side of the comma.

"item" is an arbitrary name I used to reference the current item that is being reviewed in the array. I could have called this, n, k, s, foo... it's just arbitrary. FindAll passes the current item in the array into this variable (just the same way you'd pass into another method).

The lambda function then takes this value as a FieldInfo and uses item.GetValue(m) (where m is our structure instance) to check if the field that matches the signature specified by "item" in object m is set to true and returns an implicit boolean back to the FindAll method.

If True is returned to FindAll then the current field is added to the return list. Once FindAll has iterated through all items in the list it returns the list which is stored in the variable Selected.

Think of the following code which would do the same kind of thing:

Expand|Select|Wrap|Line Numbers
  1. public FieldInfo[] FindAll(MyStruct inst, FieldInfo[] fieldList)
  2. {
  3.   List<FieldInfo> matches = new List<FieldInfo>;
  4.   foreach(FieldInfo item in fieldList)
  5.   {
  6.      if (item.GetValue(inst) == true)
  7.         matches.Add(item.Name);
  8.   }
  9.   return matches.ToArray();
  10. }
I've got a post on my blog that covers this: Predicates/List.FindAll()
Nov 18 '08 #6
Frinavale
9,735 Expert Mod 8TB
So I ended up learning a lot about the Delegates, Lambda Expressions / Functions, and the Array.Find methods.

Expand|Select|Wrap|Line Numbers
  1.  Private Function RetrieveRecordName(ByVal retrievedRecordType As RecordType) As String
  2.         Dim recordName As String = ""
  3.  
  4.         Dim rType As Type = GetType(RecordType)
  5.         Dim recordTypeMembers() As FieldInfo = rType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
  6.  
  7. 'Method 1
  8. 'Using Lambda Expression with the Array.FindAll method to retrieve all the selected record types
  9.  
  10.         'Dim selectedMembers() As FieldInfo = Array.FindAll(recordTypeMembers, Function(x As FieldInfo) CType(x.GetValue(retrievedRecordType), Boolean) = True)
  11.         'If selectedMembers IsNot Nothing Then
  12.         '    recordName = selectedMembers(0).Name
  13.         'End If
  14.  
  15. 'Method 2
  16. 'Using Lambda Expression with Array.Find method to retrieve the first selected record type
  17.         Dim selectedMember As FieldInfo = Array.Find(recordTypeMembers, Function(x As FieldInfo) CType(x.GetValue(retrievedRecordType), Boolean) = True)
  18.         If selectedMember IsNot Nothing Then
  19.             recordName = selectedMember.Name
  20.         End If
  21.  
  22. 'Method 3
  23. 'Not using the Array.Find methods nor Lambda Expressions to find the first selected item
  24.         'Dim selectedMember As FieldInfo = Nothing
  25.         'For Each item In recordTypeMembers
  26.         '    If CType(item.GetValue(retrievedRecordType ), Boolean) Then
  27.         '        selectedMember = item
  28.         '        Exit For
  29.         '    End If
  30.         'Next
  31.         'If selectedMember IsNot Nothing Then
  32.         '    recordName = selectedMember.Name
  33.         'End If
  34.  
  35.         Return recordName 
  36.     End Function
  37.  
  38.  
  39.  

I still haven't totally figured out how to find the Array.Find method with a delegate to solve this problem...yet...I'm still working on it.

Thanks for all your help!

-Frinny
Nov 18 '08 #7
balabaster
797 Expert 512MB
All looks good - just a pointer, it might not seem like much but you might consider using DirectCast instead of CType.

We're not actually converting the objects to boolean, we're telling the code that these objects should be interpreted as boolean - because while they are passed as a "typeless" object, they are really boolean. Thus DirectCast should perform better than CType in this situation. Syntactically, the two methods are the same (other than their obviously different names)...

If we were to pass 0 or the string literal "false" but wish to interpret it as boolean, then we'd use CType.
Nov 18 '08 #8
Frinavale
9,735 Expert Mod 8TB
All looks good - just a pointer, it might not seem like much but you might consider using DirectCast instead of CType.

We're not actually converting the objects to boolean, we're telling the code that these objects should be interpreted as boolean - because while they are passed as a "typeless" object, they are really boolean. Thus DirectCast should perform better than CType in this situation. Syntactically, the two methods are the same (other than their obviously different names)...

If we were to pass 0 or the string literal "false" but wish to interpret it as boolean, then we'd use CType.
Good point :)

I didn't mention that, because of how the .NET Interop treats the structure, the Booleans are actually shorts.
Nov 18 '08 #9
balabaster
797 Expert 512MB
Good point :)

I didn't mention that, because of how the .NET Interop treats the structure, the Booleans are actually shorts.
ah, gotcha - I guess it's okay the way it is then...
Nov 18 '08 #10
Frinavale
9,735 Expert Mod 8TB
I still haven't totally figured out how to find the Array.Find method with a delegate to solve this problem...yet...I'm still working on it.
I never did get this to work.
The Find method requires a reference to a function that takes 1 parameter...I need there to be 2 parameters.

The Lambda Function gets around that requirement very nicely!
I'm starting to really understand how powerful Lambda Functions are.


Aside from that...
I am now attempting to use the System.Reflection.FieldInfo class to set values in my type with the following code:
Expand|Select|Wrap|Line Numbers
  1.         Dim rec As New RecordType
  2.         Dim recType As Type = GetType(RecordType)
  3.         Dim recordTypeMembers() As FieldInfo = kTransactionType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
  4.         For Each item In recordTypeMembers
  5.             Dim x As Short = -1
  6.             item.SetValue(rec, x)
  7.         Next
  8.  
Despite what I try the values are not changed.
According to this article on the FieldInfo.SetValue(obj,value) Method:

This method will assign value to the field reflected by this instance on object obj. If the field is static, obj will be ignored.
My field is not static (note the GetFields(BindingFlags.Instance Or BindingFlags.Public) returns public instance fields) but this still isn't working.
Nov 18 '08 #11
Frinavale
9,735 Expert Mod 8TB
Sometimes I'm an idiot.
The SetValue method requires an Object as the parameter.
I'm not passing it an Object, I'm passing it a Structure.

I think this is the source of my problem.
It is strange that the GetValue method still works when I pass the method a Structure though...

<edit>
Wouldn't you know, there's a thread here on the bytes about the SetValue method not working with Structures ...too bad the link doesn't work.</edit>
Nov 18 '08 #12
balabaster
797 Expert 512MB
I never did get this to work.
The Find method requires a reference to a function that takes 1 parameter...I need there to be 2 parameters.
There's no nice way to get around this - you have to use a class level field to transfer the value into your delegate - it's ugly as sin, I hate that. So yes, Lambda expressions nicely sidesteps that oversight.

As for not being able to set your value... this appears to be an issue with the fact that the object is a structure. I had a play and the only way I could get around it was to change MyStruct from a structure to a class.

I'm not guessing that's too much help... I did some checking and it was reported a while back to Microsoft to fix - they closed it stating that this was by design!

I'm wondering if they really checked it out though - it seems the guy who reported it was trying to pass the structure as an argument into another method byval instead of byref which doesn't really apply in this case.
Nov 18 '08 #13
Frinavale
9,735 Expert Mod 8TB
There's no nice way to get around this - you have to use a class level field to transfer the value into your delegate - it's ugly as sin, I hate that. So yes, Lambda expressions nicely sidesteps that oversight.

As for not being able to set your value... this appears to be an issue with the fact that the object is a structure. I had a play and the only way I could get around it was to change MyStruct from a structure to a class.

I'm not guessing that's too much help... I did some checking and it was reported a while back to Microsoft to fix - they closed it stating that this was by design!

I'm wondering if they really checked it out though - it seems the guy who reported it was trying to pass the structure as an argument into another method byval instead of byref which doesn't really apply in this case.
I came across that same information.

The best explanation I've found as to why this wont work is in this article.

There are a few other forums where people have linked to "solutions" to this problem but all of the links are not working. It almost feels as if someone's trying to hide the answer from me.

I've been working on trying to trick the SetValue function by wrapping my structure in an Object.....

Seeing as this is an example application (for other developers) I'm really looking for a simple way to do this................................

This is anything but simple.
Nov 18 '08 #14
balabaster
797 Expert 512MB
Ha! Got it!

C#
Expand|Select|Wrap|Line Numbers
  1. public static void setValue(object var, string fieldName, string newValue)
  2. {
  3.     Type t = var.GetType();
  4.     FieldInfo f = t.GetField(fieldName);
  5.     if (t.IsValueType)
  6.     {
  7.         ValueType vt = (ValueType)var;
  8.         f.SetValue(vt, newValue);
  9.     }
  10.     else
  11.     {
  12.         f.SetValue(var, newValue);
  13.     }
  14. }
  15.  
  16. static void Main(string[] args)
  17. {
  18.     MyStruct m = new MyStruct();
  19.     m.item1 = "Old Value";
  20.  
  21.     Type t = typeof(MyStruct);
  22.     BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
  23.     FieldInfo f = t.GetField("item1", flags);
  24.  
  25.     Console.WriteLine(f.GetValue(m));
  26.     setValue(m, f.Name, "New Value");
  27.     Console.WriteLine(f.GetValue(m));
  28.  
  29.     Console.ReadKey();
  30. }
We can apply this to our situation just trading up the boolean values for the strings...

It's because Structures are referenced differently than Classes... we need to tell reflection to update a ByVal object and not a ByRef object. Structures are treated in the same way as regular variables - albeit complex variables. Classes are treated as objects and as such the two are referenced slightly differently.
Nov 18 '08 #15
balabaster
797 Expert 512MB
Maybe I spoke too soon - I thought I had it...

What we have to do is pass it byval to a function that will then pass it back out as the return value:

Expand|Select|Wrap|Line Numbers
  1. Public Sub SetValue(ByVal var As Object, _
  2.                              ByVal FieldName As String, _
  3.                              ByVal NewValue As Boolean)
  4.  
  5.   Dim t As Type = var.GetType()
  6.   Dim f As FieldInfo = t.GetField(fieldName)
  7.   If t.IsValueType Then
  8.     Dim vt As ValueType = DirectCast(var, ValueType)
  9.     f.SetValue(vt, NewValue)
  10.   Else
  11.     f.SetValue(var, NewValue)
  12.   End If
  13.   Return var
  14.  
  15. End Sub
  16.  
  17. 'Then you'd need to call this method to do the set...
  18. myStructInstance = DirectCast(SetValue(passVal, item.Name, false), MyStruct)
I have to say it's not the prettiest code in the world... but it'll do the job...
Nov 18 '08 #16
Frinavale
9,735 Expert Mod 8TB
VB.NET Version
Expand|Select|Wrap|Line Numbers
  1.  Private Sub setValue(ByRef var As Object, ByVal fieldName As String, ByVal newValue As Short)
  2.         Dim t As Type = var.GetType
  3.         Dim fi As FieldInfo = t.GetField(fieldName)
  4.         If (t.IsValueType) Then
  5.             Dim vt As ValueType = CType(var, ValueType)
  6.             fi.SetValue(vt, newValue)
  7.         Else
  8.             fi.SetValue(var, newValue)
  9.         End If
  10.     End Sub
  11.  
Nov 18 '08 #17
Frinavale
9,735 Expert Mod 8TB
Maybe I spoke too soon - I thought I had it...

What we have to do is pass it byval to a function that will then pass it back out as the return value:


I have to say it's not the prettiest code in the world... but it'll do the job...
I passed ByRef....

-Frinny
Nov 18 '08 #18
balabaster
797 Expert 512MB
I passed ByRef....

-Frinny
Hmm... I tried that and it failed - but then, I was using C# and it freaked out when I tried to pass a "value" object byref. I guess with VBs ByRef/ByVal keywords you can force the issue and it'll work just nicely...

Glad we got there in the end :)
Nov 18 '08 #19

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

5 posts views Thread by kazack | last post: by
15 posts views Thread by damian birchler | last post: by
2 posts views Thread by Madhav | last post: by
14 posts views Thread by Kavya | last post: by
3 posts views Thread by cman | last post: by
5 posts views Thread by =?Utf-8?B?QXlrdXQgRXJnaW4=?= | last post: by
1 post views Thread by CARIGAR | last post: by
reply views Thread by suresh191 | last post: by
reply views Thread by Gurmeet2796 | last post: by
reply views Thread by mdpf | last post: by
reply views Thread by harlem98 | last post: by
reply views Thread by listenups61195 | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.