============================================
5:  Passing and Returning User-Defined Types
============================================

With  Visual Basic 3.0,  you  can  pass a  user-defined type (UDT) by 
reference  to a  function. In addition to this, Visual Basic 4.0 also 
allows functions to return a user-defined type. You still cannot pass 
a UDT  by value to a DLL function. A user-defined type is returned in 
the  same way it would be with any other function, as demonstrated in 
code example below.

It is  important to make sure that the UDT as defined in Visual Basic 
is  of  the  same  size  as  the C  structure  defined  in the DLL by 
enforcing  a strict  one-to-one alignment of corresponding members in 
both UDTs. This can be a problem because the 16-bit version of Visual 
Basic 4.0 packs the members of user-defined types on a byte boundary, 
and the  32-bit  version uses dword (4-byte) alignment; while the DLL 
uses  the  "Struct Member Alignment"  Compiler Option as specified by 
the program developer. Thus, an odd-sized string in VB may cause this 
problem.  In a  32-bit  Application, it is efficient\optimal to use a 
4-byte Struct Member Alignment, which is what VB uses. So, if you are 
compiling  the  code in this example for a 16-bit DLL, make sure that 
you  use a  single byte struct alignment compiler setting; and if you 
are  compiling  a 32-bit DLL,  make sure  that you  select the 4-byte 
struct member alignment compiler setting.

So, if  you  need  to call a  DLL  function  that takes a C struct by 
reference  from  VB, you  should  keep  in mind a  simple  rule while 
designing its counterpart UDT in VB. This rule is that if the size of 
the struct member is less than the "Struct Member Alignment" size and 
if the  next member can fit in the same alignment size then they will 
be packed together. Thus, the order in which the members are declared 
inside  a struct  is  also  important. For  instance, in the  example 
below, for the  32-bit case, the "byt" member is packed together with 
the  "bln" member because of the above rule and it is followed by a 1 
byte  padding because the next member which is a VARIANT structure is 
16 bytes, which  is  greater  than  the  4 byte struct alignment size 
being  used. However, if  the  location  of the  "vnt" member and the 
"strg" member were swapped, then there would be no padding and the 11 
character  of  "strg"  would   occupy   consequent  memory  locations 
immediately  after the  "byt"  member. This is  because the size of a 
char is 1 byte which is less than the alignment size of 4.

Another  thing  to  keep  in  mind is that an integer in VB is always
2 bytes  (on both Win16 and win32), whereas an  "int" in C is 4 bytes
on  Win32, but  2 bytes  on  Win16. So, on Win32, this  might  pose a
problem  when  using  an  array  of integers in VB. The corresponding
array of "int"s in C will take up 2 extra bytes  and  a  huge  amount
of  padding  may be  required  in the  VB UDT. Hence a short is used,
which is 2 bytes (on both win16 and win32).

Thus, if the  C type is int then the VB type will have to change from
Integer  to  Long  when going from 16 to 32 bits. Otherwise, if short
and long are used in C, then the corresponding Integer and Long types
can be used in VB, regardless of the platform.

If you can help it, it is  usually  preferable to place all odd-sized 
fields  at the  end of  the  user-defined  type. This way  even-sized 
fields  will  be on  at least  a WORD (2-byte) boundary, and are thus 
more efficiently accessed.


16\32-Bit Example
-----------------

The  function  in  this  example,  shows  how to  pass  and  return a 
user-defined  type  (UDT) containing all possible data types that can 
be members, from  a DLL function called from Visual Basic. it takes a 
UDT  by reference, and returns a copy of the UDT to Visual Basic. The 
DLL function  modifies  the  members  of  the  passed  UDT, which  is 
reflected back in VB.

NOTE:  In the  32-bit  version, the Size of the UDT passed to the DLL 
will  be  76 bytes  (after all padding), while in the 16-bit version, 
the  size  will  be  only  72 bytes (which is the actual size without 
padding in both cases). However, in the  32-bit case, the size of the 
UDT  actually stored by VB is 86 bytes, the extra 12 bytes accounting 
for the fact that the fixed length string member, "strg" is stored as 
a Unicode string, and so the extra 11 bytes for the string and 1 byte 
for "Unicode padding".


#include <windows.h>
#include <ole2.h>
#define MAXSIZE 11

#ifdef _WIN32 
  #define CCONV _stdcall
  #define NOMANGLE
#else
  #define CCONV FAR PASCAL _export
  #define NOMANGLE EXTERN_C
  #include <stdlib.h>
  #include <compobj.h>    
  #include <dispatch.h> 
  #include <variant.h>
  #include <olenls.h>  
#endif

typedef struct
{
	short intgr;		//integer
	long lng;		//long
	float sng;		//single
	double dbl;		//double
	double cur;		//currency
	double dtm;		//date
	short bln;		//boolean
	BYTE byt;		//byte
	VARIANT vnt;		//variant
	BSTR vstrg;		//variable length string
	char strg[MAXSIZE];	//fixed length string
	short array[1][1][2];	//array of integers (2 Bytes in VB)
} UDT;

