
                          VB4DLL.TXT

             Notes for Developing DLLs for use with
                 Microsoft (R) Visual Basic (R)

                         Version 4.00
             (C) Copyright Microsoft Corporation, 1995

========
Contents
========

1	Calling Conventions
2 	16/32-bit Issues
3	Passing and Returning Common Built-in Types
4	Passing and Returning Strings
5	Passing and Returning User-Defined Types
6	Passing and Returning Objects
7	Passing and Returning Arrays
8	Manipulating Variants
9	Passing Unicode Strings
10	Creating a Type Library
11	Compiling a 32-bit Unicode DLL
12	Creating a TypeLib For All The Functions In This Document
13	Additional Resources


=======================
1:  Calling Conventions
=======================

The 16-bit version of Visual Basic uses the Pascal calling convention
when  calling  DLLs. The  32-bit  version  of  Visual Basic  uses the 
stdcall calling convention. In all versions  of  Visual Basic 4.0, if 
you  pass an argument ByVal by either specifying ByVal in the Declare 
statement or when calling the  procedure, the  actual value is passed 
on the stack.(the exception to the rule being the passing of strings, 
that are  handled differently  as detailed in section 4). However, if 
you  don't  specify  ByVal,  a  pointer to the value is passed on the
stack.

DLLs  actually get loaded into memory only when a function in the DLL 
is called for the first time.


====================
2:  16/32-bit Issues
====================

Perhaps  one  of the  biggest  concerns you will run into between the 
16-bit  and  32-bit versions of Visual Basic 4.0 are the ANSI/UNICODE 
issues. When  writing  DLLs,  you  can  consider an  ANSI string as a 
one-byte-per-character string, and a UNICODE string as a two-byte-per 
-character string.

The 16-bit  version of Visual Basic uses either ANSI or DBCS strings. 
The 32-bit  version of  Visual Basic uses UNICODE Strings. Because of 
this, there are some issues you need to be aware of when writing DLLs 
for use with Visual Basic 4.0.


1) Byte data type.

If  you  need a  1-byte  variable, you  can use a 'String * 1' in the 
16-bit  version of  Visual Basic. However,  in  the 32-bit version of 
Visual Basic,  a  'String * 1' actually occupies two bytes in memory.  
Because  of  this, the Byte data type  was introduced. This data type 
occupies  1-byte,  regardless of whether  it is used on the 16-bit or 
32-bit version of Visual Basic.

For  example, you  will  need  to  use  this data type if you need to
create a user-defined type in  Visual Basic (16-bit) that  matches  a
user-defined type in C:

typedef struct
{
    char a;
    short b;
    long c;
} UDT;

This  user-defined  type is a  7-byte structure, if structure members
are  aligned  on  single byte boundaries. To create a similar type in
Visual Basic (16-bit) that uses 1-byte structure alignment, you would
define a type like the following:

Type UDT
    a As Byte
    b As Integer
    c As Long
End Type


2)  In Win32, DLL procedure names are case-sensitive;  In Win16, they
are  not.  So,  for  e.g.,  this  means   that  GetSystemMetrics  and
GETSYSTEMMETICS  are  distinct function names. However, in this case,
only  the  former  is  correct as it exists in User32.dll. Now if the 
normal Declare Statement for this function:

Declare Function GetSystemMetrics Lib "User32" (ByVal n As Integer) _
	As Integer

also exists somewhere else in the project, as say:
(Say, the  CAPSLOCK  button  accidentally  goes  ON  when  typing the
function name)

Declare Function GETSYSTEMMETRICS Lib "User32" (ByVal n As Integer) _
	As Integer

then, this will change the previous definition accordingly as well.

This  makes  no  difference  on  Win16 as function names are not case
sensitive,  but  on  Win32,  a  run-time  error  will  occur  because
GETSYSTEMMETRICS  does not  exist  in the DLL. To avoid this problem,
it is recommended to use the Alias Clause as follows:

Declare Function GetSystemMetrics Lib "User32" Alias _
	GetSystemMetrics (ByVal n As Integer) As Integer

By  doing  this, you make sure that the name used in the alias is not
affected by a conversion (if any); so regardless of what case is used
for  the  name in other parts of the code, the declaration will still
refer to the correct procedure name in the DLL.


3) The  32-bit  version of Visual Basic 4.0 maintains UNICODE strings 
internally. But whenever you pass a  string  to a  DLL,  Visual Basic 
will convert it to  ANSI. If  you do not want Visual Basic to convert 
your UNICODE string to ANSI, you should first place the string into a 
byte array, and  then  pass the  address of the  first element of the 
byte array. Note,  however,  that  this is only true if you are using 
Declare  statements  for  your  DLL. If  you  create  a type library, 
Visual Basic will use whatever type is indicated by the type library.

Normally  when  compiling  32-bit  DLLs  using a Microsoft Visual C++ 
compiler  the  _declspec  keyword is used with the dllexport modifier 
attribute to enable you to export functions, data, and objects from a 
DLL. This  attribute  explicitly  defines the DLL's  interface to its 
client, which  can be  an  executable  file or another DLL. Declaring 
functions  as dllexport  eliminates  the need for a module-definition 
(.DEF)  file, at least  with respect to the specification of exported 
functions. dllexport  replaces the  _export keyword used earlier with 
16-bit DLLs. However, when writing 32-bit DLLs that need to be called 
from the  32-bit version of VB4, you need to export your function via 
the DEF file, by  specifying  all the  function names with an EXPORTS 
statement. This is  because, the  use of  dllexport  mangles function 
names,  which is  the very  nature of a C++ compiler, but VB does not 
understand  mangled  names. Even if you use a file with an  extension
of .C for your source code (so that the standard C compiler is used),
the same thing applies, i.e the use of dllexport will mangle function
names. In this case also you will have to using a .DEF file.

Another thing to keep in mind is that VB4 (32-bit) expects the called
function to clean up the stack before it returns. Hence  you must use
the _stdcall calling convention.

When compiling 16-bit DLLs, it not necessary to specify  the names of 
the  functions  in an  EXPORTS  statement in the DEF file. The 16-bit 
version  of VB4  uses the Pascal calling convention and functions are 
exported by using the _export keyword.  If a C++ compiler is used for 
compiling  the 16-bit DLL, it is also necessary to specify extern "C" 
in front  of the function definition, to explicitly tell the compiler 
not to mangle function names. If  a  C  compiler is used, this is not
required. The  OLE  header  files define EXTERN_C to be extern "C" if
a C++ compiler. This is what is used in all the examples.

The following is an example of a typical DEF file:

; The DEF File
LIBRARY vb4dll32

CODE	PRELOAD MOVEABLE DISCARDABLE
DATA	PRELOAD MOVEABLE

EXPORTS
	UpperCaseByRef	@1
	UpperCaseByVal	@2

