Solved

Passing a UDT Variable to a Sub Argument Typed as Variant

Posted on 2004-08-20
20
790 Views
Last Modified: 2006-11-17
I want to develop a generic, reusable Sub procedure to accept any user-defined type (UDT) as a variant argument. I plan to call this Sub procedure from Form modules. I want the Sub procedure to accept any UDT as the variant argument. I envision the Sub procedure to be defined as

   Public MySub(ByVal varUDT as Variant)

I'm getting all confused as to (1) where (what kind of module) do I define the UDT? .BAS module? Class module? Can I define a single variable as a UDT type and have this variable be used anywhere in an application? If so, where do I place the statement

   Public pubVar as MyUDT

where pubVar is the name of the variable and MyUDT is defined by a Type statement somewhere.

I'm getting all twisted up with what kind of modules to place various statements so that I can have a Public Sub Procedure and public variables of UDT type.

The above text is a simplified version of my problem. In actuality, I'm trying to write a generic procedure to add, change, delete and insert fixed-length records into any binary file (specified by an argument). In my struggles with this problem I've gotten the following error message several times:
 
    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 need a more elementary explanation than this authentic Visual Basic gibberish.



0
Comment
Question by:Rodr
  • 8
  • 6
  • 3
  • +1
20 Comments
 

Assisted Solution

by:Imbue
Imbue earned 100 total points
ID: 11857077
Try this.

File > Add project > ActiveX DLL

In this new project put your UDT in the class module. Make sure you make it public. (Public Type udtWhatever)

Now go back to your first project (Standard EXE) and go to Project > References. Check the name of the second project. Now you should be able to pass those UDTs back and forth all over the place.
0
 
LVL 7

Accepted Solution

by:
Enlade earned 100 total points
ID: 11859393

I don't think he is using more then one processes.  That is I don't think he is having trouble passing data from one of his processes to another.

Basically, you define all your "user defined types" in a .BAS module.  Or, you create a Class that will contain the data.  Most likely you are not dealing with Classes in your program.  So, just create a module called modTypes and put them in there as Public types.  I normally create a few modules ever time I start a new program (whether I end up using them or not).

modTypes  -  Contains all my global types
modGlobs  -  Contains all my global variables
modConst  -  Contains all my Constants
modGenFuncs  -  Contains any general functions that I might use (not specific to this application).
modResource -  Contains all the defines I might need to address my resource data.

In any case, put all your Public Type definitions inside of modTypes.  Then put your Public global variables inside of modGlobs.  Remember you can't pass a user defined type between forms, so sometimes you need to have temporary global locations to store your data (kind of simulate passing types between forms by useing a global variable).  I am not sure why Microsoft doesn't allow such a useful thing, but they don't.

Hope that helps.

0
 
LVL 7

Expert Comment

by:Enlade
ID: 11859409

Oh, I mentioned the alternitive, which is to encapsulate your data inside of a class.  In that case you will just define a class for each data type and give access to each field through the class methods and properties.  We can talk about that if you want, but from what you are saying I think you just need to define the types as public in a .BAS module.
0
 

Expert Comment

by:Imbue
ID: 11859676
Enlade, you say that it is not possible to pass UDTs between forms. Actually, using my first answer it is possible to have a public function of a form that takes and returns a UDT. Also, no one is talking about having more than one process.

To pass around UDTs you must put them in a "public object modules," according to your error message. If you make a class in your project it cannot be totally public, so you have to add an ActiveX DLL project and create a public class module in that, and you are doing what the error message says to fix your problem.
0
 
LVL 7

Expert Comment

by:Enlade
ID: 11860088

Remember I am saying between "Forms".  Clearly you can pass a type structure to a module function, but you cannot pass a structure to a form even if the forms function is public.  For example, try passing this to a function on a form:

Public Type MyType
  Field1 As Long
  Field2 As Long
End Type