NOMANGLE UDT CCONV CopyUDT(UDT *pUdt)
{
    UDT udtRet;
    int i, cbLen;
    LPSTR strSrc;

    // Copy Passed-in UDT into the UDT that has to be returned
    udtRet.intgr = pUdt->intgr;
    udtRet.cur = pUdt->cur;
    udtRet.lng = pUdt->lng;
    udtRet.sng = pUdt->sng;
    udtRet.dbl = pUdt->dbl;
    udtRet.dtm = pUdt->dtm;
    udtRet.bln = pUdt->bln;
    udtRet.byt = pUdt->byt;
    udtRet.array[0][0][0] = pUdt->array[0][0][0];
    udtRet.vstrg = SysAllocString(pUdt->vstrg);

    // must initialize all Variants
    VariantInit(&udtRet.vnt);        
    VariantCopy(&udtRet.vnt, &pUdt->vnt);

    strncpy(udtRet.strg, pUdt->strg, MAXSIZE-1);
    
    // Modify members of passed-in UDT
	
    #ifdef _WIN32 
	cbLen = SysStringByteLen(pUdt->vstrg);
    #else
	cbLen = SysStringLen(pUdt->vstrg);
    #endif
	
    strSrc = (LPSTR)pUdt->vstrg;

    for(i=0; i<cbLen; i++)
        *strSrc++ = toupper(*strSrc);
    
    pUdt->array[0][0][0]++;
    VariantChangeType(&pUdt->vnt, &pUdt->vnt, NULL, VT_BSTR);

    pUdt->intgr++;
    pUdt->lng++;
    pUdt->sng += (float)1.99;
    pUdt->dbl += 1.99;
    pUdt->dtm++;
    pUdt->bln = ~pUdt->bln;
    pUdt->byt = toupper(pUdt->byt);
    strncpy(pUdt->strg, "Bob", MAXSIZE-1);

    return udtRet;
}


The following Visual Basic code calls the above two UDT functions:

NOTE:  For the  32-bit  version, in the  Visual Basic  UDT, the first 
member  is an  integer  which  is  2 bytes in size. The corresponding 
member  in the C struct is a short, whose size is also 2 bytes. Hence 
there  is a  padding  of 2 bytes  following it, as the next member is 
a long  which is 4 bytes and cannot fit in the same "alignment slot". 
The  "bln" and "byt" member are packed together and there is a 1 byte 
padding after it. Finally, the "strg" member in the VB UDT is a fixed 
length  string  of  size  22 bytes  as stored in VB and 11 bytes when 
passed  to the  DLL. The  next member  is an  array of integers. Each 
member  of this  array  which  is of  size 2 bytes is actually stored 
inside the UDT. So, there is no padding after the fixed length string 
as stored  in VB  as it can be packed with the last Unicode character 
of the  fixed length string according to the rule above; but there is 
a padding  of  1 byte  in the  passed UDT (because only 1 byte can be 
accommodated  in the  current "alignment slot" and the next member is 
an array element of size 2 bytes).


Private Const MAXSIZE = 11

Private Type UDT
    intgr As Integer
    lng As Long
    sng As Single
    dbl As Double
    cur As Currency
    dtm As Date
    bln As Boolean
    byt As Byte
    vnt As Variant
    vstrg As String
    strg As String * MAXSIZE
    array(0, 0, 1) As Integer
End Type

#If Win32 Then
    Private Declare Function CopyUDT Lib "vb4dll32.dll"(Src As UDT) _ 
	As UDT
#Else
    Private Declare Function CopyUDT Lib "vb4dll16.dll"(Src As UDT) _ 
	As UDT
#End If

Private Const NotEnough As Currency = 100.0067


Private Sub UDTtest()

    Dim Src As UDT, Cpy As UDT

    Src.strg = "Robert" + Chr$(0)
    Src.intgr = 25
    Src.lng = 77777
    Src.sng = 77777.0178
    Src.dbl = 77777.0178
    Src.cur = NotEnough
    Src.dtm = CDate(#11/16/68#)
    Src.bln = True
    Src.byt = Asc("m")
    Src.vnt = 3.14106783
    Src.array(0, 0, 0) = 6
    Src.vstrg = "hello world!"

    Cpy = CopyUDT(Src)

    Dim Msg As String

    Msg = "Integer: " & Cpy.intgr & vbCrLf
    Msg = Msg & "Currency: $" & Cpy.cur & vbCrLf
    Msg = Msg & "Long: " & Cpy.lng & vbCrLf
    Msg = Msg & "Date: " & Cpy.dtm & vbCrLf
    Msg = Msg & "Boolean: " & Cpy.bln & vbCrLf
    Msg = Msg & "Single: " & Cpy.sng & vbCrLf
    Msg = Msg & "Double: " & Cpy.dbl & vbCrLf
    Msg = Msg & "VarType: " & VarType(Cpy.vnt) & vbCrLf
    Msg = Msg & "VarString: " & Cpy.vstrg & vbCrLf
    Msg = Msg & "Array: " & Cpy.array(0, 0, 0) & vbCrLf
    Msg = Msg & "Name(<Byte>.<fixedstring>): " & Chr$(Cpy.byt)
    Msg = Msg & ". " & Cpy.strg & vbCrLf
    MsgBox Msg, vbInformation, "UDT Returned From DLL"

    Msg = "Integer: " & Src.intgr & vbCrLf
    Msg = Msg & "Currency: $" & Src.cur & vbCrLf
    Msg = Msg & "Long: " & Src.lng & vbCrLf
    Msg = Msg & "Date: " & Src.dtm & vbCrLf
    Msg = Msg & "Boolean: " & Src.bln & vbCrLf
    Msg = Msg & "Single: " & Src.sng & vbCrLf
    Msg = Msg & "Double: " & Src.dbl & vbCrLf
    Msg = Msg & "VarType: " & VarType(Src.vnt) & vbCrLf
    Msg = Msg & "VarString: " & Src.vstrg & vbCrLf
    Msg = Msg & "Array: " & Src.array(0, 0, 0) & vbCrLf
    Msg = Msg & "Name(<Byte>.<fixedstring>): " & Chr$(Src.byt)
    Msg = Msg & ". " & Src.strg & vbCrLf
    MsgBox Msg, vbInformation, "Modified UDT Passed-In To DLL"

End Sub