All  the examples in this document have been written to be completely 
portable  across  16\32 bit platforms. This has been made possible by 
the use of the predefined constants  _WIN32  (in Microsoft VC++)  and
Win32  (in VB4)  and  pre-processor  directives.  So,  this  has  the 
advantage  of  having  a  common  code  base. All  you  need to do is 
re-compile  the  same code under the  16\32 bit versions of VB4\VC++. 
Furthermore,  the  Declare statements in all the examples assume that 
the  names  of  the  16-bit  and  32-bit  versions  of  the  DLLs are 
vb4dll16.dll and vb4dll32.dll respectively, and that they are located 
in the system directory. If you want to place the DLLs  in some other
directory, then you will have to specify the complete path name to it
in the Lib clause of the Declare statements. 


===============================================
3:  Passing and Returning Common Built-in Types
===============================================

The  functions  in this  example demonstrate how to pass variables of
common  VB  built-in  types like Byte, Integer, Long, Boolean, Single
and  Double, both  by value and by reference to a DLL, as well as how
to return them. Each function, takes  a variable of the type by value
as its  first  parameter, and  another  variable  of the same type by
reference, as its second parameter. It assigns the byVal parameter to
the  byRef  parameter  (which  is  reflected  back  in VB), and  then
modifies the byVal parameter and returns it.


#include <windows.h>
#include <ole2.h>

#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

NOMANGLE BYTE CCONV PassByte (BYTE byt, LPBYTE pbyt)
{
	*pbyt = byt;
	return byt + 1;
}

NOMANGLE short CCONV PassInteger (short intgr, short far *pintgr)
{
	*pintgr = intgr;
	return intgr + 1;
}

NOMANGLE short CCONV PassBoolean (short bln, short far *pbln)
{
	*pbln = ~bln;
	return bln;
}

NOMANGLE LONG CCONV PassLong (LONG lng, LPLONG plng)
{
	*plng = lng;
	return lng + 1;
}

NOMANGLE float CCONV PassSingle (float sng, float far *psng)
{
	*psng = sng;
	return sng + (float)1.99;
}

NOMANGLE double CCONV PassDouble (double dbl, double far *pdbl)
{
	*pdbl = dbl;
	return dbl + 1.99;
}


The following Visual Basic code calls the above functions:


#If Win32 Then

    Private Declare Function PassByte Lib "vb4dll32.dll" (ByVal byt _ 
	As Byte, pbyt As Byte) As Byte
    Private Declare Function PassInteger Lib "vb4dll32.dll"  (ByVal _ 
	intgr As Integer, pintgr As Integer) As Integer
    Private Declare Function PassBoolean Lib "vb4dll32.dll"  (ByVal _ 
	bln As Boolean, pbln As Boolean) As Boolean
    Private Declare Function PassLong Lib "vb4dll32.dll" (ByVal lng _ 
	As Long, plng As Long) As Long
    Private Declare Function PassSingle Lib "vb4dll32.dll" (ByVal _
	sng As Single, psng As Single) As Single
    Private Declare Function PassDouble Lib "vb4dll32.dll" (ByVal _
	dbl As Double, pdbl As Double) As Double

#Else

    Private Declare Function PassByte Lib "vb4dll16.dll" (ByVal byt _ 
	As Byte, pbyt As Byte) As Byte
    Private Declare Function PassInteger Lib "vb4dll16.dll"  (ByVal _ 
	intgr As Integer, pintgr As Integer) As Integer
    Private Declare Function PassBoolean Lib "vb4dll16.dll"  (ByVal _
	bln As Boolean, pbln As Boolean) As Boolean
    Private Declare Function PassLong Lib "vb4dll16.dll" (ByVal lng _ 
	As Long, plng As Long) As Long
    Private Declare Function PassSingle Lib "vb4dll16.dll" (ByVal _
	sng As Single, psng As Single) As Single
    Private Declare Function PassDouble Lib "vb4dll16.dll" (ByVal _
	dbl As Double, pdbl As Double) As Double

#End If


Private Sub BuiltIntest()

    Dim i As Integer, b As Boolean, c As Byte
    Dim l As Long, s As Single, d As Double
    
    Dim ir As Integer, br As Boolean, cr As Byte
    Dim lr As Long, sr As Single, dr As Double
    
    i = 7
    b = True
    c = Asc("R")
    l = 77
    s = 0.7
    d = 7.77
    
    i = PassInteger(i, ir)
    Print i, ir
    b = PassBoolean(b, br)
    Print b, br
    c = PassByte(c, cr)
    Print Chr$(c), Chr$(cr)
    l = PassLong(l, lr)
    Print l, lr
    s = PassSingle(s, sr)
    Print s, sr
    d = PassDouble(d, dr)
    Print d, dr
    
End Sub


=================================
4:  Passing and Returning Strings
=================================

Visual  Basic  maintains variable-length strings internally as BSTRs. 
BSTRs  are  defined  in the  OLE header  files  as  OLECHAR FAR *. An
OLECHAR is a UNICODE character in 32-bit OLE and an ANSI character in
16-bit OLE. A BSTR  can  contain NULL values because a length is also
maintained with the BSTR. BSTRs  are also NULL terminated so they can
be treated as an LPSTR. Currently this length  is stored  immediately
prior  to the  string. This may change in the future, however, so you
should use the OLE APIs to access the string length.

You  can pass a string from Visual Basic to a DLL in one of two ways. 
You can pass it "by value" (ByVal) or "by reference". When you pass a 
string  ByVal, Visual Basic  passes a pointer to the beginning of the 
string  data  (i.e. it passes a BSTR). When  a  string  is  passed by
reference, Visual Basic passes a  pointer to  a pointer to the string
data (i.e. it passes a BSTR *).

The  following  table  lists  what  Visual Basic  will  pass to a DLL 
function when passing a string.

Version	   By Value	By Reference
------------------------------------
3.0	   LPSTR	HLSTR
4.0	   BSTR         BSTR *

In  Visual Basic 3.0, you  could use the Visual Basic API routines to 
access  and  modify  an HLSTR. In Visual Basic 4.0 you should use the 
OLE APIs to access a BSTR. The following table lists the Visual Basic 
3.0 string-handling APIs, and the OLE equivalents.


Visual Basic API	OLE API
--------------------------------------------------------
VBCreateHlstr		SysAllocString/SysAllocStringLen
VBCreateTempHlstr	SysAllocString/SysAllocStringLen
VBDerefHlstr*		N/A
VBDerefHlstrLen*	N/A
VBDerefZeroTermHlstr	N/A
VBDestroyHlstr		SysFreeString
VBGetHlstrLen		SysStringLen
VBResizeHlstr		SysReAllocStringLen
VBSetHlstr		SysReAllocString

NOTE:  The  BSTR  is a  pointer  to the  string, so you don't need to 
dereference it.


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

The  first  function  in this  example takes a Visual Basic string by 
reference, and  returns  an  uppercase copy of the string. The second 
function  takes a  Visual Basic  string  by value and also returns an 
uppercase  copy  of the string. This is similar to the UCase function 
in  Visual Basic. In both cases, the DLL function modifies the passed 
string, which is  reflected back in VB. This happens even when the VB 
string  is passed  "ByVal" because what is passed to the DLL function 
is a BSTR which is a char far *, and thus, it is possible to directly 
access the memory buffer pointed to by the BSTR.


