/*******************************************************
   File dllparam.c

   Demonstration of Visual Basic to DLL interface

   Written By Daniel Appleman
   Copyright (c) 1992, by Desawre - All rights reserved

********************************************************/

#define LINT_ARGS
#define NOMINMAX
#define OEMRESOURCE
#define NOREGION
#define NOWH
#define NORASTEROPS
#define NOMETAFILE
#define NOCLIPBOARD
#define NOOPENFILE
#define NOKANJI
#define NOSOUND
#define NOCOMM
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES 

#include "windows.h"
#include "\windows\vb\custom\vbapi.h"
#include "stdlib.h"
#include "string.h"

HANDLE modulehandle;    /* module handle for library */

BOOL FAR PASCAL LibMain(HANDLE hModule, WORD wDataSeg,
                        WORD cbHeap, LPSTR lpszCmdLine)
{
   modulehandle = hModule;
   return(1);
}

VOID FAR PASCAL WEP (int bSystemExit)
{   return;  }

char tbuf[80];
#define MSGBOX(s) MessageBox(GetFocus(), (LPSTR)tbuf, \
                    (LPSTR)s, MB_OK)

/* Utility function to convert floating point to string
 * Quick and dirty - as the DLL library does not have
 * function gcvt  */
fToString(double d, char *t)
{
   int x;
   static int decimalvar, signvar;
   char *buffer;
   buffer = fcvt(d,5,&decimalvar, &signvar);
   if(signvar) *t++ = '-';
   for(x=0; x<decimalvar && *buffer; x++) *t++ = *buffer++;
   if(*buffer) *t++ = '.';
   while(*buffer) *t++ = *buffer++;
   *t = '\0';
}

/* These function demonstrates passing numeric
 * variables by value, and returning numeric variables. 
 * The MessageBox statement in each routine shows the
 * value of the variable received. */
int FAR PASCAL ReceivesInteger(int x)
{
   itoa(x, tbuf, 10); /* Place value in temporary buffer */
   MSGBOX("ReceivesInteger");
   return(x);
}

long FAR PASCAL ReceivesLong(long y)
{
   ltoa(y, tbuf, 10); /* Place value in temporary buffer */
   MSGBOX("ReceivesLong");
   return(y);
}

float FAR PASCAL ReceivesSingle(float f)
{
   fToString((double)f, tbuf);
   MSGBOX("ReceivesSingle");
   return(f);
}

double FAR PASCAL ReceivesDouble(double d)
{
/* Refer to the article text for some important notes
 * relating to the declaration of functions that
 * receive or return double parameters.  */
   fToString(d, tbuf);
   MSGBOX("ReceivesDouble");
   return(d);
}

/*
 * These functions demonstrate passing numeric
 * variables by reference.  Note how the DLL function
 * can modify the variable used by VB.  These
 * particular examples are defined as VOID to be used
 * by SUB declarations in VB.  One could just as easily
 * had these return results as done in the examples
 * above.  */

VOID FAR PASCAL Add5ToInteger(int FAR *x)
{  *x = (*x) + 5;  }
VOID FAR PASCAL Add5ToLong(long FAR *y)
{  *y = (*y) + 5;  }
VOID FAR PASCAL Add5ToSingle(float FAR *f)
{  *f = (*f) + 5;  }
VOID FAR PASCAL Add5ToDouble(double FAR *d)
{  *d = (*d) + 5;  }

/* Currency is a special 8 byte data type. 
 * Fortunately, Microsoft C (and probably others) can
 * pass structures as parameters by value, and return
 * them.  You'll need to make sure that your compiler
 * uses the Microsoft C calling convention on
 * structures for this to work.  In practice, you will
 * need to build your own complete library of currency
 * data type math to use this data type.  Refer to the
 * article text for details. */

/* Currency data type structure */
typedef struct currencystruct {
   char cbuf[8];
} currency;

/* This example shows passing a currency variable by
 * value, and returning a currency value */
currency FAR PASCAL ReceivesCurrency(currency curr)
{
   double tres = 0, factor = 1, tval;
   LPSTR tptr;
   short x;

/* This currency variable is in base 10 with 4 digits
 * to the right of the decimal point */

/* This conversion (for display purposes) involves a
 * loss of precision and does not handle negative
 * numbers */
   for(x=0; x<8; x++) { /* Convert currency to double */
      tval = (double)((WORD)curr.cbuf[x]);
      tres+= tval*factor;
      factor *=256;
      }
   tres/=10000;   /* Number is fixed 4 digit precision,
                    divide to obtain the actual value */
   fToString(tres, tbuf);
   MSGBOX("ReceivesCurrency");
   return(curr);
}

