How to fix an import from AD - windows

I'm not a developer, i'm trying to understand why my IT ticketing software is unable to import people from the windows AD. It was working ok a month ago. I have no access to the windows AD but they said "no changes or upgrades" recently made
I have isolated problem is related to the AD "manager" field, meaning, my ticketing software has an option not to try to import the manager field(or whatever it is) and it will work fine
Here is the code that performs the import
Option Explicit On ' Change to Off instead of On to disable the need to declare variables with Dim. (NOT recommended)
Option Strict Off ' Change to Off instead of On to cast variables automatically. (NOT recommended)
Option Compare Binary ' Change to Text instead of Binary to let string "A" equal to "a".
Imports System
Imports System.Data
Imports System.Collections
This is a transformation script class. Please take notice of the following rules:
' - You are not allowed to change the name of the class (must be "TransformationScript")
' - You are not allowed to change the namespace the class is in (the class "TransformationScript" must not be in a namespace).
' - You are not allowed to change the name of the method (must be "Transform")
' - You are not allowed to change the parameters of the "Transform" method
' This method is called by the transformation logic. The dt parameter contains the data table with the data to transform.
' Any additional arguments are passed in the additionalArguments list.
Public Class TransformationScript
Public Sub Transform (ByVal dt As System.Data.DataTable, ByVal additionalArguments As SortedList)
For Each row As DataRow In dt.Rows
Dim strValue As String
If Not String.IsNullorEmpty(row.Item("manager").ToString)
strValue = row.Item("manager").ToString
Dim strArray() As String
Dim strManager As String
Dim strManagerDisplayName As String
split the AD manager object on "OU" and the results to array
strArray = strValue.Split("OU")
get string array position "0" that contains the manager name
strManager = strArray(0)
clean up the Manager display and remove the "CN=" and "\" and the last ","
strManagerDisplayName = strManager.Replace("CN=","")
strManagerDisplayName = strManagerDisplayName.Replace("\","")
strManagerDisplayName = strManagerDisplayName.Remove(strManagerDisplayName.Length-1)
return the Manager Display Name
row.Item("manager") = strManagerDisplayName
End If
Next
End Sub
End Class
I want to understand what the code is trying to do and possible root causes of what could be causing the issue
Thanks in advance

Related

Is there a faster way to get file metadata than by using the shell COM component?

Reading various answers here and elsewhere, I pieced together this bit to get the file metadata that I need:
Public Class windows_metadata_helper
Public Shared shell As New Shell32.Shell
Public Shared indices_of_interest As New Dictionary(Of Integer, String)
Public Shared path_index As Integer
Shared Sub New()
'snipped long piece code for figuring out the indices of the attributes that I need, they are stored in indices_of_interest, for example 0:Name
End Sub
Public Shared Function get_interesting_data(path) As Dictionary(Of String, String)
Dim fi As New IO.FileInfo(path)
Dim f_dir = shell.NameSpace(fi.DirectoryName)
Dim data As New Dictionary(Of String, String)
For Each item In f_dir.Items()
If f_dir.GetDetailsOf(item, path_index) = fi.FullName Then
For Each kvp In indices_of_interest
Dim val = f_dir.GetDetailsOf(item, kvp.Key)
If Not String.IsNullOrEmpty(val) Then data.Add(kvp.Value, val)
Next
Exit For
End If
Next
Return data
End Function
End Class
Its not the most efficient code in the world, namely getting the path attribute of each file in the directory to identify the file I'm actually interested in. Optimizing this to only read the path attribute of each file once makes it around 50% faster (tested by letting it take the first file it finds whether its the right one or not) but regardless, its far slower than expected.
It needs to fetch 24 attributes from each file and it needs to find around 20k files from within ~100k, currently this takes an entire hour.
Profiling tells me that CPU is the bottleneck and whatever is taking up the cycles I can't see since its 99% inside the Shell32.Folder.GetDetailsOf method.
Is there a faster way to get the metadata? Answer doesn't have to be vb or .net specific.
Since you are seeking maximum speed, I suggest that you enable Option Strict for your code and make the necessary modifications that will be suggested by the IDE. This will eliminate unnecessary type conversions.
For instance,
Public Shared Function get_interesting_data(path) As Dictionary(Of String, String)
should be:
Public Shared Function get_interesting_data(path As String) As Dictionary(Of String, String)
Instead of enumerating the Shell32.Folder.Items collection, use the Shell32.Folder.ParseName Method to directly retrieve a FolderItem object. This object can be cast to a Shell32.ShellFolderItem that will allow using the ShellFolderItem.ExtendedProperty method.
There are two ways to specify a property. The first is to assign the
property's well-known name, such as "Author" or "Date", to sPropName.
However, each property is a member of a Component Object Model (COM)
property set and can also be identified by specifying its format ID
(FMTID) and property ID (PID). An FMTID is a GUID that identifies the
property set, and a PID is an integer that identifies a particular
property within the property set.
Specifying a property by its FMTID/PID values is usually more
efficient than using its name. To use a property's FMTID/PID values
with ExtendedProperty, they must be combined into an SCID. An SCID is
a string that contains the FMTID/PID values in the form "FMTID**PID",
where the FMTID is the string form of the property set's GUID. For
example, the SCID of the summary information property set's author
property is "{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 4".
Many FMTID/PID values can be found under links presented at Windows Properties.
You can find the full property table here (scroll down).
Putting this together for some selected properties:
Public Shared Function get_interesting_data(path As String) As Dictionary(Of String, String)
Dim fi As New IO.FileInfo(path)
Dim f_dir As Shell32.Folder = shell.NameSpace(fi.DirectoryName)
' instead of enumerating f_dir.Items to find the file of interest
' directly retrieve the item reference
Dim item As Shell32.ShellFolderItem = DirectCast(f_dir.ParseName(fi.Name), Shell32.ShellFolderItem)
Dim scid_Bitrate As String = "{64440490-4C8B-11D1-8B70-080036B11A03} 4" ' Audio: System.Audio.EncodingBitrate
Dim scid_Title As String = "{F29F85E0 - 4.0FF9-1068-AB91-08002B27B3D9} 2" ' Core: System.Title
Dim scid_Created As String = "{B725F130-47EF-101A-A5F1-02608C9EEBAC} 15" ' Core: System.DateCreated
Dim scid_Copyright As String = "{64440492-4C8B-11D1-8B70-080036B11A03} 11" ' Core: System.Copyright
Dim scid_Publisher As String = "{64440492-4C8B-11D1-8B70-080036B11A03} 30" ' Media: System.Media.Publisher
Dim scid_FullDetails As String = "{C9944A21-A406-48FE-8225-AEC7E24C211B} 2" ' PropList: System.PropList.FullDetails
Dim bitrate As Object = item.ExtendedProperty(scid_Bitrate)
Dim title As Object = item.ExtendedProperty(scid_Title)
Dim created As Object = item.ExtendedProperty(scid_Created)
Dim copyright As Object = item.ExtendedProperty(scid_Copyright)
Dim publisher As Object = item.ExtendedProperty(scid_Publisher)
Dim fullDetails As Object = item.ExtendedProperty(scid_FullDetails)
Dim data As New Dictionary(Of String, String)
' save the retrieved properties
Return data
End Function
I do not know if this technique of retrieving the properties is faster than you have currently using GetDetailsOf, but the other changes should make some improvement.

JavaEdit search function not locating visible element

I've got two library functions:
Function searchWindow(title)
Set searchWindow = Window("title:=" + title)
End Function
And
Function searchField(label)
Set searchField = JavaEdit("attached text:=" + label)
End Function
Here I'm testing them:
Environment.Loadfromfile("C:\UFTConstants\constants.ini")
Set loginFrame = searchWindow(Environment.Value("frameLogin"))
loginFrame.Click
Set userField = searchField("User ID / Ci-Usager")
userField.Set "test"
The first function works fine, and it's title property matches that of the application. However, the second will not find the text field, despite properties matching:
The error:
I've tried other properties as well, tagname, various class properties, as well as combinations of all three, and none are producing a find.
Any ideas?
First Update
As per request, full spy screencap:
The full line generated by the recording tool:
JavaWindow("Application Name").JavaDialog("Window Title").JavaEdit("User ID / Ci-Usager").Set "user"
However, when I try to re-create this programmatically, I get the same error, only for JavaWindow instead:
"Cannot identify the object [JavaWindow] of (class JavaWindow)..."
Possible Java set-up issue? This would not explain why recording can still locate Java objects, however.
Second Update
Here are the recognition properties:
I have ensured that all properties are set, still unable to locate.
Final Update
Ok, I've reduced the code to absolute barebones. No external constant file, no external library calls. I've copied the full extent of what is recorded in recording mode. I've printed each variable to ensure accuracy. I've included the full object hierarchy:
Set objWin = JavaWindow("label:=<redacted>")
objWin.SetTOProperty "to_class", "JavaWindow"
objWin.SetTOProperty "toolkit class", "javax.swing.JFrame"
MsgBox objWin.GetTOProperty("label")
MsgBox objWin.GetTOProperty("to_class")
MsgBox objWin.GetTOProperty("toolkit class")
Set objDialog = objWin.JavaDialog("label:=<redacted>")
objDialog.SetTOProperty "to_class", "JavaDialog"
objDialog.SetTOProperty "toolkit class", "<redacted>.LoginDialog"
MsgBox objDialog.GetTOProperty("label")
MsgBox objDialog.GetTOProperty("to_class")
MsgBox objDialog.GetTOProperty("toolkit class")
Set objEdit = objDialog.JavaEdit("attached text:=User ID / Ci-Usager")
objEdit.SetTOProperty "to_class", "JavaEdit"
objEdit.SetTOProperty "toolkit class", "javax.swing.JTextField"
MsgBox objEdit.GetTOProperty("attached text")
MsgBox objEdit.GetTOProperty("to_class")
MsgBox objEdit.GetTOProperty("toolkit class")
objEdit.Set "test"
Note the redacted text is to remove identifying elements from the code. They have been triple-checked on my side and are correct.
This still does not work.
However, recording the same does. What gives?
I think you have to mention the full hierarchy while working with the Javaedit field. Try re-writing the code for the function searchField as :
Function searchField(label)
Dim objFrame
Set objFrame = searchWindow(Environment.Value("frameLogin"))
Set searchField = objFrame.JavaEdit("attached text:=" + label) 'Javaedit should be the child of the login window. You had to mention the full hierarchy here
End Function

Retrieved parent folders from a document aren't always correct

We're migrating a library content from Filenet Content Services to Filenet P8.
So we wrote an extractor which outputs both folders tree and document list, in XML format, each document with versions, properties and parent folder. This extractor relies on a home-made dll which virtualizes FileNet objects.
Documents are retrieved this way (huge sql request) :
Public Function getAllDocumentIds() As ADODB.Recordset
Dim cmdProperties As New Dictionary
cmdProperties.Item("Maximum Rows") = 0
cmdProperties.Item("Page Size") = 0
Set getAllDocumentIds = _
executeADOQuery("SELECT idmId, idmVerFileName, " & vbNewLine & _
" idmVerCreateDate, idmAddedByUser" & vbNewLine & _
" FROM FNDOCUMENT ORDER BY idmId ASC", & _
cmdProperties)
End Function
But we encounter issues when we retrieve parent folders this way (slightly modified to be used as an example) :
Public Function getFolders(document As IDMObjects.document) As Collection
Dim f As IDMObjects.Folder
' [...]
For Each f In document.FoldersFiledIn '
' folders retrieval
Next
End Function
For a little amount of documents, some "wrong" parent folders ("folders [the document is] filed in") are reported.
"Wrong" because the following way doesn't report the document has being filed in them (slightly modified code too) :
Public Function getDocumentIds(folder As IDMObjects.Folder) As Collection
Dim rs As ADODB.Recordset
Dim cmdProperties As New Dictionary
' Actually there is a control here, to prevent Filenet crashing.
' Indeed, setting "Page Size" to 0 crashes if no results are returned.
' *Maybe* a product bug.
cmdProperties.Add "SearchFolderName", internalObject.id ' Folder parent
cmdProperties.Item("Maximum Rows") = 0 ' No limits
cmdProperties.Item("Page Size") = 0 ' No limits. Crashes if Recordset is empty
' Here, the cmdProperties entries are copied to an
' ADODB.Command object, into the "properties" member.
' The query are copied to this object, into the "CommandText" member.
' Then ADODB.Command.Execute is invoked and returs an ADODB.RecordSet.
' So this Recordset contains all documents filled in this folder.
Set rs = executeADOQuery("SELECT * from FnDocument", cmdProperties)
Exit Function
End Function
We're working on a workaround, which may take more resources (for each document, double-check...). But understanding why we don't get same results could be relevant to check our library.
If I understand the problem correctly, I believe the quick answer is that the logical order of parent and child records in the result set can not be guaranteed by the query. You are making an assumption about the ID sequences. A document can be moved so there is nothing to guarantee a folder id will occur before that of a document id or vice versa. For a large document set, to solve this without recursion, defer the child records without a parent and resolve them later (in the sample I used a sentinel to flag/filter such records). Depending upon the number of 'orphaned' rows, you may be able to do this in memory, or it may require a second pass. The getrows method will allow you to handle 'huge' datasets, especially if you're using XML and don't want to run out of memory.

Co-ordinate conversion

I am making a simple coordinate converter with the help of eye4software. Following link provides the required Visual Basic 6 codes for the converter.
http://www.eye4software.com/products/gpstoolkit/source/vb/datumtransformation/
I have followed said process according to the given details in the link.
Private Sub Form1_Load()
Private objProjection As GpsProjection
Private objDatumSrc As GpsDatumParameters
Private objDatumDst As GpsDatumParameters
Set objProjection = CreateObject("Eye4Software.GpsProjection")
Set objDatumSrc = CreateObject("Eye4Software.GpsDatumParameters")
Set objDatumDst = CreateObject("Eye4Software.GpsDatumParameters")
End Sub
Option Explicit
Private objProjection As GpsProjection
Private objDatumSrc As GpsDatumParameters
Private objDatumDst As GpsDatumParameters
Private Sub CommandTranslate_Click()
' Set Source Datum ( WGS84 )
' The ID for WGS84 is 4326, see 'http://www.eye4software.com/resources/datums' for a full list of supported datums
' To convert from another datum, just change the code below (EPSG code)
objDatumSrc.LoadFromId (4326)
' Set Destination Datum ( NAD27 )
' The ID for NAD27 is 4267, see 'http://www.eye4software.com/resources/datums' for a full list of supported datums
' To convert to another datum, just change the code below (EPSG code)
objDatumDst.LoadFromId (4267)
' Set Source coordinates
objProjection.Latitude = CDbl(Textlat1.Text)
objProjection.Longitude = CDbl(Textlon1.Text)
' Perform the datum transformation
objProjection.TransformDatum objDatumSrc, objDatumDst
' Display the result
Textlat2.Text = objProjection.Latitude
Textlon2.Text = objProjection.Longitude
End Sub
But i am getting a run time error for this code (objDatumSrc.LoadFromId (4326)) saying object required. Since i'm a beginner i was unable to solve this. please help me.
You have two objDatumSrc variables.
One is a private variable inside Form_Load - you are initialising that one.
The other one is a module-level one and you are not initialising that one.
Delete the Private variable declarations inside Form_Load
To me, it looks like you aren't understanding scope, but the real problem is a non-instantiated variable. Your declaration of objDatumSrc in the form load event will not be able to be seen in the rest of the form because The variables you are declaring outside of a method are not being instantiated.
Replace your current code with this...
Option Explicit
Private objProjection As New GpsProjection
Private objDatumSrc As New GpsDatumParameters
Private objDatumDst As New GpsDatumParameters
Private Sub CommandTranslate_Click()
' Set Source Datum ( WGS84 )
' The ID for WGS84 is 4326, see 'http://www.eye4software.com/resources/datums' for a full list of supported datums
' To convert from another datum, just change the code below (EPSG code)
objDatumSrc.LoadFromId (4326)
' Set Destination Datum ( NAD27 )
' The ID for NAD27 is 4267, see 'http://www.eye4software.com/resources/datums' for a full list of supported datums
' To convert to another datum, just change the code below (EPSG code)
objDatumDst.LoadFromId (4267)
' Set Source coordinates
objProjection.Latitude = CDbl(Textlat1.Text)
objProjection.Longitude = CDbl(Textlon1.Text)
' Perform the datum transformation
objProjection.TransformDatum objDatumSrc, objDatumDst
' Display the result
Textlat2.Text = objProjection.Latitude
Textlon2.Text = objProjection.Longitude
End Sub
The code here so obviously shouldn't compile, it is obvious that you are not showing your real code. For instance, what is your error handling? If you have done something like On Error Resume Next, then if the following lines raise errors, then the errors won't be reported.
Set objProjection = CreateObject("Eye4Software.GpsProjection")
Set objDatumSrc = CreateObject("Eye4Software.GpsDatumParameters")
Set objDatumDst = CreateObject("Eye4Software.GpsDatumParameters")
Since they would be set to Nothing, if you tried to execute methods and properties on objProjection, objDatumSrc, and objDatumDst, they would raise the error "object required".
And since this is likely not the code you have tried to run, can you verify that all the Program Ids e.g. "Eye4Software.GpsProject" are correct? In fact - have you registered these components? And why can't you instantiate these objects using the slightly cleaner notation, e.g.
Set objProjection = New Eye4Software.GpsProjection
?
Try either:
Call objDatumSrc.LoadFromId(4326)
or
objDatumSrc.LoadFromId 4326
VB gets a little funky doing method calls with parameters. If it's not in the expected format, some results may vary.

Self Inspection of VB6 UDTs

I have a feeling the answer to this is going to be "not possible", but I'll give it a shot...
I am in the unenviable position of modifying a legacy VB6 app with some enhancements. Converting to a smarter language isn't an option.
The app relies on a large collection of user defined types to move data around. I would like to define a common function that can take a reference to any of these types and extract the data contained.
In pseudo code, here's what I'm looking for:
Public Sub PrintUDT ( vData As Variant )
for each vDataMember in vData
print vDataMember.Name & ": " & vDataMember.value
next vDataMember
End Sub
It seems like this info needs to be available to COM somewhere... Any VB6 gurus out there care to take a shot?
Thanks,
Dan
Contrary to what others have said, it IS possible to get run-time type information for UDT's in VB6 (although it is not a built-in language feature). Microsoft's TypeLib Information Object Library (tlbinf32.dll) allows you to programmatically inspect COM type information at run-time. You should already have this component if you have Visual Studio installed: to add it to an existing VB6 project, go to Project->References and check the entry labeled "TypeLib Information." Note that you will have to distribute and register tlbinf32.dll in your application's setup program.
You can inspect UDT instances using the TypeLib Information component at run-time, as long as your UDT's are declared Public and are defined within a Public class. This is necessary in order to make VB6 generate COM-compatible type information for your UDT's (which can then be enumerated with various classes in the TypeLib Information component). The easiest way to meet this requirement would be to put all your UDT's into a public UserTypes class that will be compiled into an ActiveX DLL or ActiveX EXE.
Summary of a working example
This example contains three parts:
Part 1: Creating an ActiveX DLL project that will contain all the public UDT declarations
Part 2: Creating an example PrintUDT method to demonstrate how you can enumerate the fields of a UDT instance
Part 3: Creating a custom iterator class that allows you easily iterate through the fields of any public UDT and get field names and values.
The working example
Part 1: The ActiveX DLL
As I already mentioned, you need to make your UDT's public-accessible in order to enumerate them using the TypeLib Information component. The only way to accomplish this is to put your UDT's into a public class inside an ActiveX DLL or ActiveX EXE project. Other projects in your application that need to access your UDT's will then reference this new component.
To follow along with this example, start by creating a new ActiveX DLL project and name it UDTLibrary.
Next, rename the Class1 class module (this is added by default by the IDE) to UserTypes and add two user-defined types to the class, Person and Animal:
' UserTypes.cls '
Option Explicit
Public Type Person
FirstName As String
LastName As String
BirthDate As Date
End Type
Public Type Animal
Genus As String
Species As String
NumberOfLegs As Long
End Type
Listing 1: UserTypes.cls acts as a container for our UDT's
Next, change the Instancing property for the UserTypes class to "2-PublicNotCreatable". There is no reason for anyone to instantiate the UserTypes class directly, because it's simply acting as a public container for our UDT's.
Finally, make sure the Project Startup Object (under Project->Properties) is set to to "(None)" and compile the project. You should now have a new file called UDTLibrary.dll.
Part 2: Enumerating UDT Type Information
Now it's time to demonstrate how we can use TypeLib Object Library to implement a PrintUDT method.
First, start by creating a new Standard EXE project and call it whatever you like. Add a reference to the file UDTLibrary.dll that was created in Part 1. Since I just want to demonstrate how this works, we will use the Immediate window to test the code we will write.
Create a new Module, name it UDTUtils and add the following code to it:
'UDTUtils.bas'
Option Explicit
Public Sub PrintUDT(ByVal someUDT As Variant)
' Make sure we have a UDT and not something else... '
If VarType(someUDT) <> vbUserDefinedType Then
Err.Raise 5, , "Parameter passed to PrintUDT is not an instance of a user-defined type."
End If
' Get the type information for the UDT '
' (in COM parlance, a VB6 UDT is also known as VT_RECORD, Record, or struct...) '
Dim ri As RecordInfo
Set ri = TLI.TypeInfoFromRecordVariant(someUDT)
'If something went wrong, ri will be Nothing'
If ri Is Nothing Then
Err.Raise 5, , "Error retrieving RecordInfo for type '" & TypeName(someUDT) & "'"
Else
' Iterate through each field (member) of the UDT '
' and print the out the field name and value '
Dim member As MemberInfo
For Each member In ri.Members
'TLI.RecordField allows us to get/set UDT fields: '
' '
' * to get a fied: myVar = TLI.RecordField(someUDT, fieldName) '
' * to set a field TLI.RecordField(someUDT, fieldName) = newValue '
' '
Dim memberVal As Variant
memberVal = TLI.RecordField(someUDT, member.Name)
Debug.Print member.Name & " : " & memberVal
Next
End If
End Sub
Public Sub TestPrintUDT()
'Create a person instance and print it out...'
Dim p As Person
p.FirstName = "John"
p.LastName = "Doe"
p.BirthDate = #1/1/1950#
PrintUDT p
'Create an animal instance and print it out...'
Dim a As Animal
a.Genus = "Canus"
a.Species = "Familiaris"
a.NumberOfLegs = 4
PrintUDT a
End Sub
Listing 2: An example PrintUDT method and a simple test method
Part 3: Making it Object-Oriented
The above examples provide a "quick and dirty" demonstration of how to use the TypeLib Information Object Library to enumerate the fields of a UDT. In a real-world scenario, I would probably create a UDTMemberIterator class that would allow you to more easily iterate through the fields of UDT, along with a utility function in a module that creates a UDTMemberIterator for a given UDT instance. This would allow you to do something like the following in your code, which is much closer to the pseudo-code you posted in your question:
Dim member As UDTMember 'UDTMember wraps a TLI.MemberInfo instance'
For Each member In UDTMemberIteratorFor(someUDT)
Debug.Print member.Name & " : " & member.Value
Next
It's actually not too hard to do this, and we can re-use most of the code from the PrintUDT routine created in Part 2.
First, create a new ActiveX project and name it UDTTypeInformation or something similar.
Next, make sure that the Startup Object for the new project is set to "(None)".
The first thing to do is to create a simple wrapper class that will hide the details of the TLI.MemberInfo class from calling code and make it easy to get a UDT's field's name and value. I called this class UDTMember. The Instancing property for this class should be PublicNotCreatable.
'UDTMember.cls'
Option Explicit
Private m_value As Variant
Private m_name As String
Public Property Get Value() As Variant
Value = m_value
End Property
'Declared Friend because calling code should not be able to modify the value'
Friend Property Let Value(rhs As Variant)
m_value = rhs
End Property
Public Property Get Name() As String
Name = m_name
End Property
'Declared Friend because calling code should not be able to modify the value'
Friend Property Let Name(ByVal rhs As String)
m_name = rhs
End Property
Listing 3: The UDTMember wrapper class
Now we need to create an iterator class, UDTMemberIterator, that will allow us to use VB's For Each...In syntax to iterate the fields of a UDT instance. The Instancing property for this class should be set to PublicNotCreatable (we will define a utility method later that will create instances on behalf of calling code).
EDIT: (2/15/09) I've cleaned the code up a bit more.
'UDTMemberIterator.cls'
Option Explicit
Private m_members As Collection ' Collection of UDTMember objects '
' Meant to be called only by Utils.UDTMemberIteratorFor '
' '
' Sets up the iterator by reading the type info for '
' the passed-in UDT instance and wrapping the fields in '
' UDTMember objects '
Friend Sub Initialize(ByVal someUDT As Variant)
Set m_members = GetWrappedMembersForUDT(someUDT)
End Sub
Public Function Count() As Long
Count = m_members.Count
End Function
' This is the default method for this class [See Tools->Procedure Attributes] '
' '
Public Function Item(Index As Variant) As UDTMember
Set Item = GetWrappedUDTMember(m_members.Item(Index))
End Function
' This function returns the enumerator for this '
' collection in order to support For...Each syntax. '
' Its procedure ID is (-4) and marked "Hidden" [See Tools->Procedure Attributes] '
' '
Public Function NewEnum() As stdole.IUnknown
Set NewEnum = m_members.[_NewEnum]
End Function
' Returns a collection of UDTMember objects, where each element '
' holds the name and current value of one field from the passed-in UDT '
' '
Private Function GetWrappedMembersForUDT(ByVal someUDT As Variant) As Collection
Dim collWrappedMembers As New Collection
Dim ri As RecordInfo
Dim member As MemberInfo
Dim memberVal As Variant
Dim wrappedMember As UDTMember
' Try to get type information for the UDT... '
If VarType(someUDT) <> vbUserDefinedType Then
Fail "Parameter passed to GetWrappedMembersForUDT is not an instance of a user-defined type."
End If
Set ri = tli.TypeInfoFromRecordVariant(someUDT)
If ri Is Nothing Then
Fail "Error retrieving RecordInfo for type '" & TypeName(someUDT) & "'"
End If
' Wrap each UDT member in a UDTMember object... '
For Each member In ri.Members
Set wrappedMember = CreateWrappedUDTMember(someUDT, member)
collWrappedMembers.Add wrappedMember, member.Name
Next
Set GetWrappedMembersForUDT = collWrappedMembers
End Function
' Creates a UDTMember instance from a UDT instance and a MemberInfo object '
' '
Private Function CreateWrappedUDTMember(ByVal someUDT As Variant, ByVal member As MemberInfo) As UDTMember
Dim wrappedMember As UDTMember
Set wrappedMember = New UDTMember
With wrappedMember
.Name = member.Name
.Value = tli.RecordField(someUDT, member.Name)
End With
Set CreateWrappedUDTMember = wrappedMember
End Function
' Just a convenience method
'
Private Function Fail(ByVal message As String)
Err.Raise 5, TypeName(Me), message
End Function
Listing 4: The UDTMemberIterator class.
Note that in order to make this class iterable so that For Each can be used with it, you will have to set certain Procedure Attributes on the Item and _NewEnum methods (as noted in the code comments). You can change the Procedure Attributes from the Tools Menu (Tools->Procedure Attributes).
Finally, we need a utility function (UDTMemberIteratorFor in the very first code example in this section) that will create a UDTMemberIterator for a UDT instance, which we can then iterate with For Each. Create a new module called Utils and add the following code:
'Utils.bas'
Option Explicit
' Returns a UDTMemberIterator for the given UDT '
' '
' Example Usage: '
' '
' Dim member As UDTMember '
' '
' For Each member In UDTMemberIteratorFor(someUDT) '
' Debug.Print member.Name & ":" & member.Value '
' Next '
Public Function UDTMemberIteratorFor(ByVal udt As Variant) As UDTMemberIterator
Dim iterator As New UDTMemberIterator
iterator.Initialize udt
Set UDTMemberIteratorFor = iterator
End Function
Listing 5: The UDTMemberIteratorFor utility function.
Finally, compile the project and create a new project to test it out.
In your test projet, add a reference to the newly-created UDTTypeInformation.dll and the UDTLibrary.dll created in Part 1 and try out the following code in a new module:
'Module1.bas'
Option Explicit
Public Sub TestUDTMemberIterator()
Dim member As UDTMember
Dim p As Person
p.FirstName = "John"
p.LastName = "Doe"
p.BirthDate = #1/1/1950#
For Each member In UDTMemberIteratorFor(p)
Debug.Print member.Name & " : " & member.Value
Next
Dim a As Animal
a.Genus = "Canus"
a.Species = "Canine"
a.NumberOfLegs = 4
For Each member In UDTMemberIteratorFor(a)
Debug.Print member.Name & " : " & member.Value
Next
End Sub
Listing 6: Testing out the UDTMemberIterator class.
If you change all your Types to Classes. You have options. The big pitfall of changing from a type to a class is that you have to use the new keyworld. Every time there a declaration of a type variable add new.
Then you can use the variant keyword or CallByName. VB6 doesn't have anytype of reflection but you can make lists of valid fields and test to see if they are present for example
The Class Test has the following
Public Key As String
Public Data As String
You can then do the following
Private Sub Command1_Click()
Dim T As New Test 'This is NOT A MISTAKE read on as to why I did this.
T.Key = "Key"
T.Data = "One"
DoTest T
End Sub
Private Sub DoTest(V As Variant)
On Error Resume Next
Print V.Key
Print V.Data
Print V.DoesNotExist
If Err.Number = 438 Then Print "Does Not Exist"
Print CallByName(V, "Key", VbGet)
Print CallByName(V, "Data", VbGet)
Print CallByName(V, "DoesNotExist", VbGet)
If Err.Number = 438 Then Print "Does Not Exist"
End Sub
If you attempt to use a field that doesn't exist then error 438 will be raised. CallByName allows you to use strings to call the field and methods of a class.
What VB6 does when you declare Dim as New is quite interesting and will greatly minimize bugs in this conversion. You see this
Dim T as New Test
is not treated exactly the same as
Dim T as Test
Set T = new Test
For example this will work
Dim T as New Test
T.Key = "A Key"
Set T = Nothing
T.Key = "A New Key"
This will give a error
Dim T as Test
Set T = New Test
T.Key = "A Key"
Set T = Nothing
T.Key = "A New Key"
The reason for this is that in the first example VB6 flags T so that anytime a member is accessed it check whether the T is nothing. If it is it will automatically create a new instance of the Test Class and then assign the variable.
In the second example VB doesn't add this behavior.
In most project we rigorously make sure we go Dim T as Test, Set T = New Test. But in your case since you want to convert Types into Classes with the least amount of side effects using Dim T as New Test is the way to go. This is because the Dim as New cause the variable to mimic the way types works more closely.
#Dan,
It looks like your trying to use RTTI of a UDT. I don't think you can really get that information without knowing about the UDT before run-time.
To get you started try:
Understanding UDTs
Because of not having this reflection capability. I would create my own RTTI to my UDTs.
To give you a baseline. Try this:
Type test
RTTI as String
a as Long
b as Long
c as Long
d as Integer
end type
You can write a utility that will open every source file and add The RTTI with the name of the type to the UDT. Probably would be better to put all the UDTs in a common file.
The RTTI would be something like this:
"String:Long:Long:Long:Integer"
Using the memory of the UDT you can extract the values.

Resources