#include <windows.h>
#include <ole2.h>

#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

NOMANGLE BSTR CCONV UpperCaseByRef(BSTR *pbstrOriginal)
{
    BSTR bstrUpperCase;
    int i;
    int cbOriginalLen;
    LPSTR strSrcByRef, strDst;

    #if !defined(_WIN32)
	cbOriginalLen = SysStringLen(*pbstrOriginal);
    #else
    	cbOriginalLen = SysStringByteLen(*pbstrOriginal);
    #endif

    bstrUpperCase = SysAllocStringLen(NULL, cbOriginalLen);

    strSrcByRef = (LPSTR)*pbstrOriginal;
    strDst = (LPSTR)bstrUpperCase;

    for(i=0; i<=cbOriginalLen; i++)
        *strDst++ = toupper(*strSrcByRef++);

    SysReAllocString (pbstrOriginal, (BSTR)"Good Bye");

    return bstrUpperCase;
}

NOMANGLE BSTR CCONV UpperCaseByVal(BSTR bstrOriginal)
{
    BSTR bstrUpperCase;
    int i;
    int cbOriginalLen;
    LPSTR strSrcByVal, strDst;

    #if !defined(_WIN32)
	cbOriginalLen = SysStringLen(bstrOriginal);
    #else
    	cbOriginalLen = SysStringByteLen(bstrOriginal);
    #endif

    bstrUpperCase = SysAllocStringLen(NULL, cbOriginalLen);

    strSrcByVal = (LPSTR)bstrOriginal;
    strDst = (LPSTR)bstrUpperCase;

    for(i=0; i<=cbOriginalLen; i++)
        *strDst++ = toupper(*strSrcByVal++);

    SysReAllocString (&bstrOriginal, (BSTR)"Good Bye");

    return bstrUpperCase;
}


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


#If Win32 Then

    Private Declare Function UpperCaseByRef Lib "vb4dll32.dll" (Str _ 
	As String) As String
    Private Declare Function UpperCaseByVal Lib "vb4dll32.dll" _
	(ByVal Str As String) As String

#Else

    Private Declare Function UpperCaseByRef Lib "vb4dll16.dll" (Str _ 
	As String) As String
    Private Declare Function UpperCaseByVal Lib "vb4dll16.dll" _
	(ByVal Str As String) As String

#End If


Private Sub StringTest ()

	Dim Str As String, NewStr As String

	Str = "Hello World!"
	MsgBox "In VB, Before: " & Str
	NewStr = UpperCaseByRef(Str)
	MsgBox "In VB, After: " & Str
	MsgBox "In VB, CapsStr: " & NewStr
    
	Str = "Hello World!"
	MsgBox "In VB, Before: " & Str
	NewStr = UpperCaseByVal(Str)
	MsgBox "In VB, After: " & Str
	MsgBox "In VB, CapsStr: " & NewStr

End Sub


============================================
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


=================================
6:  Passing and Returning Objects
=================================

You can pass and return objects from a function in Visual Basic. What
Visual Basic  is  actually  passing is a pointer to an OLE interface. 
All  OLE  interfaces  support  QueryInterface, so  you  can  use this 
interface  to get  to  other  interfaces you might need. To return an 
object to Visual Basic, you simply return an interface pointer.


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

For  example, the  following ClearObject function will try to execute 
the  Clear method  for the  object, if it  has one. The function will 
then simply return a pointer to the interface passed in.

NOTE: For the 32-bit version, you need to pass the name of the method 
to  invoke  (in this case "clear")  as a  Unicode  string because all 
strings in  32-bit OLE must be Unicode. Strings in 16-bit OLE must be 
ANSI, and hence the requirement of the Pre-compiler flag, _WIN32.


#include <windows.h>
#include <ole2.h>

#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

NOMANGLE LPUNKNOWN CCONV ClearObject(LPUNKNOWN *lpUnk)
{
    LPDISPATCH pdisp;
    
    if((*lpUnk)->QueryInterface(IID_IDispatch, (LPVOID *)&pdisp) ==
  	NOERROR)
    {
        DISPID dispid;
        DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

        #ifdef _WIN32
	    BSTR name = L"clear";
	#else
	    BSTR name = "clear";
	#endif

        if(pdisp->GetIDsOfNames(IID_NULL, &name, 1, NULL, &dispid) ==
	   S_OK)
        {
            pdisp->Invoke(dispid, IID_NULL, NULL, DISPATCH_METHOD,
		          &dispparamsNoArgs, NULL, NULL, NULL);
        }

        pdisp->Release();
    }

    return *lpUnk;
}


The following Visual Basic code calls the ClearObject function:


#If Win32 Then

    Private Declare Function ClearObject Lib "vb4dll32.dll" (X As _
	Object) As Object

#Else

    Private Declare Function ClearObject Lib "vb4dll16.dll" (X As _
	Object) As Object

#End If


Private Sub Form_Load()

    List1.AddItem "item #1"
    List1.AddItem "item #2"
    List1.AddItem "item #3"
    List1.AddItem "item #4"
    List1.AddItem "item #5"

End Sub

Private Sub ObjectTest()

    Dim X As Object
    
    ' Assume there is a ListBox with some displayed items on the form
    Set X = ClearObject(List1)
    X.AddItem "This should be added to the ListBox"

End Sub


================================
7:  Passing and Returning Arrays
================================

When  an  array  is  passed  to a DLL function, Visual Basic actually 
passes  a  pointer  to  a  pointer  to a  SAFEARRAY  structure, or an 
LPSAFEARRAY FAR *. SafeArrays  are  structures  that  are used in OLE 
2.0. Visual Basic 4.0 uses SafeArrays internally to store arrays. 

SafeArrays  contain  information  about the  number of dimensions and 
their bounds. The  data  referred by an array descriptor is stored in
column-major  order  (i.e.  the  leftmost  dimension  changes first), 
which  is the  same  scheme  used by Visual Basic, but different than 
that  used   by  PASCAL  or  C. The  subscripts  for  SafeArrays  are 
zero-based.

The OLE 2.0  APIs can be used to access and manipulate the array. The 
following table lists the Visual Basic APIs used to reference arrays, 
and the OLE 2.0 equivalents.

Visual Basic API	OLE API
-------------------------------------------------------------
VBArrayBounds		SafeArrayGetLBound/SafeArrayGetUBound
VBArrayElement		SafeArrayGetElement
VBArrayElemSize		SafeArrayGetElemsize
VBArrayFirstElem	N/A
VBArrayIndexCount	SafeArrayGetDim

The OLE API function SafeArrayGetDim returns the number of dimensions 
in   the   array,   and   the   functions    SafeArrayGetLBound   and 
SafeArrayGetUBound  return  the  lower  and  upper bounds for a given 
array  dimension. All of  these functions require a parameter of type 
LPSAFEARRAY, in order to describe the target array.

