We help IT Professionals succeed at work.

How to pass user-defined type to class method?

Jim_S
Jim_S asked
on
How do I pass a user-defined type to a class method?

I want to make up a user-defined type and pass an instance of
that type to a method in a class.

E.g.

Public type foo_t
    foo1 as integer
end type

Dim foo_instance as foo_t
call obj_instance.class_method(foo_instance)

In the class module:
Public sub class_method(fooey as foo_t)
     fooey.foo1 = 42
end sub


I have received either of the following two error messages
depending on whether or not a copy of the user-type
declaration was present in the class module.



Compile error:

Private Enum and user defined types cannot be used as parameters
or return types for public procedures, public data members,
or fields of public user defined types


Compile error:

Only public user defined types defined in public object modules
can be used as parameters or return types for public procedures
of class modules or as fields of public user defined types.



I do not see why this should be so difficult?  It is done in C++
every day.  The class is strictly in-process; marshalling is not
a consideration.  So what is the secret of how to get my user-type
data into the class method?
Comment
Watch Question

BRONZE EXPERT
Top Expert 2012
Commented:
Your best bet would be to use a variant array or define a new class with properties that match the structure of the UDT.

Anthony

Commented:
If that is in the same project, change "Public" to "Friend"
Public sub class_method(fooey as foo_t)
to:
Friend sub class_method(fooey as foo_t)


If that method must be PUBLIC, accessible from other projects (in-process or out-of-process), your project must be ActiveX DLL or ActiveX EXE - then you'll be able to put declaration to "public object module" instead of "bas module".

You can see sample in this PAQ (8 pts):
http://www.experts-exchange.com/jsp/qShow.jsp?qid=20117807

Commented:
Another solution would be to send the pointer to the variable. Something like this..

Private Declare Sub CopyMem Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

  Public Sub Class_method(fooey as Long)

    Dim Data As foo_t

    Data.foo1 = 42
    Call CopyMem(ByVal fooey, Data, LenB(foo_t))

  End Sub

Now the call to this method would look like...

  Dim foo_instance as foo_t
  call obj_instance.class_method(VarPtr(foo_instance))

Commented:
Note however that the above solution will only work as presented when dealing with simple data types, i.e. long, integer, byte. When the type variable contains a complex data type like string, variant etc you will have to add another call, you need to reset the state of the local variable, you can use the ZeroMemory API for this. The method should be coded something like this...

  Private Declare Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As Long)

  Public Sub Class_method(fooey as Long)

    Dim Data As foo_t

    Data.foo1 = 42
    Call CopyMem(ByVal fooey, Data, LenB(foo_t))
    Call ZeroMemory(Data, LenB(foo_t))

  End Sub

Author

Commented:
ameba,


I downloaded the prior answer as you suggested.  This does
not work for me.  Part of the answer contains the following
few lines:

   With MyFrm.a1
       .X = 10
       MsgBox .X
   End With

This shows a scalar (10) being stored in a single element
of "a1."  I wanted to pass an entire user-type of data to
an object.

BTW.  I see in the messages and documentation many
references to "Public" objects and "Private" objects.
I do not know how to tell a Public from a Private.
To create my object, I pulled down the "Project"
menu and clicked "Add Class Module."  Nobody ever asked
me whether I wanted to call it public or private.  
Could you please explain this?



Vbmaster,

This use of RtlMoveMemory is really interesting.  Thank you for
sharing it.  However, I do not think it would be appropriate
here.  Almost all my data elements are variable-length strings.
And it kinda makes my VB program look more like a C program.
Of course, if I were writing in C, I would not have this problem
in the first place.

Commented:
>clicked "Add Class Module."  Nobody ever asked me whether I wanted to call it public or private

They will be Private in Standard EXE project.
And there is no option to change this or even see Instancing property (You can set focus to class module and press F4 - there are only 3 properties - Name, and 2 Data properties)

Your project must be ActiveX EXE or ActiveX DLL - only those projects allow other instancing values (Public MultiUse, Public Not Creatable ...) for classes.  And only those projects allow your classes to be instantiated by OTHER application.
To change project type use menu Project, Properties, Project Type.


