Home
Back
|
Calling
VBToolbox from C and C++ Code VBToolbox may be
called statically from C, C++ or any language which supports the standard Microsoft MSVC
"LIB" file format. This would include the Microsoft Visual C++ family of
compilers and many other compilers which can use this type of library. The LIB file
(mslib145.lib) will be included in distributions or made available via a direct download
link complete with a header file which declares the relevant exported function prototypes.
Unicode or ANSI?
The standard distribution is ANSI. A Unicode version is also available
both for LIB and TLB formats. The Unicode version of VBToolbox is named MSLIB146.*
How To Use
First, either #include the supplied header file
(MSLIB145.H) or selectively cut/paste the required function prototypes into your source
code.
Secondly, you need to add the LIB file (ANSI:mslib145.lib or Unicode:mslib146.lib) to the
linker settings of your compiler. You will usually need to go to your project settings
area and find the required page or tab for linker settings and then append the path the
above files so the linker knows how to find the file. Instructions will vary depending on
which compiler you are using. The compiler must support the MSVC5 LIB format.
After this has been done you can just use the functions as you would any
other C/C++ function. You will, of course, need to check the function prototypes carefully
in order to pass the correct parameters to each function call.
C/C++ Header File
The LIB, TLB and ANSI header file is included with the main distribution ZIP
file.
2011 April 9th - v1.39 ANSI Header File
Update
Page top
Using the VBToolbox CallDLL Functions to Call External DLLs
The CallDLL* series of functions may be used by C/C++ programmers in
order to call any compatible __stdcall
DLL function to call dynamically on-the-fly without the need to use LoadLibrary
and complex function declaration prototypes. For CallDLL* prototypes see below. Any type
of parameter may be passed via CallDLL if it can be coerced to some value held within a
VARIANT field.
Advantages
Flexibility. Calls don't need to be "hard-wired" and declared
in advance or use complex, hard-to-fathom function prototypes. DLL function calls can be
made conditionally or on-the-fly from code logic. If code is generated carefully then the
code will be stable. Can call just about any DLL including Windows API/system DLLs.
Disadvantages
Fragility. Far less error-checking. Requires an understanding of
coercing data types into VARIANT structures. Requires careful adherence to the function
prototype and generation of variant arrays used to pass data. Sloppy call-generation may
make your code unstable. CallDLL is designed to handle ANSI data but might be coerced to
use Unicode (this has not been tested). Called DLL exported functions must support the __stdcall
interface unless CallDLLCDecl is used (v1.40+)
Function |
Return Type(s) |
DLLParamInit |
Non-DLL function (not present for VB) which is included in the C/C++
header
Used to clear/initialise/wipe VARIANT structures |
CallDLL |
Any 32-bit, 16-bit or 8-bit types (DWORD/WORD), also LPSTR
(char*), BSTR, short, byte/char, void* and struct* e.. VARIANT*.
Values narrower than 32 bit will need to be coerced (cast) on return to preserve sign-bit
values.
Values wider than 32 bit cannot be handled (e.g. int64) although pointers to such types
may be passed via a cast |
CallDLLSingle |
Returns 4 byte float type (VB style Single) |
CallDLLDouble |
Returns an 8 byte double type (VB style Double) |
CallDLLVariant |
Returns an allocated 16-byte VARIANT type |
CallDLLCDecl |
__cdecl
declared DLLs *only* such as msvcrt40.dll - C/C++ and similar languages only, not for use
with Visual BASIC
This function cleans up the stack according to the __cdecl
calling convention rather than relying on the called function (VBToolbox v1.40+)
Any 32-bit, 16-bit or 8-bit types (DWORD/WORD), also LPSTR (char*), BSTR, short,
byte/char, void* and struct* e.. VARIANT*.
Values narrower than 32 bit will need to be coerced (cast) on return to preserve sign-bit
values.
Values wider than 32 bit cannot be handled (e.g. int64) although pointers to such types
may be passed via a cast
Strings (LPSTR) may be cast to (long) and passed as such if convenient
This function permits the calling of functions with varying numbers of arguments such as
printf() |
CallDLLCDeclSingle |
As CallDLLSingle but calls using the __cdecl
calling convention
This function permits the calling of functions with varying numbers of arguments such as
printf() |
CallDLLCDeclDouble |
As CallDLLDouble but calls using the __cdecl
calling convention
This function permits the calling of functions with varying numbers of arguments such as
printf() |
CallDLLCDeclVariant |
Not implemented as few cases are anticipated for the use of VARIANT
objects with Cdecl calling conventions |
CallDLL Examples
Parameters are passed by means of a VARIANT array. This must be suitably
dimensioned and declared according to the number of parameters the DLL function call
requires. You must specify both the variable type and value for each variant in the array.
Additionally, you should call DLLParamInit() to ensure that variants are cleared to zero
(wiped) - particularly if they are re-used within a program. The number of parameters
being passed must be specified in the call and the particular casting required to pass the
variant array must be adhered to - (VARIANT*)&varname. This variant array must be an
actual array and not an array of pointers unless these have been declared to point to a
valid variant array and any necessary casts adjusted.
Only external functions which are compliant with the __stdcall
convention may be called using the CallDLL* functions
The exception is that __cdecl (C
calling convention) functions may be called by non VB programs using CallDLLCDecl
Once the above rules are adhered to, calling the CallDLL* family is
quite straightforward.
Standard C/C++ lib functions and Win32 API are highlighted in magenta, VBToolbox functions are highlighted in orange
// Prototype:
// BSTR _stdcall MTRandomStr(long len, const short style=RANDOM_STYLE_MIXED)
VARIANT v[2];
DLLParamInit((VARIANT*)&v,howbig(v)); // Ensure variant array zeroed out
v[0].vt=VT_I4; // long (4 byte) data type
v[0].lVal=60; // string length - 60 chars
v[1].vt=VT_I2; // short (2 byte) data type
for(int i=0; i<15; i++)
{
v[1].iVal=i; // Call with each type of string format
printf("Randomstr(%i)=[%s]\n",v[1].iVal,
(LPSTR)CallDLL("mslib145.dll","MTRandomStr",&errcode,2,(VARIANT*)&v));
}
|
Another example. Wordlist() creates a SAFEARRAY string which is returned
in a VARIANT
// Wordlist ////////////////
// Prototype: VARIANT _stdcall WordList(LPCSTR s, long base=0)
VARIANT v[2];
VARIANT vt; // Return variant
DLLParamInit((VARIANT*)&v,howbig(v));
v[0].vt=VT_BSTR;
v[0].bstrVal=(BSTR)"Now is the time for all good men to come to the aid of the party";
v[1].vt=VT_I2; // short (2 byte) data type
v[1].iVal=0;
vt=CallDLLVariant("mslib145.dll","WordList",&errcode,2,(VARIANT*)&v);
printf("Done WordList. Calling PrintR()\n");
// Prototype: VARIANT _stdcall QSort(VARIANT* v,
// const short reverse_sort=0, const short ignore_case=0)
QSort(&vt,0,1); // Call VBToolbox LIB function directly
// Prototype: long _stdcall PrintR(const VARIANT* v,
// const short show_empty=0, const short show_address=0);
PrintR(&vt,0); // Call VBToolbox LIB function directly
VariantClear(&vt); // Release dynamic memory
|
And another...
// CSVSplit and ElementCount
// Prototype: VARIANT _stdcall CSVSplit(LPCSTR s,
// const long inbase=0, char separator=',')
// Prototype: long _stdcall ElementCount(VARIANT* v)
DLLParamInit((VARIANT*)&v,howbig(v));
VARIANT v[3];
VARIANT vt; // Return variant
v[0].vt=VT_BSTR;
v[0].bstrVal=(BSTR)"One,two,three four,five,six seven, eight";
v[1].vt=VT_I4; // long (4 byte) data type
v[1].lVal=1;
v[2].vt=VT_I1; // char (1 byte) data type
v[2].bVal=',';
vt=CallDLLVariant("mslib145.dll","CSVSplit",&errcode,3,(VARIANT*)&v);
printf("Finished CSVSplit - calling PrintR...\n");
PrintR(&vt,0); // Call VBToolbox function
// Now call ElementCount with variant vt
v[0].vt=vt.vt; // Copy original variant flag type
v[0].pvarVal=&vt; // pointer to variant
printf("Element count=%i\n",CallDLL("mslib145.dll","ElementCount",&errcode,1,(VARIANT*)&v));
VariantClear(&vt); // Release dynamic memory
|
Example calling the Win32 API using _stdcall...
// Win32 API Prototype: int WINAPI MessageBox(HWND hWnd,
// LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
DLLParamInit((VARIANT*)&v,howbig(v));
v[0].vt=VT_NULL; // Use this for NULL parameters
v[1].vt=VT_BSTR;
v[1].bstrVal=(BSTR) "Now is the time for all good men to vote against the party";
v[2].vt=VT_BSTR;
v[2].bstrVal=(BSTR) "Hello world";
v[3].vt=VT_UI4;
v[3].ulVal=MB_ICONINFORMATION | MB_ICONQUESTION | MB_OKCANCEL;
ret=CallDLL("User32.dll","MessageBoxA",&errcode,4,(VARIANT*)&v);
printf("Check: MessageBox=[%i] (0==FAIL) (errcode=%i)\n",ret,errcode);
|
Calling a Microsoft Visual C++ Runtime DLL function (__cdecl)
// Prototype: char* asctime( const struct tm* timeptr );
// Calling a __cdecl DLL which takes a struct
struct tm when;
time_t now;
time(&now); // Get current time as c time
when = *localtime(&now); // Convert from c time to struct
DLLParamInit((VARIANT*)&v,howbig(v));
v[0].vt=VT_I4;
v[0].lVal=(long)&when; // Cast struct address to long
LPSTR ptr=(LPSTR)CallDLLCDecl("msvcrt40.dll","asctime",&errcode,1,(VARIANT*)&v);
printf("asctime() returned %s errcode=%i\n",ptr,errcode);
// asctime() returned [Mon Apr 23 00:45:33 2012
|
Calling a Microsoft Visual C++ Runtime DLL function (__cdecl)
// Test calling variable-arg C function
// int printf( const char *format [, argument]... );
DLLParamInit((VARIANT*)&v,howbig(v));
v[0].vt=VT_I4;
v[0].lVal=(long)"* Hello from %s short=%i long=%i double=%f *\n";
v[1].vt=VT_I4;
v[1].lVal=(long)"printf()";
v[2].vt=VT_I2;
v[2].iVal=1234;
v[3].vt=VT_I4;
v[3].lVal=123456790;
v[4].vt=VT_R8;
v[4].dblVal=1234.56789;
long ret=CallDLLCDecl("msvcrt40.dll","printf",&errcode,5,(VARIANT*)&v);
printf("printf() returned: %i char(s) errcode=%i\n",ret,errcode);
// Console echo from printf...
// * Hello from printf() short=1234 long=123456790 double=1234.567890 *
|
Calling a Microsoft Visual C++ Runtime DLL function (__cdecl) from
Visual BASIC
Dim v(5) As Variant
Dim i As Integer
v(0) = VBStrToCStr("c:\windows\system32\drivers\hosts.txt")
v(1) = VBStrToCStr(String$(255, " ")) ' Create C ANSI buffers using malloc()
v(2) = VBStrToCStr(String$(255, " ")) ' VBStrToCStr returns a 32-bit long
v(3) = VBStrToCStr(String$(255, " "))
v(4) = VBStrToCStr(String$(255, " "))
CallDLLCDecl "msvcrt40.dll", "_splitpath", ErrCode, 5, v(0)
Debug.Print "_splitpath: Errcode="; ErrCode
CStrFree v(0) ' Release memory using free()
For i = 1 To 4
Debug.Print i; "=["; CStrToVBStr(v(i)); "]"
CStrFree v(i) ' Release memory using free()
Next
|
_splitpath: Errcode= 0
1 =[c:]
2 =[\windows\system32\drivers\]
3 =[hosts]
4 =[.txt]
|
Calling a Microsoft Visual C++ Runtime DLL function (__cdecl) from
C++ (as above code)
C strings may be coerced into longs without ill effect on Win32
DLLParamInit((VARIANT*)&v,howbig(v));
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
v[0].vt=VT_I4;
v[0].lVal=(long)"c:\windows\system32\drivers\etc\fred.txt";
v[1].vt=VT_I4;
v[1].lVal=(long)&drive;
v[2].vt=VT_I4;
v[2].lVal=(long)&dir;
v[3].vt=VT_I4;
v[3].lVal=(long)&fname;
v[4].vt=VT_I4;
v[4].lVal=(long)&ext;
CallDLLCDecl("msvcrt40.dll","_splitpath",&errcode,5,(VARIANT*)&v);
printf("_splitpath returned drive [%s]\n",drive);
printf("_splitpath returned dir [%s]\n",dir);
printf("_splitpath returned fname [%s]\n",fname);
printf("_splitpath returned ext [%s]\n",ext);
printf("_splitpath() returned void errcode=%li\n",errcode);
|
Code highlighted statically using CGI-Highlight
Page top
Troubleshooting
If crashes occur this will usually be due to the following
- Improperly declaring/setting the variant element type (VT_*_)
- NULL values should normally be passed using VT_NULL with no value set
- Setting the wrong variant field with passed data data
- Specifying the wrong number of parameters in the CallDLL* call
These must match the function prototype even if default parameters are present in the
declared library function
- Using the wrong function cast when passing the variant array (VARIANT*) -
pass &VARIANT or (VARIANT*)&varname if an array
- Calling an external DLL which does not support the __stdcall
interface
- CDECL DLLs such as MSVCRT40.DLL require the use of CallDLLCdecl()
(v1.40+)
Page top
CallDLL Function Prototypes
These are the export prototypes of relevant VBToolbox
CallDLL* functions. These will be required for C/C++ programs and their declaration may be
adapted for use with other languages which can use the import LIB file.
The CallDLL* routines are intended primarily to support
calling external DLLs using the __stdcall calling
convention via Visual BASIC, but additional functions are offered which support the __cdecl
calling convention to support C/C++ programs calling C DLLs
Calling C routines from Visual BASIC may be problematic
since VB handles Win32 API strings via handles. Also it holds these internally as Unicode
although function parameter calls to declared DLLs are automatically converted to/from
ANSI. Extra VBToolbox functions are available from v1.40+ which allocate/deallocate
strings using malloc/free - these being CStrToVBStr, VBStrToCStr and CStrFree.
DWORD _stdcall CallDLL(LPCSTR dllname, LPCSTR funct,
long* errcode=NULL, long paramcount=0, VARIANT* data=NULL);
float _stdcall CallDLLSingle(LPCSTR dllname, LPCSTR funct,
long* errcode=NULL, long paramcount=0, VARIANT* data=NULL);
double _stdcall CallDLLDouble(LPCSTR dllname, LPCSTR funct,
long* errcode=NULL, long paramcount=0, VARIANT* data=NULL);
VARIANT CallDLLVariant(LPCSTR dllname, LPCSTR funct,
long* errcode=NULL, long paramcount=0, VARIANT* data=NULL);
// Cdecl variants... (v1.40+).
DWORD _stdcall CallDLLCDecl(LPCSTR dllname, LPCSTR funct,
long* errcode=NULL, long paramcount=0, VARIANT* data=NULL);
float _stdcall CallDLLCDeclSingle(LPCSTR dllname, LPCSTR funct,
long* errcode=NULL, long paramcount=0, VARIANT* data=NULL);
double _stdcall CallDLLCDeclDouble(LPCSTR dllname, LPCSTR funct,
long* errcode=NULL, long paramcount=0, VARIANT* data=NULL);
// CallDLLCDeclVariant is not implemented
|
Page top
Calling Sub()-routines Instead of Functions
VBToolbox Visual BASIC Sub(routines) - i.e. C/C++ functions which take
no arguments, may be called in an abbreviated manner. These don't require a valid VARIANT
structure and NULL may be passed along with an argument count of Zero
printf("IsNetworked=%i\n",CallDLL("mslib145.dll","IsNetworked",&errcode,0,NULL));
|
Page top
Calling Sub()-routines Instead of Functions
For C/C++ and other non-VB use the CallDLLCDecl* range of functions
provided with VBToolbox v1.40+. The standard DLL interface is __stdcall
but some DLLs export using the __cdecl
convention. Care must be taken to use the correct calling convention otherwise the stack
will become corrupted and your program will behave unpredictably. The Cdecl convention
passes parameters in right-to-left order as with __stdcall,
but the calling routine cleans up the allocated stack pointer
// Prototype: msvcrt40.dll - long atol(const char *string);
DLLParamInit((VARIANT*)&v,howbig(v));
long errcode=0;
v[0].vt=VT_I4;
v[0].lVal=(long) "1234567890";
long ret=CallDLLCDecl("msvcrt40.dll","atol",&errcode,1,(VARIANT*)&v);
printf("atol=[%i] errcode=%i\n",ret,errcode);
|
Note that the Microsoft Visual C++ runtime is given as an example,
there would usually be no practical reason to call the DLL from C/C++ programs
Page top
Releasing Returned String Memory
Many VBToolbox functions will return strings which have been
dynamically-allocated by the Win32 API family of routines such as SysAllocString.
In all cases the result of these function, if not NULL, must be released using
SysFreeString(). Visual BASIC would normally take care of such memory housekeeping.
//Prototype: LPSTR _stdcall CreateGUID(const short formatted=TRUE)
v[0].vt=VT_I4; // 32-bit integer.
v[0].lVal=1; // 1L (TRUE)
LPSTR ptr=(LPSTR)CallDLL("mslib145.dll","CreateGUID",&errcode,1,(VARIANT*)&v);
printf("CreateGUID=%s\n",ptr);
SysFreeString((BSTR)ptr);
|
Page top
VBToolbox Functions Not Recommended for C/C++ Use
The following VBToolbox library functions are not recommended for C/C++
use as they are designed specifically to accommodate the VB interface(s)
VBToolbox Function Name |
Notes |
CString |
Designed for use with VB memory management
The input parameter must be a string allocated using SysAlloc* API calls
Cannot be called with stack or heap based strings
The input string is freed (this behaviour may be altered)
The return string is identical to the new input string
May be called with care from C/C++ |
Page top
Interfacing VBToolbox CallDLL* Functions With C#.Net
Can be done but is possibly not recommended as this relies
on blocks of code which are marked as "unsafe" in order to access
pointers in C#. Marshalling of
raw structures and low-level bit-manipulation is necessarily limited in C# for security
reasons. Most VBToolbox functions can be called via a suitable pinvoke declaration with a suitable
attribute block. Using pointers means that code is less portable. Research is continuing.
Strings may also be Marshalled instead of using VBStrToCStr
etc using (int)Marshal.StringToHGlobalAnsi("Hello World"); .
// Partial (incomplete) C# example code calling VBToolbox via CallDLL interface
namespace ConsoleApplication1
{
// VT_* Enums for interfacing with VARAIANT routines
public enum Vnt: ushort // (enum definition omitted) ...
unsafe class Program
{
// Struct definition (incomplete) - emulates a C union
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 16)]
public unsafe struct Variant // (code omitted) ...
[DLLImport("mslib145.dll")]
private static extern int CallDLL(string dllName, string funct,
int* errcode, int paramcount = 0, Variant* v = null);
// Other DLL definitions omitted ....
static unsafe void main(string[] args)
{
double d;
Variant[] vt = new Variant[10];
// General service lib routine calls
Console.WriteLine("LibVersion=" + (LibVersion() / 100.0).ToString("0.00"));
Console.WriteLine("LibUnicode=" + LibUnicode());
Console.WriteLine("LibName=" + LibName());
Console.WriteLine("LibName=" + LibDate());
// Test short-form call
d = CallDLLDouble("mslib145.dll", "VBNow", &errcode);
Console.WriteLine("errcode=" + errcode);
Console.WriteLine("VBNow=" + d);
// Long form call with CC++ malloc() memory allocation
// char *__stdcall MKTempName(const char * path,const char * ftype,
// const short pathlen,const short style);
vt[0].vt = (ushort)Vnt.VT_BSTR;
vt[0].bstrVal = 0; // Path (NULL)
vt[1].vt = (ushort)Vnt.VT_BSTR;
ptr = VBStrToCStr(".tmp"); // Allocate a C string using malloc()
vt[1].bstrVal = ptr;
vt[2].vt = (ushort)Vnt.VT_I2;
vt[2].iVal = 8;
vt[3].vt = (ushort)Vnt.VT_I2;
vt[3].iVal = 0; // RANDOM_STYLE_MIXED==0
fixed (Variant* v = &vt[0]) // This wrapper sucks but appears necessary
{ // ... or a variable-scoping error may occur
ptr2 = CallDLL("mslib145.dll", "MKTempName", &errcode, 4, v);
}
CStrFree(ptr); // Call to CC++ free() for malloc()ed string
Console.WriteLine("MKTempName: " + StrCast(ptr2));
}
}
}
|
References
Page top
This page and software, unless otherwise stated, is
Copyright (c) M Shaw
This page was developed using "Dinosaurwear" (FrontPage 98)
Last updated 26 February, 2021 |