The first function in this example shows how to get the dimension and 
bound information of a Visual Basic array (of strings) that is passed 
to a  DLL. It  also  creates  a new  array, copies  an element of the 
passed  in array  into the  corresponding  index  location in the new 
array. It  then  modifies  the  string  element  at  this  same index 
location  in the original passed in array (which is reflected back in 
VB). Finally  it  stores  the new  array in a Variant and returns the 
same to VB. 

The  second  function  is a  shorter version  of the first one. It is 
slightly different, in that all the elements from the passed-in array 
are  copied into the new array automatically. In both cases, however, 
for  string  arrays  in the  32-bit  version of VB4, you should first 
convert the string to Unicode. This is required because, VB4 (32-bit) 
uses Unicode to store strings internally, however it converts them to 
ANSI  on  the  way  in to a  DLL. It will normally convert it back to 
Unicode  on the way  out of  the DLL; but  since  the string is being 
copied into an array that has been *NEWLY CREATED INSIDE THE DLL*, VB
will not know enough to do the conversion. This is not required  when
compiling the DLL for 16-bits because, the 16-bit version of VB4 uses
ANSI strings internally.

This  example  demonstrates  passing and returning arrays of strings. 
But  it can  easily  be  modified to work for arrays of any permitted 
datatype. The  only  modifications  that have to be made are changing 
the Declare statements and the VT_XXXX flags to match the appropriate 
type.  And  of  course,  you  don't  have   to  worry  about  Unicode 
conversions when dealing with non-string data-types.


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

#include <windows.h>
#include <ole2.h>
#include <stdio.h>

#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

// hold the SAFEARRAY pointer to be returned in a Variant
LPSAFEARRAY lpsa1;
LPSAFEARRAY lpsa2;

NOMANGLE VARIANT CCONV ProcessArray(LPSAFEARRAY FAR *ppsa)
{
	VARIANT vnt;
	unsigned int i, cdims;
	char buff[40];
	long rgIndices[] = {0,1,2};
	BSTR element = NULL;

	cdims = SafeArrayGetDim(*ppsa);

	// Must initialize variant first
	VariantInit(&vnt);        

	// Create an array descriptor
	if (SafeArrayAllocDescriptor (cdims, &lpsa1) != S_OK)
	{
	    MessageBox (NULL, "Can't create array descriptor. \n
		Will return an empty variant", "Error!", MB_OK);
	    lpsa1 = NULL;
	}

	// Specify the size and type of array elements
	if (lpsa1)
	{
	    lpsa1->cbElements = sizeof(BSTR);
	    lpsa1->fFeatures = FADF_BSTR;
	}

	// Get the bound info for passed in array, display it and
	// store the same in the array to be returned in a variant
	for (i=1; i <= cdims; i++)
	{
	    long Lbound, Ubound;

	    SafeArrayGetLBound (*ppsa, i, &Lbound);
	    SafeArrayGetUBound (*ppsa, i, &Ubound);
		
	    if (lpsa1)
	    {
		lpsa1->rgsabound[cdims-i].cElements= Ubound-Lbound+1;
		lpsa1->rgsabound[cdims-i].lLbound = Lbound;
	    }

  	    sprintf (buff, "Index %d: Lbound = %li, Ubound = %li\n",
		     i, Lbound, Ubound);
	    MessageBox (NULL, buff, "SafeArrayInfo from DLL", MB_OK);
	}
   	
	if (!lpsa1)
	    return vnt;

	// Allocate space for the actual array elements
	if (SafeArrayAllocData (lpsa1) != S_OK)
	{
	    MessageBox (NULL, "can't create array elements","Error!", 
			MB_OK);
	    return vnt;
	}

	// Get the value of the string element at (0,1,2). This will
	// be an ANSI string.
	SafeArrayGetElement (*ppsa, rgIndices, &element);

	#ifdef _WIN32
	    // Convert this to Unicode, as VB4 (32-bit) will not do
	    // so for you, as the string is inside an array *NEWLY
	    // CREATED* inside the DLL!
	    unsigned int length = SysStringByteLen(element);
	    BSTR wcElement = NULL;
		
	    wcElement = SysAllocStringLen(NULL, length*2);
	    MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)element 
				, -1, (LPWSTR)wcElement, length*2);

	    // Put this Unicode string into the corresponding
	    // location in the array to be returned in a variant
	    lpsa1->fFeatures ^= FADF_BSTR;
	    SafeArrayPutElement (lpsa1, rgIndices, &wcElement); 
	    lpsa1->fFeatures |= FADF_BSTR;
	#else
	    // Put the (ANSI) string back into the corresponding
	    // location in the array to be returned
	    SafeArrayPutElement (lpsa1, rgIndices, element); 
	#endif

	SysFreeString (element);
	element = SysAllocString((BSTR)"Good Bye");

	// Modify the same element (0,1,2) of the passed-in array
	SafeArrayPutElement (*ppsa, rgIndices, element);
	SysFreeString (element);

	// store the array to be returned in a variant
	vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR;
	vnt.pparray = &lpsa1;

   	return vnt;
}

NOMANGLE VARIANT CCONV CopyArray(LPSAFEARRAY FAR *ppsa)
{
	VARIANT vnt;
	BSTR element = NULL;
	long rgIndices[] = {0,1,2};

	// Must initialize variant first
	VariantInit(&vnt);        

	// copy the passed-in array to the array to be returned in
	// variant
	SafeArrayCopy (*ppsa, &lpsa2);
	
	// Get the value of the string element at (0,1,2). This will
	// be an ANSI string.
	SafeArrayGetElement (lpsa2, rgIndices, &element);

	#ifdef _WIN32
	    // Convert this to Unicode, as VB4 (32-bit) will not do
	    // so for you, as the string is inside an array *NEWLY
	    // CREATED* inside the DLL!
	    unsigned int length = SysStringByteLen(element);
	    BSTR wcElement = NULL;
		
	    wcElement = SysAllocStringLen(NULL, length*2);
    	    MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)element
				, -1, (LPWSTR)wcElement, length*2);

	    // Put this Unicode string back into the corresponding
	    // location in the array to be returned
	    SafeArrayPutElement (lpsa2, rgIndices, wcElement); 
	    SysFreeString (wcElement);
	#else
	    // Put the (ANSI) string back into the corresponding
	    // location in the array to be returned
	    SafeArrayPutElement (lpsa2, rgIndices, element); 
	#endif

	SysFreeString (element);
	element = SysAllocString((BSTR)"Hello Again!");

	// Modify the same element (0,1,2) of the passed-in array
	SafeArrayPutElement (*ppsa, rgIndices, element);
	SysFreeString (element);

	// store the array to be returned in a variant
	vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR;
	vnt.pparray = &lpsa2;

   	return vnt;
}


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


#If Win32 Then

    Private Declare Function ProcessArray Lib "vb4dll32.dll"(a() As _ 
	String) As Variant
    Private Declare Function CopyArray Lib "vb4dll32.dll" (a() As _
	String) As Variant

#Else

    Private Declare Function ProcessArray Lib "vb4dll16.dll"(a() As _ 
	String) As Variant
    Private Declare Function CopyArray Lib "vb4dll16.dll" (a() As _
	String) As Variant

