While a lot of measurement instruments have RS-232 serial port that does not require additional software drivers, many devices are connected to PC through GPIB (IEEE488) or another specialized interfaces such as VXI. Some kinds of measurement hardware are implemented as PC extension cards immediately pluggable into the PC motherboard. All USB-based devices also require special drivers.
Device driver software may install a set of ActiveX controls. In most cases these controls may be instantiated and accessed from HTML and script code without problems. Unfortunately, sometimes all you have in hands is a dynamic link library (DLL) and a header file with function prototypes. Unlike full-scale Visual Basic, VBScript has no ability to declare and call external binary units, so you might think that you must create special ActiveX wrapper control for that DLL in order to invoke its API from your scripts.
Although in some cases this is the most practical solution, it requires programming environment such as Delphi or Visual Studio, and a solid development background. There is also another option that helps you to save time and keep away from professional development tools. As of build #290, DMForms library includes special DMDLLHelper object that can call into wide set of functions exported by DLLs immediately from scripting languages.
The most important disadvantage of DLLs is that they don't include any type information, so you have to describe function prototypes (including function name, type of parameters and return value) "by hands". As a result, you have to understand in details machine-specific types system, calling conventions and parameter passing rules. By contrast, any ActiveX component does all this job automatically. Keep in mind that incorrect declaration of functions in the DLL will definitely result in application failure, so you should always prefer ActiveX controls (if possible), and be extremely accurate if you decide to work with DMDLLHelper object.
Typically hardware-related APIs exported from device driver DLLs use following subset of machine-specific types:
All abovementioned types are scalar types - that is, they don't involve indexing. In addition, DMDLLHelper object supports one-dimensional index for all numeric types. Internally, it allocates as many numbers as was passed in the appropriate Variant array parameter, so you should provide sufficient room if your function returns array of numbers. Also you can try to represent compound types (such as records) as plain arrays of bytes or words. In general, only size of the variable (in bytes) is important; for example, boolean values on Win32 platform typically passed as dwords. Returned value may have one of the types listed above, or function may have no return value ("void" in C-family languages, "procedure" in Delphi and "subroutine" in Visual Basic).
Calling conventions imply what assembler language code will be produced when compiler processes declaration of external function. This is important because usually DLL and caller application are written in different programming environments, and DMDLLHelper object actually implements simple just-in-time compiler.
Every routine called by DMDLLHelper must be declared using stdcall (_WINAPI) calling conventions. Scalar numeric parameters may be passed either by value or by reference ("var" in Delphi or "ByRef" in VBScript). Arrays always passed by reference, but the object should be informed whether it must update variant array after DLL function returns. Keep in mind that PChars in DLL function parameter declaration are compatible with byte arrays, as well as pointers to other scalar numeric types are compatible with appropriate arrays and parameters passed by reference. Anyway, when using strings or arrays, caller must allocate and pass to the object variant array of sufficient size.
Let's now take a look how all that stuff works in practice. Suppose we have old Hewlett-Packard (now Agilent) HP4191A impedance analyzer with GPIB interface, and USB-to-GPIB 82357A adapter, and we would like to create device driver for DM2003. Although 82357A adapter comes with Agilent VisaCOM ActiveX framework, here we will use low-level SICL API exported by appropriate dynamic link library (a file named sicl32.dll).
First you need to find and explore documentation on SICL API, example applications and header files with functions declarations. You can discover that the simplest, synchronous access to GPIB-based device typically requires only following 5 functions declared in sicl.h (using C headers is preferred because C language has more straightforward and well-known types system comparing with VB):
int iopen (char *addr); int iclose (int id); int iwrite (int id, char *buf, int datalen, int endi, int *actualcnt); int itimeout (int id, int tval); int iread (int id, char *buf, int bufsize, int *reason, int *actualcnt);
Now you are ready to create special INI file with type information required by DMDLLHelper object. Open Script Editor window and run DLL Wizard (File|New...|DLL Wizard). Select DLL file (it can be found in the %SYSTEM% folder), add function names and proceed to function definition page shown at the screenshot:
Two topmost combo boxes at this page allows you to select function and define its result type. Below you can see group of controls named "Parameters". You can edit parameters list and see Delphi prototype in the preview area. When all functions are defined, click OK to generate INI file and place it into the Script Editor.
At the next step we should create HTML device driver. Use HTML Wizard (File|New...|HTML Wizard) to create driver template, then select Help|Wizards|ActiveX Wizard menu item to create HTML code for DMDLLHelper object. Use following code to open and close this object and initialize HP4191A impedancemeter:
sub public_put_IsActive(value) dim actual, ff if value then ' open if ID<>0 then exit sub ' already opened if not APIHelper.Open(GetFileName("sicl.ini")) then MsgBox "API compilation error!" TriggerBtn.disabled=true exit sub end if ID=APIHelper.API.iopen("gpib0," & gpib_adr.innerText) if ID=0 then MsgBox "Error in iopen()!" else stat=APIHelper.API.itimeout(ID, 1000) if stat=0 then stat=APIHelper.API.iwrite(ID, "R0T3EN", 6, 1, actual) ff="FR" & FEdit.value & "EN" stat=APIHelper.API.iwrite(ID, ff, Len(ff), 1, actual) TriggerBtn_onclick end if end if else ' close if ID<>0 then stat=APIHelper.API.iclose(ID) ID=0 end if APIHelper.Close end if TriggerBtn.disabled=(ID=0) end sub
We should send special command to the device in order to trigger measurement cycle:
sub TriggerBtn_onclick dim actual if stat<>0 then exit sub call SetTimeout("ReadData", 1000) stat=APIHelper.API.iwrite(ID, "EX", 2, 1, actual) end sub
Because HP4191A is a relatively slow device, measurement readout is obtained asynchronously by timer (SetTimeout with 1000 ms interval):
sub ReadData dim actual, reason, buf, ba if stat<>0 then exit sub if AutoCB.checked then call SetTimeout("TriggerBtn_onclick", DelaySE.Value) ' === read GPIB buf=" " stat=APIHelper.API.iread(ID, buf, 50, reason, actual) if stat<>0 then exit sub ' === process data ba=Split(buf, ",") ba(0)=Trim(ba(0)) ' corrects parasitic space Display1.innerHTML=Mid(ba(0), 4, 11) Disp1U.innerHTML=Left(ba(0), 3) Display2.innerHTML=Mid(ba(1), 4, 11) Disp2U.innerHTML=Left(ba(1), 3) if not AutoCB.checked then RaiseTriggerEvent end sub
Notice that before iread() call we have to allocate buf string variable whose length is larger than value passed in bufsize parameter. Full source code of this driver (HTML file named hp4191_82357.htm) available in the [SAMPLES\DAQ] folder in the DM2003 installation. As you can conclude, using DMDLLHelper object is not far more complex than standard COM port driver, or specialized CWGPIB control from NI ComponentWorks library (see example driver named k6517_ni488.htm). In addition, you can examine files from [SAMPLES\DLL] folder for more samples related to DMDLLHelper object.