I think this is what you need, correct me if I'm wrong:

If you don't want your class to be created or be visible to other applications, use Standard Exe project.

For Standard EXE project, solution is simple - change "Public" to "Friend".  

Commented:
' pass udt - simple case, you'll need one form, project is Standard EXE
' Form1 code ------------------------
Option Explicit
Private Type UDT_EXPERT
   Name As String
   Points As Integer
End Type

Private Sub Form_Click()
    Dim x As UDT_EXPERT
   
    x.Name = "John"
    x.Points = 100
    Test x
End Sub

' change Friend to Public to get error "Private ... cannot be used ..."
Friend Sub Test(arg As UDT_EXPERT)
    MsgBox "Name: " & arg.Name & vbCrLf & "Points: " & arg.Points
End Sub

Commented:
"This use of RtlMoveMemory is really interesting.  Thank you for sharing it.  However, I do not think it would be appropriate here. Almost all my data elements are variable-length strings."

There's no problem dealing with strings as long as you use the ZeroMemory API as presented in the 2nd solution. After all strings are just pointers to the actual unicode data. And all you do is move the pointer to another position in memory (and no need to worry about the string being referenced somewhere else in memory as you zeroing out the data using the ZeroMemory API). But I do agree with you, this might make it harder to read if you are not too familiar with API's.

Commented:
' pass udt - complex case
--------------------------------
'Friend' works only in the same project.

If UDT must be Public, so it can be passed between components/applications, then it must be defined in public class module.
You cannot have 'public class module' in Standard EXE project.
Project should be ActiveX EXE (or any other ActiveX project type)

Start new ActiveX EXE project.
Class1 is created automatically. (Now, it shows Instancing and Persistable properties.)

Set project Start Mode to Standalone: menu Project, Project properties, Components tab
(this is to simplify test - for Start Mode 'ActiveX Component', we'd need two projects)

We'll have two classes, one module and one form in our project:


1. In the class module we have a method which accepts UDT as argument
'-----------------------------------------------
' Class1
Option Explicit

Friend Sub Test(arg As UDT_EXPERT)
     MsgBox "Name: " & arg.Name & vbCrLf & "Points: " & arg.Points
     arg.Points = 42
End Sub
'-----------------------------------------------


2.  "UDT must be defined in public class module"
Add class module to put our UDT definition, set Instancing=6
'-----------------------------------------------
' Class2, set Instancing=6 'GlobalMultiUse
Option Explicit

Public Type UDT_EXPERT
     Name As String
     Points As Integer
End Type
'-----------------------------------------------


3. Our test form
'-----------------------------------------------
' Form1
Option Explicit

Private Sub Form_Click()
     Dim x As UDT_EXPERT
     Dim objClass1 As Class1
   
     ' create instance of Class1
     Set objClass1 = New Class1
   
     x.Name = "John"
     x.Points = 100
   
     objClass1.Test x   ' pass UDT
     objClass1.Test x   ' pass UDT

End Sub
'-----------------------------------------------


4. Form can be set as 'Startup Object' only in Standard EXE project
Here, we'll set 'Sub Main' as Startup Object (menu Poject, Project properties, Startup Object)
We'll need standard bas module for Sub main (menu Project, Add Module)
'-----------------------------------------------
' Module1
Option Explicit

Sub main()
     Form1.Show
End Sub
'-----------------------------------------------

Press F5 and click the form.

Author

Commented:
This is getting way too complicated.  Initially, I just wanted to
pass a group of about 8 or 9 values in one UDT.  The need to define
a second class module or run ZeroMemory is more trouble than it is
worth.  

So I have changed my code to use a variant array as per the
suggestion of acperkins.  Not nearly so sophisticated as these
other suggestions, but it is a simple way to get the job done.

I appreciate all of your comments.  I have learned a lot from both
ameba and from vbmaster.  Were there a way of awarding points to
more than one person, I would certainly award points to both of you.
(HotDispatch allows one to distribute an award among up to 5 experts.)

Commented:
You didn't answer if your project is Standard Exe.

Anyway, if you are not perfectly happy with the answer, you should ask for more info - do not punish the expert with the bad grade.