#End If


Private Sub ArrayTest()

    Dim a(4, 5, 6) As String
    Dim v1 As Variant
    Dim v2 As Variant

    a(0, 1, 2) = "Hello!"

    Print VarType(v1)
    v1 = ProcessArray(a())
    Print VarType(v1)

    MsgBox v1(0, 1, 2), vbInformation, "Element Value of Array _
	   Returned In Variant - 1"
    MsgBox a(0, 1, 2), vbInformation, "Modified Element Value of _
	   Passed-in Array - 1"

    v2 = CopyArray(a())
    Print VarType(v2)

    MsgBox v2(0, 1, 2), vbInformation, "Element Value of Array _
	   Returned In Variant - 2"
    MsgBox a(0, 1, 2), vbInformation, "Modified Element Value of _
	   Passed-in Array - 2 "

End Sub


=========================
8:  Manipulating Variants
=========================

Visual  Basic  2.0  introduced  a new  data  type known as a Variant. 
Developers  were able to use the Visual Basic APIs in a DLL to access 
and  manipulate  these  Variant  types. Visual Basic 4.0  also  has a 
Variant  data  type, but  it  uses  the  OLE 2.0  Variant  data type.  
Therefore  you must  use the  OLE 2.0  API  functions  to  access and 
manipulate Variants.

The  following  table  lists the Visual Basic 3.0 APIs used to access 
Variants, and the OLE 2.0 API equivalents.

Visual Basic API	OLE API
--------------------------------------------------------
VBCoerceVariant		VariantChangeType
VBGetVariantType	N/A*
VBGetVariantValue	N/A*
VBSetVariantValue	N/A*

* You  can  get  and  set  the  Variant's  type and value directly by 
accessing the Variant's fields.

A  Variant  is  really  just a structure. Its definition, however, is 
somewhat complicated. The Variant contains 5 fields.

Here is a visual layout of the Variant:

+-----+-----+-----+-----+-----------------------+
|  A  |  B  |  C  |  D  |           E           |
+-----+-----+-----+-----+-----------------------+

Field  A  contains the Variant type. Fields  B, C, and D are reserved 
for  future  use, and  field  E  is an 8-byte union that contains any 
intrinsic  type  from an Integer to a double-precision floating point 
number. It  can  also  contain BSTRs, interface pointers, or pointers 
to one of the intrinsic types.


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

The first function in this example demonstrates how to pass a variant 
by  reference  to a  DLL, makes a copy of it, changes the type of the 
copy to a string, and then returns the copy. the second function does 
the same thing, but passes the variant by value.



#include <windows.h>
#include <ole2.h>

#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

NOMANGLE VARIANT CCONV VariantByRef(VARIANT *pvar)
{
    VARIANT var;

    // must initialize all Variants
    VariantInit(&var);        
    VariantCopy(&var, pvar);

    VariantChangeType(&var, &var, NULL, VT_BSTR);

    return var;
}

NOMANGLE VARIANT CCONV VariantByVal(VARIANT var)
{
    VARIANT vnt;

    // must initialize all Variants
    VariantInit(&vnt);        
    VariantCopy(&vnt, &var);

    VariantChangeType(&vnt, &vnt, NULL, VT_BSTR);

    return vnt;
}


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


#If Win32 Then

    Private Declare Function VariantByRef Lib "vb4dll32.dll"(var As _ 
	Variant) As Variant
    Private Declare Function VariantByVal Lib "vb4dll32.dll" (ByVal _
	var As Variant) As Variant

#Else

    Private Declare Function VariantByRef Lib "vb4dll16.dll"(var As _ 
	Variant) As Variant
    Private Declare Function VariantByVal Lib "vb4dll16.dll" (ByVal _ 
	var As Variant) As Variant

#End If


Private Sub VariantTest()

    Dim v1 As Variant, v2 As Variant
    v1 = 3.14159
    
    v2 = VariantByRef(v1)
    MsgBox VarType(v2)
    ' v2 should now be "3.14159"
    
    v2 = Empty	' Make v2 empty again
    v2 = VariantByVal(v1)
    MsgBox VarType(v2)
    ' v2 should again be "3.14159"

End Sub


===========================
9:  Passing Unicode Strings
===========================

UNICODE  is a  16-bit  character  set  capable  of encoding all known 
characters  and  used as a world-wide character encoding standard. So 
a  Unicode  string  is a  two-byte-per character string while an ANSI 
string is a one-byte-per character string.

Win32  API  functions  that  have  string  parameters  are  generally 
implemented in one of three formats:

 - A generic version that can be compiled for either ANSI or UNICODE
 - An ANSI version
 - A UNICODE version

The generic function prototype consists of the standard function name 
implemented as a macro, that maps to the appropriate specific version 
of  the  function. The  letters   A  (for ANSI version)  and  W  (for 
wide-character or UNICODE version)  are  added  to  the  end  of  the 
function names in the specific function prototypes.

Windows 95  uses ANSI internally. Windows NT uses Unicode exclusively 
at the system level. The 32-bit version of Visual Basic 4.0 maintains 
Unicode strings internally. But whenever Visual Basic passes a string 
to a  DLL  function, it always converts it to ANSI on the way out and 
back  to  UNICODE  on  the  way  in. So, you can always call the ANSI 
version of the APIs from VB, on either platform. But the only way for 
VB to call the UNICODE version of the APIs from either platform is to 
create a type library resource for the DLL, register it in the system 
registry  and then add a reference to it from VB. This will allow you 
to call  the UNICODE version of the API functions without an explicit 
Declare Statement because Visual Basic will then use whatever type is 
indicated  by  the  type library. Note that Windows 95 currently does
not  support  the  UNICODE  version  of  the APIs. It just has "place
holder" entry points for the UNICODE (W) API functions.

Visual Basic 4.0 (32-bit) will coerce every string passed to a DLL to
ANSI on the  way  out of  Visual Basic and back to UNICODE on the way
in. In  order  to  force  Visual Basic 4.0  (32-bit)  to  always pass
Unicode strings, you  can  create a type library for the DLL and then
add a reference to the typelib from Visual Basic 4.0.

Type  libraries  are  built  using  a  language  known  as the Object 
Description  Language  (ODL). An ODL file is similar to a C file, but 
contains  additional  OLE 2.0  specific  additions. The ODL file then 
needs  to be  compiled  to a  type  library  (.TLB)  file, using  the 
MKTYPLIB utility that comes with Microsoft Visual C++.


============================
10:  Creating a Type Library
============================

With Visual Basic, you can make calls into a DLL by declaring the DLL 
function inside your Visual Basic code.  However, it is also possible 
to make  a type library resource for your DLL.  The advantage of this 
technique  is that if users register your type library, then they can 
add a  reference  to your DLL from within Visual Basic, and call your 
DLL routines without an explicit Declare statement.

The following code is a sample ODL file you can use to build a 32-bit 
type library  to  replace  the following Declare statement. Note that 
the line numbers aren't really part of the ODL file, and are included 
for reference purposes only.