/* This example is call-by-reference. */
void FAR PASCAL AddPennyToCurrency(currency FAR *curptr)
{
   short x;
   WORD temp, toadd;

/* This currency variable is in base 10 with 4 digits
 * to the right of the decimal point */

/* Now you can use whatever math routines you have to
 * maniuplate this buffer - consider this trivial 'add
 * penny' algorithm */
   toadd = 100;   /* 1 penny in this scale */
   for(x=0; x<8 && toadd; x++) {
      temp = curptr->cbuf[x];
      temp+=toadd;
      if(temp>=256) {
         temp-=256;  toadd=1; /* continue with carry */
         }
      else toadd=0;  /* Finished with the addition */
      curptr->cbuf[x] = temp;
      }
}

/* Method used for most API calls.  VB passes a null
 * terminated string */
VOID FAR PASCAL ReceivesString(LPSTR tptr)
{/* Warning - it's not a copy despite the
  * byval part in the declaration */
   MSGBOX("ReceivesString");
}

/* This example shows how a string can be modified - as
 * long as you don't go beyond the space allocated */
VOID FAR PASCAL ChangesString(LPSTR tptr)
{/* Warning on overwriting null!!! */
   if (*tptr) *tptr = '!';
}

/* This example shows how you can pass Visual Basic
 * strings to a DLL, but the DLL must be linked with
 * VBAPI.LIB - part of the VB control development kit. */
VOID FAR PASCAL ReceivesVBString(HLSTR sptr)
{
   LPSTR tptr;
   int vbstrlen;
   WORD x;
   vbstrlen=VBGetHlstrLen(sptr); /* Get len of VB string */
   tptr = VBDerefHlstr(sptr); /* Get ptr to VB string */
   for(x=0; x<vbstrlen && x<sizeof(tbuf)-1; x++)
      tbuf[x] = *tptr++;
   tbuf[x] = '\0';   /* Null terminate the string */
   /* Remember - VB strings can contain NULLs */
   MSGBOX("ReceivesVBString");
}

/* This example shows how you can change a string that
 * was passed as a parameter in a DLL.  No length
 * restrictions apply when using this technique (other
 * than the usual VB string length limits */
VOID FAR PASCAL ChangesVBString(HLSTR sptr)
{
   VBSetHlstr((HLSTR FAR *)&sptr,(LPSTR)"Any Length OK",13);
}

/* This example shows how you can return a VB string
 * from a DLL.  Note - this technique is not
 * documented, and while it does seem to work, there is
 * no guarantee that it will work under future versions
 * of VB */
HLSTR FAR PASCAL ReturnsVBString()
{
   char *a = "This string is created in the DLL";
   return(VBCreateHlstr((LPSTR)a,strlen(a)));
}

typedef struct usertypestruct {
   int a;   int b;
   int c;   int d;
   HLSTR hs;
} usertype;

/* Call by reference only */
VOID FAR PASCAL ReceivesUserType(usertype FAR *u)
{
   wsprintf((LPSTR)tbuf,
            (LPSTR)"usertype contains %d %d %d %d",
            u->a, u->b, u->c, u->d);
   MSGBOX("ReceivesUserType");
}

/* VB strings in a user defined type can be accessed. 
 * If not initialized, they may be null (though I have
 * never actually seen this - see article text), in
 * which case the DLL can create the string - otherwise
 * the DLL should use VBSetHlstr().  */
VOID FAR PASCAL AddUserString(usertype FAR *u)
{
   if(!u->hs)
   /* Documentation suggests this case may be possible */
      u->hs = VBCreateHlstr((LPSTR)"New string here!",16);
   else
      VBSetHlstr(&u->hs, (LPSTR)"Replaced string", 15);
}

/* Array of integers - Be careful not to exceed the limit of the array!
 * This technique can be used on all numeric data types.
 * Note the special calling sequence in the VB example.
 * It will not work on strings. */
VOID FAR PASCAL ReceivesIntArray(int FAR *iptr)
{
   wsprintf((LPSTR)tbuf,
            (LPSTR)"1st 4 entries are %d %d %d %d",
            *(iptr), *(iptr+1), *(iptr+2), *(iptr+3));
   MSGBOX("ReceivesIntArray");
}

/* CDK-oriented stuff */
HWND FAR PASCAL GetControlHwnd(HCTL hctl)
{
   return(VBGetControlHwnd(hctl));
}