I think that you might be giving him a much larger answer then he is looking for (though I'm not sure).  It seems to me that he wants to define some types and he is having trouble passing variables of those types between forms.  Though, maybe he is talking about creating class objects and passing them....and/or creating his own ActiveX object and linking it into his project.  So he will need to clearify that for us.

In any case, I'm not sure what you mean by "if you make a class in your project it cannot be totally public"?  It is totally public to your process.  Its just not totally public to some other process that your application may spawn.  But clearly I can create a class within my project that is totally public to my entire project and even create an object of that class that is also entirely public to all the code in my project.  Maybe I just don't understand what you are trying to suggest.  It seems like you are suggesting that he should create some ActiveX object to encapulate his data and that seems like an overkill.  Though maybe that is what he wants and I'm just not understanding him.  I'll wait for him to clearify his question for me.
0
 

Author Comment

by:Rodr
ID: 11860242
Imbue--

I tried your suggestions (File > Add project > ActiveX DLL, etc.)

I put the following UDT in a Class Module in Project2:

    Public Type BinaryFileUtilityType
        PostDateTime as Variant
        Amount as Single
        Bool as Boolean
        LongInteg as Long
        Integ as Integer
        Desc as String * 25
    End Type


When I try to run the application I get the following error message:

   Compile error:

   Fixed-length strings and use of the 'new' qualifier are not allowed for fields in public user defined type defined in an object module.

and the 'Desc as String *' is highlighted by the error.

My problem is simple. Please don't read any complexity into it. All I want to do is create a generic binary file update procedure to add, change, delete and insert records into any binary file. Any such record will be defined by its own UDT. By "generic...procedure" I mean a procedure in some kind of module that I can develop once and reuse it over and over again for any record (UDT) structure in any application. Naturally, each record (UDT) structure will be unique, which is why I want to type the record parameter as a Variant in the generic procedure's Sub statement.

0
 
LVL 7

Expert Comment

by:Enlade
ID: 11860439

The problem is that you are using the term UDT and a lot of people simple understand that to mean "Class".  Well, a Class is a UDT, but so is a simple type structure like you are showing above.  Just don't use a Class module at all.  Just create your public type in a regular old module.

0
 
LVL 7

Expert Comment

by:Enlade
ID: 11860494

Oh, I think I see what you want to do.  You want to make this a generic function that takes any such type structure and writes it to a record in the file.  This is not a simple task.  I think you might have trouble with the fact that a Variant has additional information that is beyond that contained in the strucuture that you are passing.  Right now you are having trouble passing the structure, but you might end up with a larger problem when you try to write the variant (that contains the structure) into the file (thinking that it is just a file containing your original structures).  I'll need to think about this a bit.
0
 
LVL 7

Expert Comment

by:Enlade
ID: 11860532

I now understand what Imbue meant by "totally public".  I'll need to rethink this problem a bit to see if there is an easier solution.

0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 7

Expert Comment

by:Enlade
ID: 11860606

Nope, no easier way that I can think of (short of copying the structure into a byte array or something).  So, I'll defer to Imbue on this one.  Still, I think you might end up with another problem when you try to write the variant to disk.
0
 

Expert Comment

by:Imbue
ID: 11860612
Ok, unless I am missing something (and I could very well be), you will need to have an ActiveX DLL project. If you only have the EXE project then you cannot get UDT data into the type library for that program (that means you cannot make it public enough to do cool stuff with it).

Anyway, to make it simple the fixed length string is causing your problem now. I don't know of an really slick ways to deal with that, though. You could make it variable length, or use a byte array. Why does it need to be fixed length?


Enlade, when you said "you cannot pass a structure to a form even if the forms function is public" I don't think you could be more wrong. If the UDT is in the type library you can do just about anything with it, including passing it to and from classes (forms).
0
 

Expert Comment

by:Imbue
ID: 11860642
You will not have any problems writing a variant (vs a real UDT) to a file. The variable length string shouln't cause any problems either, VB is really flexible and handles this stuff very well.
0
 
LVL 7

Expert Comment

by:Enlade
ID: 11860651

It was my missunderstanding.  I over simplfied the problem.

As far as not being able to pass type structures to a form.  You will have to explain to me how to do that.  Tell me how to pass this public type structure to a form without first making the structure into a Class.

Public Type MyType
  Field1 As Long
  Field2 As Long
End Type

In any case, you are on the right path with what you are suggesting.  I was way off.  Sorry.
0
 

Expert Comment

by:Imbue
ID: 11860670
Enlade, all you need to do to pass that structure to a form is get it into a public type library. The easiest way to do that is plop it into an ActiveX DLL's class. Please understand that this is not making it into a class. It is not a class, it is a user defined type.

Anyway, then once you have the project references set up you just make your public sub in your form like Public Sub Test (TestUDT as MyType).

The difference from being a class and being in a class is.


Public Type MyType 'A UDT defined in a class.
  Field1 As Long
  Field2 As Long
End Type


Public Field1 As Long 'Using a class to act like a UDT (not a real UDT)
Public Field2 As Long
0
 
LVL 3

Expert Comment

by:PocketLintPPC
ID: 11862089
You can pass a UDT as a param to a form, just make sure you use FRIEND and not PUBLIC as the scope identifier..


in a public module...
Public Type MyType
  Field1 As Long
  Field2 As Long
End Type


on a form..
Friend Sub Test(m As MyType)
   msgbox "Madeit"
End Sub
0
 
LVL 3

Expert Comment

by:PocketLintPPC
ID: 11862162
As for making a generic method,
Remember that passing a UDT requires early binding. This means that the structure of the UDT MUST be know at compile time. So a true generic way is not really possible. Well It is if you use some balck belt thing to actually pass along the address of the UDT then inside the SUB you use a few API calls to copy the data at that loction into a local udt. But in the end, again the UDT must be known if you plan on manipulating the UDT contents.

A similar and much safer way might to use the following as a jumping off point to do what you want...
Make a master UDT that contains all the sub UDT's and include a TYPE field to let the reciever know which sub UDT to work with..


Public Type MasterType
   UDT1 as UDT1_Type
   UDT2 as UDT2_Type
   MType as TypeEnum
End type

Also include the following before all else...
Public Enum TypeEnum
   eUDT1
   eUDT2
End Enum

Public Type UDT1_Type
        PostDateTime as Variant
        Amount as Single
        Bool as Boolean
        LongInteg as Long
        Integ as Integer
        Desc as String * 25
End Type

Public Type UDT2_Type
        PostDateTime as Variant
        Total as Single
        Bool as Boolean
        LongInteg as Long
        Integ as Integer
End Type


Then dim it with something like..
Public M as MasterType

then use it like...

' Set the UDT to use
M.MType = eUDT1  

' Set some Data        
With M.UDT1
   .PostDateTime = Now
   .Amount = 100
End With


'Call the function
call Form1.NewSub(M)


And the prototype of the sub is
Friend Sub NewSub(M as MasterType)
   If M.MType = eUDT1 then
      Msgbox "PostDate = " & M.UDT1.PostDateTime
   Else
      Msgbox "PostDate = " & M.UDT2.PostDateTime
   End If
End Sub


Hope this helps some
0
 

Expert Comment

by:Imbue
ID: 11862186
Passing a UDT does not require early binding. You can pass a UDT as a variant. You can pass a UDT to a form's public methods. The UDT just must be written in the type library.
0
 
LVL 3

Expert Comment

by:PocketLintPPC
ID: 11862239
I was offer an alternate example. In a sense they have to be considered early bound unless one uses the ActiveX DLL method.
I'm right with you though that the ActiveX DLL method should work here.






0
 

Author Comment

by:Rodr
ID: 11917253
Imbue and others:

Sorry I've taken so long to respond. I've been away.

Here's what I have so far in my application:
    Project1
         Form1
         Module1
    Project2
         Class1 (ActiveX DLL type)

I have my UDT ("myUDT") defined in Class1. I have my generic Sub procedure defined in Class1.

I use Module1 to declare a Public variable of type myUDT. (Apparently, I must declare the variable in Module1. Declaring a Public variable of type myUDT in any of my other modules doesn't work.)

I've softened my requirements to surrender to the ridiculous complexity of trying to solve my problem: I've replaced the fixed-length string in myUDT with a variable length string. In my generic procedure in Class1 I'm using the statement(s)
      If TypeOf myVariant is myUDT then
         ....
      Else
         (error)
      End If

This softening of requirements means I must change my "generic Class1" to include any new UDT types and test for these new UDT types specifically with the "If TypeOf" tests. So much for the concept of "generic".

I now have new problems:

   1. A Binary file that I opened in Form1 is not recognized in the Class1 module. I pass the file number to the Sub procedure as a parameter. But the statement x = LOF(BinFileNo) generates an error (Error 52 - bad file name or number). I would like to open the file in Form1 and update the file in Class1.

  2. I take it I must instantiate a new object of Class1 in my Form1 procedure(s) in order to use the one and only Sub procedure in Class1 as a "method". Can't I just Call the procedure (e.g., Call Project2.Class1.SubProcedure...) without instantiating an object?

  3. How do I specify "early binding" for my Class1 stuff? So much documentation talks about early and late binding but I haven't found a clear description about how to achieve either binding in my IDE environment.

My original problem seemed so straightfoward. I must be missing an understanding of relevant basic concepts. It seems to me the solution is ridiculously complex.
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

Suggested Solutions

Introduction In a recent article (http://www.experts-exchange.com/A_7811-A-Better-Concatenate-Function.html) for the Excel community, I showed an improved version of the Excel Concatenate() function.  While writing that article I realized that no o…
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…

757 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now