Declare Function square Lib "oletest.dll" (ByVal x As Double) As _
	Double

1|  [uuid(73ED10A0-BDC5-11CD-9489-08002B3711DB)]
2|  library MyLibrary {
3|      [dllname("oletest.dll")]
4|      module MyModule {
5|          [entry("square")] double stdcall square([in] double x);
6|      };
7|  };

Line 1: Defines a universally  unique  identifier  (uuid)  that  will 
	uniquely  define this library on any system. (You need to use 
	the  GUIDGEN  utility  that  ships with the OLE SDK to create 
	this number.)

Line 2: Allows you to specify a name for your library.

Line 3: Specifies  the name of the DLL that contains the functions in 			
	question. The recommended practice,here is *not* to hard-code
	a path to the location of the dll. When a function  is called
	from this DLL, it will be loaded by searching for it  in  the
	standard directories that the LoadLibrary() API uses.

	NOTE:  While debugging, you  can  enter a hard-coded name but
	each backslash (\) should be prefixed with another backslash:

		[dllname("c:\\projects\\oletest.dll")]

Line 4: Allows  you to specify the name for the Module. (This will be 
	the name that shows up in the Object Browser in Visual Basic)

Line 5: You  will  want to add a line similar to this for each of the 
	functions you are exporting from the DLL.

	NOTE#1: You could also specify an ordinal entry point.
	(e.g.:  [entry(1)] ...).  This  gives better performance than
	named  entry points. However, ordinals should not be used for 
	Win API calls because the ordinals are different on different 
	Operating Systems/OS versions.

	NOTE#2: To compile a 16-bit typelib, use pascal calling conv:
	[entry("square")] double pascal square([in] double x);

For  complete  instructions  on  creating  an  ODL file and using the 
MKTYPLIB  utility, see  Chapter 7,  "Object Description Language," of 
the OLE 2 Programmer's Guide, Volume 2.

The  following  example  consists  of two parts. The first part is to
create a ODL file  for  KERNEL32.DLL in  Visual C++  and  compile  it
into  a  type library  (32-bit). The  second  part is to register and
reference  this  type  library  from  Visual Basic  and then call the
Unicode version of the function in the DLL. The API function used for
this purpose is GetPrivateProfileStringW (The UNICODE version).

Note  that  this  will work only on Windows NT and not on Windows 95,
because Windows 95 does not support the UNICODE version of this API.

Creating The ODL File and compiling the TypeLib
-----------------------------------------------

1. Start Visual C++, Version 2.0 or higher.

2. Choose  New  from  the  File menu, select Code\Text from the "New" 
   dialog box  and  click the OK button. A code window titled "Text1" 
   will be created.

3. Add the following code to this code window:

   [
      uuid(13C9AF40-856A-101B-B9C2-04021C007002),
      helpstring("WIDE Windows API Type Library")
   ]
   library WideWin32API
   {
      [
         helpstring("KERNEL API Calls"),
         dllname("KERNEL32")
      ]
      module KernelAPI
      {
         [
            helpstring("Gets the value of a .ini file setting."),
            entry("GetPrivateProfileStringW")
         ]
         long _stdcall GetPrivateProfileStringW
         (
            [in]  BSTR lpApplicationName,
            [in]  BSTR lpKeyName,
            [in]  BSTR lpDefault,
            [in]  BSTR lpReturnedString,
            [in]  long nSize,
            [in]  BSTR lpFileName
         );
      };
   };

4. Choose Save from the File menu and save this file as "WideApi.odl"

5. From  the  DOS  prompt  (or the File Manager)  run  the  following 
   command:

   MKTYPLIB /I C:\MSVC20\INCLUDE /win32 /tlb WIDEAPI.TLB WIDEAPI.ODL

   Make  sure that the MKTYPLIB.EXE utility that you are using is the
   32-bit  version. You will find it in the C:\MSVC20\BIN\ directory. 
   If WIDEAPI.ODL is not in the current directory, make sure that you 
   specify the complete pathname in the above command.

6. The  type  library  WIDEAPI.TLB  will  be  created  in the current 
   directory.


Referencing The TypeLib From Visual Basic
-----------------------------------------

1. Start a new project in Visual Basic. Form1 is created by default.

2. Choose  References  from  the  Tools  menu  and  register the type 
   library WIDEAPI.TLB by browsing for the file and then clicking the 
   OK button. You should see "WIDE Windows API Type Library" selected 
   in the Available References list. Click the OK button.

3. Add the following code to the Form_Click event of Form1:

   Dim i As Long
   Dim sRet As String
   Dim sSection As String
   Dim sEntry As String
   Dim sDefault As String
   Dim sFileName As String

   sSection = "Visual Basic"
   sEntry = "vbpath"
   sDefault = "hello"
   sRet = Space(30)
   sFileName = "c:\windows\vb.ini"

   i = GetPrivateProfileStringW (sSection, sEntry, sDefault, sRet, _
			         30, sFileName)

   MsgBox i
   MsgBox sRet

Press F5  to  run the  program. Click on Form1. A message box showing 
the number of characters read from the vb.ini file will be displayed. 
Subsequently, another  message box  will  display  the  value  of the 
requested key from the vb.ini file.


==================================
11: Compiling a 32-bit Unicode DLL
==================================

The above examples were written only from the stand point of creating
ANSI versions of a DLL. The following  example  demonstrates  how you
can  modify  the  above  code so that you have a common code base for
compiling  either  the  UNICODE or ANSI version of a 32-bit DLL. Note
that only  4 functions  (UpperCaseByRef, UpperCaseByVal, ProcessArray
and CopyArray) need to be changed. The rest are also included anyway,
unchanged  from  their  ANSI  version. The  code  for  calling  these
functions  from  VB is also the same as before. Only you will have to
comment out all the DECLARE statements.

// comment out the following 2 lines if *NOT* using a TYPELIB, but if
// using  DECLARE statements  for the DLL functions (i.e if compiling
// the  ANSI  version  of a 32-bit DLL), OR if compiling a 16-bit DLL
// (whether or not you using a TYPELIB or DECLARE statements).

#define UNICODE  // Windows header files will use Unicode conventions
#define _UNICODE // runtime libraries will use Unicode conventions

#include <windows.h>
#include <ole2.h>
#include <stdio.h>
#include <string.h>
#include <tchar.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>  
  #define LPTSTR LPSTR	// definitions included so that the same code 
  #define TEXT(X) X	// ... base may be used for 16-bit DLLs too.
#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;

// hold the SAFEARRAY pointer to be returned in a Variant
LPSAFEARRAY lpsa1;
LPSAFEARRAY lpsa2;

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
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;
}

