It will help if, in addition to the port, filename, etc., the coordinates to position the GUI window can passed on the command line. This will let the calling program control its screen location.
If you document it well enough others might write their own code in their preferred language.
For me, some things work best on the command line - port, filename, location, (I like to center all secondary windows on my app's main window.), and any other parameters that might change from one case to another.
Others work best from an INI file - title, icon, etc.
The file extension (zxb/zvm) can be used to determine the mode.
I've completed the refactoring of the downloading functions and have successfully created a DLL containing that functionality. I have also created a simple Window application that uses the DLL functions to download a .zxb file or to use a .zvm file to perform a firmware update. The complete source code (mostly C) for the modified zload.exe, zload.dll and zloadsample.exe are available in a Zip file. The zipfile also contains three Visual C projects, one for building each component.
I implemented both the callback and the "window handle" forms that I previously discussed. The invocation for downloading with callbacks is:
int CALLBACK ZXDownload(const char *file, long port, long flags, ProgFunc progFunc, long progData)
The first parameter is the file to download and the second is the port number (COM1 = 1). The third parameter specifies the operating mode (download only, download with verify, etc.) The fourth and fifth parameters give the progress callback information. The callback function needs to be defined thusly:
WORD CALLBACK progFunc(long recNum, long recCnt, long progData)
The first parameter indicates the ordinal number of the record just sent and the second gives the total number of records to be sent. These values can be used to compute a completion percentage. The third parameter is the value specified by the fifth parameter to ZXDownload(). The use and meaning of this parameter is up to the caller. It might be a pointer to a structure or a pointer to an object or other data that is needed to update the progress indicator. The return value indicates whether or not to continue the downloading process - non-zero means to continue.
The value returned by ZXDownload() is a status code. Zero means the download was successful and various non-zero values indicate error conditions.
There is an alternate form of the downloading function:
int CALLBACK ZXDownloadEx(const char *file, long port, long flags, ProgFunc progFunc, long progData, MesgFunc mesgFunc, long mesgData)
The only difference is the added two parameters that facilitate passing text messages back to the caller. This form is used by zload.exe.
There are similar functions for performing a firmware update. There are also functions for eliciting identification information from the ZX (model, firmware version, etc.) and for performing EEPROM configuration.
The "window handle" form of the download function has the form:
where percent is a value between 0 and 100 inclusively. The semantics of lParam are caller-defined.
I haven't attempted to create a VB app to use these functions but it should be possible. The sample application can be tested by creating a file association for the .zxb extension like this:
Of course, you'll have to change the directory prefix to match the directory where you install this test code. The -c2 indicates that COM2 should be used and %1 is replaced by the target file name. The filename only needs to be quoted if it contains spaces but it may be quoted even if it does not contain spaces. After creating this association, double-clicking a .zxb file will invoke the sample application to download the ZX code and display the progress. The application needs to be closed manually after completion.
int CALLBACK ZXDownloadWnd(const char *file, long port, long flags, HWND hWnd, UINT mesg, long lParam)
If someone can tell me how to write a VB4-32 DECLARE for this function, I'll test it. The CALLBACK in the C declaration worries me as VB4-32 has no provisions for callbacks.
Here is the VB6 "Help" for the declare.
---------------------------
Declaring a DLL Procedure
Even though Visual Basic provides a broad set of predefined declares in the Win32api.txt file, sooner or later you'll want to know how to write them yourself. You might want to access procedures from DLLs written in other languages, for example, or rewrite Visual Basic's predefined declares to fit your own requirements.
To declare a DLL procedure, you add a Declare statement to the Declarations section of the code window. If the procedure returns a value, write the declare as a Function:
Declare Function publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] As Type
If a procedure does not return a value, write the declare as a Sub:
DLL procedures declared in standard modules are public by default and can be called from anywhere in your application. DLL procedures declared in any other type of module are private to that module, and you must identify them as such by preceding the declaration with the Private keyword.
Procedure names are case-sensitive in 32-bit versions of Visual Basic. In previous, 16-bit versions, procedure names were not case-sensitive.
For More Information See "Declare Statement" in the Language Reference.
Specifying the Library
The Lib clause in the Declare statement tells Visual Basic where to find the .dll file that contains the procedure. When you're referencing one of the core Windows libraries (User32, Kernel32, or GDI32), you don't need to include the file name extension:
Declare Function GetTickCount Lib "kernel32" Alias _
"GetTickCount" () As Long
For other DLLs, the Lib clause is a file specification that can include a path:
Declare Function lzCopy Lib "c:\windows\lzexpand.dll" _
(ByVal S As Integer, ByVal D As Integer) As Long
If you do not specify a path for libname, Visual Basic will search for the file in the following order:
Directory containing the .exe file
Current directory
Windows system directory (often but not necessarily \Windows\System)
Windows directory (not necessarily \Windows)
Path environment variable
The following table lists the common operating environment library files.
Dynamic Link Library Description
Advapi32.dll Advanced API services library supporting numerous APIs including many security and Registry calls
Comdlg32.dll Common dialog API library
Gdi32.dll Graphics Device Interface API library
Kernel32.dll Core Windows 32-bit base API support
Lz32.dll 32-bit compression routines
Mpr.dll Multiple Provider Router library
Netapi32.dll 32-bit Network API library
Shell32.dll 32-bit Shell API library
User32.dll Library for user interface routines
Version.dll Version library
Winmm.dll Windows multimedia library
Winspool.drv Print spooler interface that contains the print spooler API calls
Working with Windows API Procedures that Use Strings
When working with Windows API procedures that use strings, you'll need to add an Alias clause to your declare statements to specify the correct character set. Windows API functions that contain strings actually exist in two formats: ANSI and Unicode. In the Windows header files, therefore, you'll get both ANSI and Unicode versions of each function that contains a string.
For example, following are the two C-language descriptions for the SetWindowText function. You'll note that the first description defines the function as SetWindowTextA, where the trailing "A" identifies it as an ANSI function:
Because neither function is actually named "SetWindowText," you need to add an Alias clause to the declare to point to the function you want to reference:
Private Declare Function SetWindowText Lib "user32" _
Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal _
lpString As String) As Long
Note that the string that follows the Alias clause must be the true, case-sensitive name of the procedure.
Important For API functions you use in Visual Basic, you should specify the ANSI version of a function, because Unicode versions are only supported by Windows NT — not Windows 95. Use the Unicode versions only if you can be certain that your applications will be run only on Windows NT-based systems.
Passing Arguments by Value or by Reference
By default, Visual Basic passes all arguments by reference. This means that instead of passing the actual value of the argument, Visual Basic passes a 32-bit address where the value is stored. Although you do not need to include the ByRef keyword in your Declare statements, you may want to do so to document how the data is passed.
Many DLL procedures expect an argument to be passed by value. This means they expect the actual value, instead of its memory location. If you pass an argument by reference to a procedure that expects an argument passed by value, the procedure receives incorrect data and fails to work properly.
To pass an argument by value, place the ByVal keyword in front of the argument declaration in the Declare statement. For example, the InvertRect procedure accepts its first argument by value and its second by reference:
Declare Function InvertRect Lib "user32" Alias _
"InvertRectA" (ByVal hdc As Long, _
lpRect As RECT) As Long
You can also use the ByVal keyword when you call the procedure.
Note When you're looking at DLL procedure documentation that uses C language syntax, remember that C passes all arguments except arrays by value.
String arguments are a special case. Passing a string by value means you are passing the address of the first data byte in the string; passing a string by reference means you are passing the memory address where another address is stored; the second address actually refers to the first data byte of the string. How you determine which approach to use is explained in the topic "Passing Strings to a DLL Procedure" later in this chapter.
Nonstandard Names
Occasionally, a DLL procedure has a name that is not a legal identifier. It might have an invalid character (such as a hyphen), or the name might be the same as a Visual Basic keyword (such as GetObject). When this is the case, use the Alias keyword to specify the illegal procedure name.
For example, some procedures in the operating environment DLLs begin with an underscore character. While you can use an underscore in a Visual Basic identifier, you cannot begin an identifier with an underscore. To use one of these procedures, you first declare the function with a legal name, then use the Alias clause to reference the procedure's real name:
Declare Function lopen Lib "kernel32" Alias "_lopen" _
(ByVal lpPathName As String, ByVal iReadWrite _
As Long) As Long
In this example, lopen becomes the name of the procedure referred to in your Visual Basic procedures. The name _lopen is the name recognized in the DLL.
You can also use the Alias clause to change a procedure name whenever it's convenient. If you do substitute your own names for procedures (such as using WinDir for GetWindowsDirectoryA), make sure that you thoroughly document the changes so that your code can be maintained at a later date.
Using Ordinal Numbers to Identify DLL Procedures
In addition to a name, all DLL procedures can be identified by an ordinal number that specifies the procedure in the DLL. Some DLLs do not include the names of their procedures and require you to use ordinal numbers when declaring the procedures they contain. Using an ordinal number consumes less memory in your finished application and is slightly faster than identifying a procedure in a DLL by name.
Important The ordinal number for a specific API will be different with different operating systems. For example, the ordinal value for GetWindowsDirectory is 432 under Win95, but changes to 338 under Window NT 4.0. In sum, if you expect your applications to be run under different operating systems, don't use ordinal numbers to identify API procedures. This approach can still be useful when used with procedures that are not APIs, or when used in applications that have a very controlled distribution.
To declare a DLL procedure by ordinal number, use the Alias clause with a string containing the number sign character (#) and the ordinal number of the procedure. For example, the ordinal number of the GetWindowsDirectory function has the value 432 in the Windows kernel; you can declare the DLL procedure as follows:
Declare Function GetWindowsDirectory Lib "kernel32" _
Alias "#432" (ByVal lpBuffer As String, _
ByVal nSize As Long) As Long
Notice that you could specify any valid name for the procedure in this case, because Visual Basic is using the ordinal number to find the procedure in the DLL.
To obtain the ordinal number of a procedure you want to declare, you can use a utility application, such as Dumpbin.exe, to examine the .dll file. (Dumpbin.exe is a utility included with Microsoft Visual C++.) By running Dumpbin on a .dll file, you can extract information such as a list of functions contained within the DLL, their ordinal numbers, and other information about the code.
For More Information For more information on running the Dumpbin utility, refer to the Microsoft Visual C++ documentation.
Flexible Argument Types
Some DLL procedures can accept more than one type of data for the same argument. If you need to pass more than one type of data, declare the argument with As Any to remove type restrictions.
For example, the third argument in the following declare (lppt As Any) could be passed as an array of POINT structures, or as a RECT structure, depending upon your needs:
Declare Function MapWindowPoints Lib "user32" Alias _
"MapWindowPoints" (ByVal hwndFrom As Long, _
ByVal hwndTo As Long, lppt As Any, _
ByVal cPoints As Long) As Long
While the As Any clause offers you flexibility, it also adds risk in that it turns off all type checking. Without type checking, you stand a greater chance of calling the procedure with the wrong type, which can result in a variety of problems, including application failure. Be sure to carefully check the types of all arguments when using As Any.
When you remove type restrictions, Visual Basic assumes the argument is passed by reference. Include ByVal in the actual call to the procedure to pass arguments by value. Strings are passed by value so that a pointer to the string is passed, rather than a pointer to a pointer. This is further discussed in the section "Passing Strings to a DLL Procedure."
I've used VB6 for years. Is VB4-32 an older version? I recall that VB6
was the first VB version supporting call-backs - since VB isn't
threaded. I just did a project with VB2005 - it is way too obese.
dkinzer wrote:The "window handle" form of the download function has the form:
int CALLBACK ZXDownloadWnd(const char *file, long port, long
flags, HWND hWnd, UINT mesg, long lParam)
If someone can tell me how to write a VB4-32 DECLARE for this
function, I'll test it.
stevech wrote:I've used VB6 for years. Is VB4-32 an older version? I recall that VB6 was the first VB version supporting call-backs - since VB isn't
threaded. I just did a project with VB2005 - it is way too obese.
VB3 was the first useful version, VB4 came in 16-bit and 32-bit versions, VB5 was the first to include AddressOf for callbacks although there were some expensive third-party toolkits that would allow it with VB4.. VB6 was the last version before MS abandoned it for VB.NET.
Since all I do is create freeware, I could never justfy spending the increasingly large amounts required to upgrade, especially when VB4-32 met all my needs. But I have run into more and more DLLs created for VB5 & VB6 that cannot be called from VB4-32.
I actually prefer your suggested standalone GUI version of the downloader. I think it will be the easiest way to do it from most any language.
Declare Function ZXDownloadWnd Lib "zload.dll" ( _
ByVal file as String, _
ByVal port as Long, _
ByVal flags as Long, _
ByVal hWnd as Long, _
ByVal mesg as Long, _
ByVal lParam as Long) As Long
You'll have to place zload.dll in one of the "usual places", e.g. /windows/system32. It may also work to have zload.dll in the same directory as the VB executable.
#define ZX_SUCCESS 0
#define ZX_PARAMETER_ERROR 1 // invalid parameter value supplied
#define ZX_ERROR_FILE_OPEN 2 // could not open the specified file
#define ZX_INVALID_DOWNLOAD_FILE 3 // download file not in expected format
#define ZX_INVALID_UPDATE_FILE 4 // update file not in expected format or failed integrity check
#define ZX_CANT_OPEN_PORT 5 // attempt to open COM port failed
#define ZX_FAILED_SPEED_CHANGE 6 // attempt to change COM speed failed
#define ZX_NO_ATN_REPSONSE 7 // device failed to respond to ATN
#define ZX_RETRY_LIMIT_EXCEEDED 8 // record transmission failed after multiple retries (update only)
#define ZX_COMM_TIMEOUT 9 // no response from device for record transmission
#define ZX_VERSION_TIMEOUT 10 // no response from device for firmware version check
#define ZX_VERSION_FAILURE 11 // device firmware version too old
#define ZX_RECORD_NAK 12 // device sent negative acknowledgement
#define ZX_BAD_RESPONSE 13 // device sent unexpected response
#define ZX_ABORTED 14 // process aborted due to response from callback
#define ZX_ALLOC_ERROR 15 // memory allocation error
#define ZX_BAD_CONFIG_DATA 16
dhouston wrote:If I can display the percent completion in a small text window (i.e. the one whose hwnd I pass), ...
If your control expects a character string then it won't work. The completion percentage is passed as a numeric value via the wParam parameter. If you can subclass the control, create a new message number for it and then handle the message, you'll be in business. Alternately, if there is a progress bar control that uses the wParam parameter that might be a better choice.
dkinzer wrote:The error codes are in the file zxcomm.h:
What about the flags?
They're in the same file. For downloads, you'll use ZX_DOWNLOAD_WRITE, optionally ORing ZX_DOWNLOAD_VERIFY with it if you want verification. Unless you know for certain that the control always returns a non-zero value in response to the message, you'll want to OR the flag ZX_IGNORE_HWND_RET. The message handler/callback can return zero to abort the download.
However, it might be simpler for those who cannot handle subclassing to just send an ANSI character string to a user specified text control.
True enough, but then it wouldn't work with a progress bar or other control that expects a numeric value. Another flag bit could be employed to select a mode where the message is sent with lParam being a pointer to a null terminated string and wParam being the number of characters in the string.
dkinzer wrote:If you can subclass the control, create a new message number for it and then handle the message, you'll be in business.
I do not understand the term create a new message number for it. I think the message number (i.e. mesg) has to be defined by the sender of the message. I can then hook that message number (once I know it) and capture the data. Or are you saying I need to create a number, pass it in mesg when I call your function, and then hook that message number?