This article applies to:
How to Communicate with External Devices via a USB Port
In order for E-Prime to communicate via the USB port, it needs to use software that is specific for each device. Windows provides this software for any device that is recognized as a Mouse, Keyboard, Human Interface Device, Joystick, or Gamepad. PST may also provide this software as drivers for its own devices. For other 3rd party devices, software called a .dll file should be provided by the device manufacturer or written by a professional programmer. The general writing of a .dll is best done by a professional programmer and is beyond the scope of PST's support for E-Prime.
The only information and/or support PST can offer is to direct you to the Declare statement in the E-Prime Command Reference. Essentially, you can bring your code into E-Prime by copying and pasting them into the User Script of your experiment. In doing so, you will likely no longer use E-Prime's commands, but will rather use the specific commands you have imported for port communications.
Such an effort should be placed in the hands of a qualified developer. Although it may be possible to integrate a custom DLL in a number of languages (C, BASIC, .NET, PASCAL, DELPHI, asm), it is strongly encouraged to only use C++ and to not use any managed .NET functions or MFC calls in the custom library unless the overhead to do so is warranted.
The following are items that are required or noted while authoring or calling a custom .dll while initially interfacing with E-Basic:
- All .dll functions require the WINAPI calling convention for proper stack tracing and cleanup. This maps to _stdcall. Also known as PASCAL.
- All .dll functions require extern "C" for C++ declarations.
- All .dll functions require the usage of an external .def file to export their function names. The usage of declspec(dllimport) or declspec(dllexport) is not supported. Libraries built with these call types will either require being rebuilt to properly expose their functions or have a proxy .dll created that exports the functions and hands off the functionality to the binary .dll (only encouraged if the .dll sources are not available or can no longer be compiled).
- All string functions must use the ByVal keyword in the declaration because the E-Basic string data type is actually a handle to the string. Note that in the prototype of the DLL function there is an ANSI and UNICODE function to show the differences in ALIAS calling.
- All strings are sent to the DLL functions as ANSI char * pointers. There buffer size should not be assumed. If overwriting any contents, do not access any memory locations above strlen(*) or an access violation and crash will occur.
- The .dll must be in the "current folder" or on the system PATH, Windows Folder, Windows System folder. Please note that on some versions of windows or configurations, the Windows folder or Windows System folder must be on the PATH environment variable. To aid in making this finding of the .dll easier, one could call the Win32 API call, SetEnvironmentVariable, and append to PATH the location of the custom .dll file. Note that calling SetEnvironmentVariable only changes the variables during the E-Run session and does not affect the entire system. For more info on SetEnvironmentVariable, see the MSDN documentation.
The following are items that are required or noted while authoring or calling a custom .dll while interacting with E-Basic:
- Operations that require DirectX, HWND windows handles, GDI functions, sound functions, or direct memory access are likely to fail.
- Support for any of the E-Basic objects inside of a custom .dll (albeit top level Clock, List, Procedure, TextDisplay, etc or low level driver, DirectX, direct memory, etc) are not available.
- Operations that consume large amount of time (> 500 microseconds) will delay the overall performance timing accuracy of the computer/experiment (although E-Prime will accurately timestamp the events and datafile will show this).
- Operations that access any memory or external devices should not spin endlessly in a loop or poll to prevent performance or timing accuracy.
It is important to note that the .dll has to have been declared in the .def file of the project and cannot use declspec(dllimport) for it to be used. Specifically, when working with professional programmers, the following information would be helpful to pass on:
The DLL is required to have public exported functions that are available by using the Win32 API function call GetProcAddress. DLL functions that use declspec(export) are not supported because of C++ name mangling limitations. One can view the public exported function by using depends.exe or View Dependencies which is included with any recent version of Microsoft Visual Studio.
Notes about calling a custom .dll from E-Basic:
- If the routine returns a value, declare Function, when calling use ( ) around the parameters and must assign to return value i.e. x = MyFunction(a, b, c).
- If routine does not return a value (void), the declare as Sub. Do not use parens around parameters (i.e., MyRoutine a, b, c).
- Do not put Public/Private in front of Declare.
- When passing strings to a function, use ByVal in the parameter declaration. If the string is to be assigned a value in the call, then declare the string being sent to the caller with the appropriate max size e.g. Dim x As String * 1024. Failure to allocate enough memory can crash the system.
- The following maps the C/C++ data types to what the parameters of the Declare statement should have: (items in are optional):
char * = ByVal x As String
short * = [ByRef]x As Integer
long * = [ByRef]x As Long
short = ByVal x As Short
long = ByVal x As Long
NOTE: In C/C++ the "int" data type is compiler dependant. If the function in C/C++ requires an int or int *, then this will equate to a short if compiled in 16 bit, a Long if compile in 32 bit. Use of 64-bit values are not currently supported.
The attached ZIP below contains very generic C++ sources for compiling a .dll and also the experiment file to call it. In addition, a sample is included on how to access a preexisting .dll. These samples are provided as-is.