// THIS FUNCTION *HAS BEEN* CHANGED FOR THE UNICODE VERSION!
NOMANGLE BSTR CCONV UpperCaseByRef(BSTR *pbstrOriginal)
{
    BSTR bstrUpperCase;
    int i;
    int cbOriginalLen;
    LPTSTR strSrcByRef, strDst;

    #if defined(UNICODE) || !defined(_WIN32)
	cbOriginalLen = SysStringLen(*pbstrOriginal);
    #else
    	cbOriginalLen = SysStringByteLen(*pbstrOriginal);
    #endif

    bstrUpperCase = SysAllocStringLen(NULL, cbOriginalLen);

    strSrcByRef = (LPTSTR)*pbstrOriginal;
    strDst = (LPTSTR)bstrUpperCase;

    for(i=0; i<=cbOriginalLen; i++)
        *strDst++ = _totupper(*strSrcByRef++);

    SysReAllocString (pbstrOriginal, (BSTR)(TEXT("Good Bye")));

    return bstrUpperCase;
}

// THIS FUNCTION *HAS BEEN* CHANGED FOR THE UNICODE VERSION!
NOMANGLE BSTR CCONV UpperCaseByVal(BSTR bstrOriginal)
{
    BSTR bstrUpperCase;
    int i;
    int cbOriginalLen;
    LPTSTR strSrcByVal, strDst;

    #if defined(UNICODE) || !defined(_WIN32)
	cbOriginalLen = SysStringLen(bstrOriginal);
    #else
    	cbOriginalLen = SysStringByteLen(bstrOriginal);
    #endif

    bstrUpperCase = SysAllocStringLen(NULL, cbOriginalLen);

    strSrcByVal = (LPTSTR)bstrOriginal;
    strDst = (LPTSTR)bstrUpperCase;

    for(i=0; i<=cbOriginalLen; i++)
        *strDst++ = _totupper(*strSrcByVal++);

    SysReAllocString (&bstrOriginal, (BSTR)(TEXT("Good Bye")));

    return bstrUpperCase;
}

// THIS FUNCTION *HAS BEEN* CHANGED FOR THE UNICODE VERSION!
NOMANGLE VARIANT CCONV ProcessArray(LPSAFEARRAY *ppsa)
{
    VARIANT vnt;
    unsigned int i, cdims;
    TCHAR buff[40];
    long rgIndices[] = {0,1,2};
    BSTR element = NULL;

    cdims = SafeArrayGetDim(*ppsa);

    // Must initialize variant first
    VariantInit(&vnt);        

    // Create an array descriptor
    if (SafeArrayAllocDescriptor (cdims, &lpsa1) != S_OK)
    {
	MessageBox (NULL, TEXT("Can't create array descriptor. \n
	    Will return an empty variant"), TEXT("Error!"), MB_OK);
	lpsa1 = NULL;
    }

    // Specify the size and type of array elements
    if (lpsa1)
    {
	lpsa1->cbElements = sizeof(BSTR);
	lpsa1->fFeatures = FADF_BSTR;
    }

    // Get the bound info for passed in array, display it and store
    // the same in the array to be returned in a variant
    for (i=1; i <= cdims; i++)
    {
	long Lbound, Ubound;

	SafeArrayGetLBound (*ppsa, i, &Lbound);
	SafeArrayGetUBound (*ppsa, i, &Ubound);
		
	if (lpsa1)
	{
	    lpsa1->rgsabound[cdims-i].cElements = Ubound-Lbound+1;
	    lpsa1->rgsabound[cdims-i].lLbound = Lbound;
	}

	_stprintf (buff, TEXT("Index %d: Lbound = %li, Ubound = %li\n"),
		   i, Lbound, Ubound);

	MessageBox (NULL, buff,TEXT("SafeArrayInfo from DLL"),MB_OK);
    }
   	
    if (!lpsa1)
	return vnt;

    // Allocate space for the actual array elements
    if (SafeArrayAllocData (lpsa1) != S_OK)
    {
	MessageBox (NULL, TEXT("can't create array elements"),
		    TEXT("Error!"), MB_OK);
	return vnt;
    }

    // Get the value of the string element at (0,1,2). this will be
    // an ANSI string.
    SafeArrayGetElement (*ppsa, rgIndices, &element);

    #if defined(_WIN32) && !defined(UNICODE)
	// Convert this to Unicode, as VB4 (32-bit) will not do so
	// for you, as the string is inside an array *NEWLY CREATED*
	// inside the DLL!
	unsigned int length = SysStringByteLen(element);
	BSTR wcElement = NULL;
		
	wcElement = SysAllocStringLen(NULL, length*2);
	MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, (LPCSTR)element, 
			     -1, (LPWSTR)wcElement, length*2);

	// Put this Unicode string into the corresponding location in 
	// the array to be returned in a variant
	lpsa1->fFeatures ^= FADF_BSTR;
	SafeArrayPutElement (lpsa1, rgIndices, &wcElement); 
	lpsa1->fFeatures |= FADF_BSTR;
    #else
	// Put the (ANSI) string back into the corresponding location 
	// in the array to be returned
	SafeArrayPutElement (lpsa1, rgIndices, element); 
    #endif

    SysFreeString (element);
    element = SysAllocString((BSTR)(TEXT("Good Bye")));

    // Modify the same element (0,1,2) of the passed-in array
    SafeArrayPutElement (*ppsa, rgIndices, element);
    SysFreeString (element);

    // store the array to be returned in a variant
    vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR;
    vnt.pparray = &lpsa1;

    return vnt;
}

// THIS FUNCTION *HAS BEEN* CHANGED FOR THE UNICODE VERSION!
NOMANGLE VARIANT CCONV CopyArray(LPSAFEARRAY *ppsa)
{
    VARIANT vnt;
    BSTR element = NULL;
    long rgIndices[] = {0,1,2};

    // Must initialize variant first
    VariantInit(&vnt);        

    // copy the passed-in array to the array to be returned in
    // variant
    SafeArrayCopy (*ppsa, &lpsa2);
	
    // Get the value of the string element at (0,1,2). this will be
    // an ANSI string.
    SafeArrayGetElement (lpsa2, rgIndices, &element);

    #if defined(_WIN32) && !defined(UNICODE)
	// Convert this to Unicode, as VB4 (32-bit) will not do so
	// for you, as the string is inside an array *NEWLY CREATED*
	// inside the DLL!
	unsigned int length = SysStringByteLen(element);
	BSTR wcElement = NULL;
		
	wcElement = SysAllocStringLen(NULL, length*2);
	MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, (LPCSTR)element, 
			     -1, (LPWSTR)wcElement, length*2);

	// Put this Unicode string back into the corresponding
	// location in the array to be returned
	SafeArrayPutElement (lpsa2, rgIndices, wcElement); 

	SysFreeString (wcElement);
    #else
	// Put the (ANSI) string back into the corresponding location 
	// in the array to be returned
	SafeArrayPutElement (lpsa2, rgIndices, element); 
    #endif

    SysFreeString (element);
    element = SysAllocString((BSTR)(TEXT("Hello Again!")));

    // Modify the same element (0,1,2) of the passed-in array
    SafeArrayPutElement (*ppsa, rgIndices, element);
    SysFreeString (element);

    // store the array to be returned in a variant
    vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR;
    vnt.pparray = &lpsa2;

    return vnt;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE LPUNKNOWN CCONV ClearObject(LPUNKNOWN *lpUnk)
{
    LPDISPATCH pdisp;
    
    if((*lpUnk)->QueryInterface(IID_IDispatch, (LPVOID *)&pdisp) ==
	NOERROR)
    {
        DISPID dispid;
        DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

	#if defined(_WIN32)
 	    BSTR name = L"clear";
	#else
	    BSTR name = "clear";
	#endif		
            
        if(pdisp->GetIDsOfNames(IID_NULL, &name, 1, NULL, &dispid) ==  
	   S_OK)
        {
            pdisp->Invoke(dispid, IID_NULL, NULL, DISPATCH_METHOD,
			  &dispparamsNoArgs, NULL, NULL, NULL);
        }

	pdisp->Release();
    }

    return *lpUnk;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE VARIANT CCONV VariantByRef(VARIANT *pvar)
{
    VARIANT var;

    // must initialize all Variants
    VariantInit(&var);        
    VariantCopy(&var, pvar);

    VariantChangeType(&var, &var, NULL, VT_BSTR);

    return var;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE VARIANT CCONV VariantByVal(VARIANT var)
{
    VARIANT vnt;

    // must initialize all Variants
    VariantInit(&vnt);        
    VariantCopy(&vnt, &var);

    VariantChangeType(&vnt, &vnt, NULL, VT_BSTR);

    return vnt;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE BYTE CCONV PassByte (BYTE byt, LPBYTE pbyt)
{
	*pbyt = byt;
	return byt + 1;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE short CCONV PassInteger (short intgr, short far *pintgr)
{
	*pintgr = intgr;
	return intgr + 1;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE short CCONV PassBoolean (short bln, short far *pbln)
{
	*pbln = ~bln;
	return bln;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE LONG CCONV PassLong (LONG lng, LPLONG plng)
{
	*plng = lng;
	return lng + 1;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE float CCONV PassSingle (float sng, float far *psng)
{
	*psng = sng;
	return sng + (float)1.99;
}

// THIS FUNCTION IS *NOT* CHANGED FOR THE UNICODE VERSION!
NOMANGLE double CCONV PassDouble (double dbl, double far *pdbl)
{
	*pdbl = dbl;
	return dbl + 1.99;
}


=====================================================================
12: Creating a TypeLib For All The Example Functions In This Document
=====================================================================

You  can  do away with the requirement for DECLARE statements, if you 
create  a  TypeLib  for  each of the functions exported from the DLL. 
However,  it  is  important to realize that VB4 (32-bit version) will 
not  automatically  convert  strings from Unicode to ANSI and back to 
Unicode   when   not  using  DECLARE  statements. So,  for  functions 
involving  strings  you must compile it as a 32-bit Unicode DLL. This 
is not  a problem with Vb4 (16-bit) which uses ANSI. So, in this case 
you can use the regular ANSI version of the above dll.

The  following example demonstrates how to build an ODL file for each 
of the above functions, in the 32-bit version:

   #ifdef WIN32
       #define CCONV __stdcall
   #else
       #define CCONV __pascal
   #endif

   [
      uuid(B9421A20-B985-11ce-825E-00AA0068851C),
      helpstring("vb4dll Type Library Info"),
      version(1.0)
   ]
   
   library VB4DLL32
   {
      [dllname("vb4dll32.dll")]

      module VB4DLLAPI
      {
	 [
            entry("ProcessArray")
	 ]
	 VARIANT CCONV ProcessArray
	 (
	 	[in, out]  SAFEARRAY(BSTR) *ppsa
	 );

	 [
            entry("CopyArray")
	 ]
	 VARIANT CCONV CopyArray
	 (
	 	[in, out]  SAFEARRAY(BSTR) *ppsa
	 );

	 [
            entry("UpperCaseByRef")
	 ]
	 BSTR CCONV UpperCaseByRef
	 (
	 	[in, out]  BSTR *pbstrOriginal
	 );

	 [
            entry("UpperCaseByVal")
	 ]
	 BSTR CCONV UpperCaseByVal
	 (
	 	[in]  BSTR bstrOriginal
	 );

	 [
            entry("ClearObject")
	 ]
	 IDispatch * CCONV ClearObject
	 (
	 	[in]  IDispatch **lpDisp
	 );

	 [
            entry("VariantByRef")
	 ]
	 VARIANT CCONV VariantByRef
	 (
	 	[in]  VARIANT *pvar
	 );

	 [
            entry("VariantByVal")
	 ]
	 VARIANT CCONV VariantByVal
	 (
	 	[in]  VARIANT var
	 );

	 [
            entry("PassByte")
	 ]
	 unsigned char CCONV PassByte
	 (
	 	[in]  unsigned char byt,
		[out]  unsigned char *pbyt
	 );

	 [
            entry("PassInteger")
	 ]
	 short CCONV PassInteger
	 (
	 	[in]  short intgr,
		[out]  short *pintgr
	 );

	 [
            entry("PassBoolean")
	 ]
	 boolean CCONV PassBoolean
	 (
	 	[in]  boolean bln,
		[out]  boolean *pbln
	 );

	 [
            entry("PassLong")
	 ]
	 long CCONV PassLong
	 (
	 	[in]  long lng,
		[out]  long *plng
	 );

	 [
            entry("PassSingle")
	 ]
	 float CCONV PassSingle
	 (
	 	[in]  float sng,
		[out]  float *psng
	 );

	 [
            entry("PassDouble")
	 ]
	 double CCONV PassDouble
	 (
	 	[in]  double dbl,
		[out]  double *pdbl
	 );

      };
   };
 
To  compile  this  ODL  into a type library (.TLB), run the following 
from the command line:

  MKTYPLIB /I C:\MSVC20\INCLUDE /win32 /tlb vb4dll32.tlb vb4dll32.odl 

Once the .TLB file is created, make a reference to it from VB (select 
the  Tools\Referneces...  menu)  and you are now ready to call any of
the above functions without a DECLARE statement.

NOTE#1:  For  the  16-bit  version, you can still essentially use the
same ODL file; however, you have to change the library name  (to say,
VB4DLL16 in this case) and the dllname. Then  run  the following from
the command line:

  MKTYPLIB /I C:\MSVC20\INCLUDE /win16 /tlb vb4dll16.tlb vb4dll16.odl

NOTE#2:  You cannot call certain functions using typelibs because VBA
does not allow  it. These  are  functions  with parameters  that  are
declared "As Any", and functions  that take a  UDT  by reference. For
these functions you still have to use DECLARE statements. That is why
in the  above  example, the  CopyUDT  function has not been modified,
even though it manipulates strings.

NOTE#3:  It is  important  to use the  GUIDGEN.EXE  utility to create
your  own  guids  for use in your ODL file. Do not use the same guids
from this sample for use with your own applications.


=========================
13:  Additional Resources
=========================

For  additional  information about OLE and OLE 2.0  APIs, consult the 
following resources:

The OLE 2.0 Programmer's Reference Vol. 1 & 2, Microsoft Press, 1994
The  Visual Basic Control Migration Pack, which is part of the Visual 
Basic, Professional Edition, version 4.0 on CD-ROM
