Author Topic: Enumerating COM ports under Win32 (includes a brief API tutorial)  (Read 5327 times)

mcalkins

  • Hero Member
  • *****
  • Posts: 1269
    • qbasicmichael.com
    • Email
Enumerating COM ports under Win32 (includes a brief API tutorial)
« on: September 19, 2011, 12:26:55 PM »
By searching Google, you will find that there are numerous ways of enumerating COM ports under Win32, and that they do not always have the same results.

For example, see:
http://www.naughter.com/enumser.html
http://stackoverflow.com/questions/1388871/how-do-i-get-a-list-of-available-serial-ports-in-win32

I have decided to demonstrate QueryDosDeviceA:
http://msdn.microsoft.com/en-us/library/aa365461%28v=VS.85%29.aspx

This is in response to Clippy's post:
http://www.qb64.net/forum/index.php?topic=4491.msg46846#msg46846

Note that I gradually enlarge the buffer until it is large enough. When I am done with it, I shrink it to "". Also, I use REDIM _PRESERVE to create and enlarge the comports array. Each COM port gets 2 strings in that array. The even numbered strings are the DOS names, the odd numbered strings are the current mappings. For each COM port, I leave the null at the end of the DOS name until after the additional call to QueryDosDeviceA to find the mapping, after which I remove the null.

I can not guarantee that this is bug free...

Regards, Michael

P.S. I think I may have put this in the wrong subforum. Perhaps it should have gone in the QB64 samples subforum...

Code: [Select]
'this example uses QueryDosDeviceA to enumerate COM ports.
'public domain, sept 2011, michael calkins
' http://www.qb64.net/forum/index.php?topic=4527.0

DECLARE DYNAMIC LIBRARY "kernel32"
 FUNCTION QueryDosDeviceA~& (BYVAL lpDeviceName AS _UNSIGNED _OFFSET, BYVAL lpTargetPath AS _UNSIGNED _OFFSET, BYVAL ucchMax AS _UNSIGNED LONG)
 FUNCTION GetLastError~& ()
END DECLARE

DIM sizeofbuffer AS _UNSIGNED LONG
DIM buffer AS STRING
DIM i AS _UNSIGNED LONG
DIM x AS _UNSIGNED LONG
DIM n AS _UNSIGNED LONG
sizeofbuffer = 1024
buffer = SPACE$(sizeofbuffer)

DO
 x = 0
 IF QueryDosDeviceA~&(0, _OFFSET(buffer), sizeofbuffer) = 0 THEN
  x = GetLastError~&
  IF x = &H7A THEN
   sizeofbuffer = sizeofbuffer + 1024
   buffer = SPACE$(sizeofbuffer)
  ELSE
   PRINT "Error: 0x"; HEX$(x)
   END
  END IF
 END IF
LOOP WHILE x = &H7A

i = 1
n = 0
DO WHILE ASC(MID$(buffer, i, 1))
 x = INSTR(i, buffer, CHR$(0))
 PRINT MID$(buffer, i, x - i)
 IF MID$(buffer, i, 3) = "COM" THEN
  REDIM _PRESERVE comports(0 TO (n * 2) + 1) AS STRING
  comports(n * 2) = MID$(buffer, i, (x - i) + 1)
  n = n + 1
 END IF
 i = x + 1
LOOP

PRINT
PRINT n; "COM ports:"
IF n THEN
 FOR i = 0 TO n - 1
  DO
   x = 0
   IF QueryDosDeviceA~&(_OFFSET(comports(i * 2)), _OFFSET(buffer), sizeofbuffer) = 0 THEN
    x = GetLastError~&
    IF x = &H7A THEN
     sizeofbuffer = sizeofbuffer + 1024
     buffer = SPACE$(sizeofbuffer)
    ELSE
     PRINT "Error: 0x"; HEX$(x)
     END
    END IF
   END IF
  LOOP WHILE x = &H7A
  comports((i * 2) + 1) = LEFT$(buffer, INSTR(buffer, CHR$(0)) - 1)
  comports(i * 2) = LEFT$(comports(i * 2), LEN(comports(i * 2)) - 1)
  PRINT CHR$(&H22); comports(i * 2); CHR$(&H22); " is mapped to: "; CHR$(&H22); comports((i * 2) + 1); CHR$(&H22)
 NEXT
END IF

buffer = ""

END
« Last Edit: September 22, 2011, 03:43:05 AM by mcalkins »
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

Clippy

  • Hero Member
  • *****
  • Posts: 16446
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Enumerating COM ports under Win32
« Reply #1 on: September 19, 2011, 01:43:25 PM »
If it works right, it should only show the actual COM ports. The code I supplied in the other post says that my XP has all of them. I can't test this as I am still using version .936.

Can that show LPT ports too?
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

mcalkins

  • Hero Member
  • *****
  • Posts: 1269
    • qbasicmichael.com
    • Email
Re: Enumerating COM ports under Win32
« Reply #2 on: September 19, 2011, 04:03:58 PM »
I fixed a bug. I was counting on n - 1 to be -1 if n is 0, but that doesn't work with unsigned integers... :-P

My laptop has no RS232 or Parallel ports. When I run the program, it finds one COM port:

"COM3" is mapped to: "\Device\Winachsf0"

On my computer, COM3 is associated with the built in dial up modem.

The program is checking all the names for any whose first three characters are "COM". When I change that to "LPT", there are no results. On my computer:

"PRN" is mapped to: "\DosDevices\LPT1"

The program prints all the names initially returned, but they are too many to fit on one screen. You can add a SLEEP 1 to slow it down.

Regards,
Michael
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

Clippy

  • Hero Member
  • *****
  • Posts: 16446
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Enumerating COM ports under Win32
« Reply #3 on: September 19, 2011, 04:44:15 PM »
I imagine that the registers are there, but the device isn't. I will wait and see if anybody else tries it to see how it works. Thanks!

Could somebody explain how to determine when to use _OFFSET and when it should be _UNSIGNED? I need to get the concept for the WIKI and I can't seem to relate to this.
« Last Edit: September 21, 2011, 10:53:13 AM by Clippy »
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

mcalkins

  • Hero Member
  • *****
  • Posts: 1269
    • qbasicmichael.com
    • Email
Re: Enumerating COM ports under Win32
« Reply #4 on: September 22, 2011, 02:15:04 AM »
Could somebody explain how to determine when to use _OFFSET and when it should be _UNSIGNED?

First of all, I don't think it really makes much difference whether it is _UNSIGNED or not. Even if you do arithmetic on it, I don't think QB64 cares about integer overflow, so I don't think it makes any difference. I use _UNSIGNED out of preference.

The short answer:

_OFFSET means a pointer. Use it any time that you need to pass or receive a pointer. With the API, types that start with P or LP, and parameters that start with p or lp are generally pointers.

The long answer:

As for when to use the _OFFSET data type, it is basically any time that you need to pass a pointer to a function as a parameter, or have a pointer returned from a function as a return value. The _OFFSET type is basically the same as a void pointer in C. A pointer is a variable that holds a memory address. (I sometimes refer to the memory address itself as being a pointer, but usually, "pointer" means a variable that holds a memory address.) The symbol for a pointer in C is the asterisk, *, following some other type name. So for example:

void *

means a void pointer, that is, a pointer to a variable of any type.

Also, for example:

CHAR *

means a pointer to a CHAR.

So, you would look at the function prototype in the MSDN article, and match the data types of all of the parameters and the return value to QB64 data types. _OFFSET is the type to use for any kind of pointer. MSDN has a list of data types:

http://msdn.microsoft.com/en-us/library/aa383751%28v=vs.85%29.aspx

You can look up the types in that article. Note that many types are derived from other types, so you may have to follow the chain back, looking up each type that the other type was derived from, until you get back to a standard type like int or void*. Deriving types in C is accomplished with typedef. (Just as a note: the current version of that article on the web seems to have a problem, where the #ifdef UNICODE blocks aren't displaying properly, at least in my browser. I am going by an older version of that article that I saved.)

For example, let's look at the QueryDosDevice function that is used above.

http://msdn.microsoft.com/en-us/library/aa365461%28v=VS.85%29.aspx

Quote
DWORD WINAPI QueryDosDevice(
  __in_opt  LPCTSTR lpDeviceName,
  __out     LPTSTR lpTargetPath,
  __in      DWORD ucchMax
);

So, the function returns a DWORD, and accepts 3 parameters: an LPCTSTR, an LPTSTR, and a DWORD.

For the return type:

If the return type were void, it would be a SUB. Any return type other than void means a FUNCTION. Look up "DWORD" in the data type list. (The URL is given above.)

Quote
A 32-bit unsigned integer. The range is 0 through 4294967295 decimal

This type is declared in WinDef.h as follows:

typedef unsigned long DWORD;

Obviously, the QB64 type that corresponds to this is _UNSIGNED LONG.

Quote
QueryDosDeviceW (Unicode) and QueryDosDeviceA (ANSI)

In this instance, I chose to use the ASCII version of the function (QueryDosDeviceA) instead of the Unicode version (QueryDosDeviceW) for simplicity. (A stands for ANSI, W stands for wide, as in wide char.)

So, we have:

FUNCTION QueryDosDeviceA~& (

For the first parameter:

Look up LPCTSTR in the data type list:

Quote
An LPCWSTR if UNICODE is defined, an LPCSTR otherwise. For more information, see Windows Data Types for Strings.

This type is declared in WinNT.h as follows:

#ifdef UNICODE
 typedef LPCWSTR LPCTSTR;
#else
 typedef LPCSTR LPCTSTR;
#endif

As noted above, I have chosen to use the ASCII version of the function, so it corresponds to LPCSTR. Look that up:

Quote
A pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts.

This type is declared in WinNT.h as follows:

typedef __nullterminated CONST CHAR *LPCSTR;

This tells us that it is a pointer to a string. Since it is a pointer, the QB64 type it corresponds to is _OFFSET.

By the way, all parameters, including pointers, will be passed BYVAL. So, we have:

FUNCTION QueryDosDeviceA~& (BYVAL lpDeviceName AS _UNSIGNED _OFFSET,

For the second parameter:

Look up LPTSTR in the data type list:

Quote
LPTSTR   

An LPWSTR if UNICODE is defined, an LPSTR otherwise. For more information, see Windows Data Types for Strings.

This type is declared in WinNT.h as follows:

#ifdef UNICODE
 typedef LPWSTR LPTSTR;
#else
 typedef LPSTR LPTSTR;
#endif

Again, I decided not to use Unicode this time, so look up LPSTR:

Quote
A pointer to a null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts.

This type is declared in WinNT.h as follows:

typedef CHAR *LPSTR;

Again, it is a pointer to a string. Again, pointers correspond to _OFFSET.

FUNCTION QueryDosDeviceA~& (BYVAL lpDeviceName AS _UNSIGNED _OFFSET, BYVAL lpTargetPath AS _UNSIGNED _OFFSET,

For the third parameter:

This is a DWORD. We have already looked up DWORD, and we know it is an _UNSIGNED LONG. We have finished our function declaration:

FUNCTION QueryDosDeviceA~& (BYVAL lpDeviceName AS _UNSIGNED _OFFSET, BYVAL lpTargetPath AS _UNSIGNED _OFFSET, BYVAL ucchMax AS _UNSIGNED LONG)


Based on the above example, it should be trivial to figure out the declaration for GetLastError:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360%28v=vs.85%29.aspx

Quote
DWORD WINAPI GetLastError(void);

The void in the parameter list means that it accepts no parameters. For the return value, we already know that a DWORD is an unsigned long:

FUNCTION GetLastError~& ()


Usage:

Now that I have declared the function, notice how I use it.

The first usage:

IF QueryDosDeviceA~&(0, _OFFSET(buffer), sizeofbuffer) = 0 THEN

The first parameter:

Quote
lpDeviceName [in, optional]

    An MS-DOS device name string specifying the target of the query. The device name cannot have a trailing backslash; for example, use "C:", not "C:\".

    This parameter can be NULL. In that case, the QueryDosDevice function will store a list of all existing MS-DOS device names into the buffer pointed to by lpTargetPath.

lpDeviceName is an optional pointer to a null terminated ASCII string holding the device name. In this case, I want a list of all of the DOS device names, so I give it a null pointer, 0. This is in harmony with: "If lpDeviceName is NULL, the function retrieves a list of all existing MS-DOS device names." This is why it is referred to as optional, because the pointer can be left null, 0.

The second parameter:

Quote
lpTargetPath [out]

    A pointer to a buffer that will receive the result of the query. The function fills this buffer with one or more null-terminated strings. The final null-terminated string is followed by an additional NULL.

    If lpDeviceName is non-NULL, the function retrieves information about the particular MS-DOS device specified by lpDeviceName. The first null-terminated string stored into the buffer is the current mapping for the device. The other null-terminated strings represent undeleted prior mappings for the device.

    If lpDeviceName is NULL, the function retrieves a list of all existing MS-DOS device names. Each null-terminated string stored into the buffer is the name of an existing MS-DOS device, for example, \Device\HarddiskVolume1 or \Device\Floppy0.

lpTargetPath is a pointer to a buffer to receive the data as null terminated ASCII strings. I give it a pointer to a buffer, _OFFSET(buffer).

The third parameter:

Quote
ucchMax [in]

    The maximum number of TCHARs that can be stored into the buffer pointed to by lpTargetPath.

We need to know what a TCHAR is, since it is involved both in ucchMax and in the return value. Look up TCHAR in the data type list:

Quote
A WCHAR if UNICODE is defined, a CHAR otherwise.

This type is declared in WinNT.h as follows:

#ifdef UNICODE
 typedef WCHAR TCHAR;
#else
 typedef char TCHAR;
#endif

Since we are not using Unicode this time, it is a char, a byte of an ASCII string. (Note that if we were using Unicode, a TCHAR would be two bytes, as you would find out by looking up WCHAR. Keep this in mind if you ever use Unicode functions, as buffers will need two bytes per character in that instance.)

ucchMax is an unsigned long specifying how large the buffer is. I give it an unsigned long, sizeofbuffer, containing the size of buffer in TCHARs.

It is very important that you don't report a size larger than the buffer actually is. The function has no other way of knowing how large the buffer is, and could overwrite other data if you specify a size that is too large. (Again, if you ever use a Unicode function, you will need 2 bytes per character, so the number of TCHARs would be LEN(buffer)\2.)

Return value:

The function returns an unsigned long.

Quote
If the function succeeds, the return value is the number of TCHARs stored into the buffer pointed to by lpTargetPath.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

If the buffer is too small, the function fails and the last error code is ERROR_INSUFFICIENT_BUFFER.

So, that is what I test for. If the return value is 0, then I check the error code using GetLastError~&. If the error code is ERROR_INSUFFICIENT_BUFFER, 0x7a, then I try again with a larger buffer.

After a successful call, buffer, the buffer pointed to by lpTargetPath, will contain a list of all of the DOS device names, each null terminated, with an extra null at the end of the list.

The second usage:

IF QueryDosDeviceA~&(_OFFSET(comports(i * 2)), _OFFSET(buffer), sizeofbuffer) = 0 THEN

The difference this time is in the first parameter. By now I have gone through the list of all device names, and have picked out the ones that start with "COM". I now want to find the current mapping for each of them. lpDeviceName is a pointer to a null terminated ASCII string containing the device name. So I give it _OFFSET(comports(i * 2)), which is exactly that. (As I said in the original post, I leave a terminating null until after this call.)

After a successful call, buffer, the buffer pointed to by lpTargetPath, will contain one or more null terminated strings, with an extra null after the last. I am only interested in the first of these, which is the current mapping.

I hope that this answers the question, and adequately demonstrates how to use the Windows API from QB64. If not, inquire further, and I or someone else will try to answer it. Please point out any mistakes.

Regards,
Michael
« Last Edit: September 22, 2011, 03:17:44 AM by mcalkins »
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

Dav

  • Hero Member
  • *****
  • Posts: 512
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #5 on: September 22, 2011, 04:32:03 AM »
The code works fine for me. 

Very informative explanation post up there!  Helps clear up some things for me. 

I was wondering what the ~ in the FUNCTION names does?  Like in GetLastError~&.  I haven't used it before.  Just to test, I tried the functions without them and they still worked. I'm not too experienced in WinAPI so if thats the right way to call them I'd like to know.

Thanks for the post, Michael.

- Dav
(Visit Dav's Qbasic Site) (Grab my IDE)

mcalkins

  • Hero Member
  • *****
  • Posts: 1269
    • qbasicmichael.com
    • Email
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #6 on: September 22, 2011, 06:37:36 AM »
I was wondering what the ~ in the FUNCTION names does?  Like in GetLastError~&.  I haven't used it before.  Just to test, I tried the functions without them and they still worked.

It means _UNSIGNED.

http://www.qb64.net/wiki/index.php?title=UNSIGNED

I think, in most cases, it probably won't matter. QB64 doesn't seem to complain about signed/unsigned issues. I guess it's up to your preference. As long as the data type is the correct size, I don't think QB64 will complain. You could probably declare it as a SINGLE, although that would obviously be incorrect.

I'm not very experienced with the API either, but I'm glad that this was helpful.

By the way, the statements I have been making about which QB64 types correspond to which C types applies to the 32 bit platform. It might be different for the 64 bit platform. According to Galleon, QB64 for Windows is always 32 bit, so it's not currently an issue.

P.S. Yes, these posts definitely apply only to Win32. With Win64, for example, a long, and therefore a DWORD, would be 64 bits I think. I have gotten it into my head that dword means 32 bits... :-P




I feel that I should probably mention structures and unions also, as some API functions that I have encountered use them.

A structure is a data structure. It can contain multiple elements of various types, but they are arranged in a specific layout. This closely corresponds to QBASIC's user defined data types.

Sometimes the API will want you to provide a pointer to a structure. You won't find the structures in the list of data types. Instead, the MSDN article should link to another article for the specific structure in question.

A somewhat complicated example that I have encountered is the ReadConsoleInput function. Without going into too much detail, or even providing a working example at this time, I will try to explain some of the structs and unions involved. I might later provide a working example.

ReadConsoleInput:

http://msdn.microsoft.com/en-us/library/ms684961(v=VS.85).aspx

Quote
BOOL WINAPI ReadConsoleInput(
  __in   HANDLE hConsoleInput,
  __out  PINPUT_RECORD lpBuffer,
  __in   DWORD nLength,
  __out  LPDWORD lpNumberOfEventsRead
);

You can use the data type list to find most of these types.

BOOL is an int, so either LONG or _UNSIGNED LONG would work.

HANDLE is a PVOID, which is a void *, so you would use _OFFSET or _UNSIGNED _OFFSET.

You already know that DWORD is an _UNSIGNED LONG.

LPDWORD is a DWORD *, so you would use _OFFSET or _UNSIGNED _OFFSET.

However, you won't find PINPUT_RECORD in the list of data types. Instead:

Quote
lpBuffer [out]

    A pointer to an array of INPUT_RECORD structures that receives the input buffer data.

    The storage for this buffer is allocated from a shared heap for the process that is 64 KB in size. The maximum size of the buffer will depend on heap usage.

So, it's a pointer. We would use _OFFSET or _UNSIGNED _OFFSET. But what is it a pointer to? The article links to a description of the INPUT_RECORD structure:

INPUT_RECORD

http://msdn.microsoft.com/en-us/library/ms683499(v=VS.85).aspx

Quote
typedef struct _INPUT_RECORD {
  WORD  EventType;
  union {
    KEY_EVENT_RECORD          KeyEvent;
    MOUSE_EVENT_RECORD        MouseEvent;
    WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
    MENU_EVENT_RECORD         MenuEvent;
    FOCUS_EVENT_RECORD        FocusEvent;
  } Event;
} INPUT_RECORD;

This is a data structure, very much like a user defined type in QBASIC or QB64.

The first element is a WORD, which is an unsigned short. In QB64, it is an _UNSIGNED INTEGER. This element is named EventType.

The second element is where things get tricky. The second element of INPUT_RECORD is a union.

A union is basically an overlap of data types or structures. It basically allows a choice of multiple (overlapping) data types or structures. The size of the union is the size of its largest member, since all of its elements overlap. The purpose, in this instance, is to allow several different types of structures to be used, depending on which one is needed.

You can see from the article that Event is a union of 5 different structures. The value of EventType, the first element of INPUT_RECORD, will tell you which type, of the 5 possibilities, Event actually represents.

To continue the example, let's look at two of these structures: KEY_EVENT_RECORD and MOUSE_EVENT_RECORD.

KEY_EVENT_RECORD

http://msdn.microsoft.com/en-us/library/ms684166(v=VS.85).aspx

Quote
typedef struct _KEY_EVENT_RECORD {
  BOOL  bKeyDown;
  WORD  wRepeatCount;
  WORD  wVirtualKeyCode;
  WORD  wVirtualScanCode;
  union {
    WCHAR UnicodeChar;
    CHAR  AsciiChar;
  } uChar;
  DWORD dwControlKeyState;
} KEY_EVENT_RECORD;

So, this is itself a structure. You already know what BOOL, WORD, and DWORD are.

uChar is another union. We have both a WCHAR and a CHAR overlapping. WCHAR is a wchar_t, which is an unsigned short (according to winnt.h). You could use an _UNSIGNED INTEGER for this. CHAR is a char, so you could use either an _UNSIGNED _BYTE or a STRING * 1. The purpose of the uChar union is to allow you to use either a unicode or an ASCII character within this structure. The two types, WCHAR and CHAR overlap into the same type, uChar. uChar is 2 bytes, the size of its largest member, WCHAR.

So, the entire KEY_EVENT_RECORD could be mostly represented in QB64 as:

TYPE KEYEVENTRECORD
 bKeyDown AS _UNSIGNED LONG
 wRepeatCount AS _UNSIGNED INTEGER
 wVirtualKeyCode AS _UNSIGNED INTEGER
 wVirtualScanCode AS _UNSIGNED INTEGER
 uChar AS _UNSIGNED INTEGER
 dwControlKeyState AS _UNSIGNED LONG
END TYPE

I'm not sure if QB64 allows underscores in type names, so I took them out.

I don't think QB64 has any direct support for unions, so the above is not an exact equivalent with regard to uChar, but it is close enough, in this instance. If you decided to use the ASCII version instead, you could change:

 uChar AS _UNSIGNED INTEGER

to:

 uChar AS _UNSIGNED _BYTE 'or STRING * 1
 padding AS STRING * 1

MOUSE_EVENT_RECORD

http://msdn.microsoft.com/en-us/library/ms684239(v=VS.85).aspx

Quote
typedef struct _MOUSE_EVENT_RECORD {
  COORD dwMousePosition;
  DWORD dwButtonState;
  DWORD dwControlKeyState;
  DWORD dwEventFlags;
} MOUSE_EVENT_RECORD;

This one is more straight forward, in my opinion.

You already know what DWORD is.

COORD

http://msdn.microsoft.com/en-us/library/ms682119(v=VS.85).aspx

Quote
typedef struct _COORD {
  SHORT X;
  SHORT Y;
} COORD, *PCOORD;

SHORT is a short, so COORD can be represented as:

TYPE COORD
 X AS INTEGER
 Y AS INTEGER
END TYPE

Back to MOUSE_EVENT_RECORD

So the whole MOUSE_EVENT_RECORD can be basically represented as:

TYPE MOUSEEVENTRECORD
 X AS INTEGER
 Y AS INTEGER
 dwButtonState AS _UNSIGNED LONG
 dwControlKeyState AS _UNSIGNED LONG
 dwEventFlags AS _UNSIGNED LONG
END TYPE

Note that X and Y should actually be members of dwMousePosition, but QB64 doesn't seem to directly support nested TYPEs.

Back to INPUT_RECORD

Since QB64 doesn't seem to directly support unions, I think the best that we can do is:

TYPE INPUTRECORD
 EventType AS _UNSIGNED INTEGER
 padding AS STRING * 2
 Event AS STRING * 16
END TYPE

Notice the padding to make Event dword aligned (aligned on a 4 byte boundry). I still don't fully understand C's alignment rules. If you can't otherwise determine correct alignment, trial and error should reveal it. (Play with it until you get it right.)

On the subject of alignment, wincon.h contains:
Quote
#ifdef __GNUC__
/* gcc's alignment is not what win32 expects */
 PACKED
#endif
within the KEY_EVENT_RECORD declaration. I'm not sure what effect that has.

Event needs to be the size of it's largest member. KEY_EVENT_RECORD and MOUSE_EVENT_RECORD are both 16 bytes. The other three members of the union are all smaller. So Event is 16 bytes.

Accessing the elements of Event can be done by using MID$ along with MKL$/MKI$/CHR$/CVL/CVI/ASC, or it can be done with my poked/pokew/pokeb/peekd/peekw/peekb functions in the other thread:

http://www.qb64.net/forum/index.php?topic=4491.0

So, you would DIM a variable of that type, and pass a pointer to it as the lpBuffer parameter to ReadConsoleInput.

DIM Buffer AS INPUTRECORD
trash = ReadConsoleInputW~&(stdin, _OFFSET(Buffer), 1, _OFFSET(trash))

where trash is an _UNSIGNED LONG, and stdin is an _OFFSET or _UNSIGNED _OFFSET containing the standard input handle obtained with GetStdHandle. nLength is 1, to only read 1 event at a time. (Otherwise, Buffer would have to be an array, and lpNumberOfEventsRead would point to something other than "trash".)

An Assembly example of ReadConsoleInputW is here:

http://www.network54.com/Forum/632471/message/1297566746/

I hope to provide a QB64 example soon.

I stayed up all night (again), am am getting tired, so I could easily have made a mistake. As always, please point out mistakes.

Regards,
Michael
« Last Edit: September 22, 2011, 06:39:12 PM by mcalkins »
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

Dav

  • Hero Member
  • *****
  • Posts: 512
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #7 on: September 22, 2011, 07:38:37 AM »
Ahh...now I know. Thanks.  This info makes a good tutorial.  I'll bet many who haven't attempted calling external fuctions in QB64 yet will get thier feet wet after reading this thread.

- Dav
(Visit Dav's Qbasic Site) (Grab my IDE)

Clippy

  • Hero Member
  • *****
  • Posts: 16446
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

mcalkins

  • Hero Member
  • *****
  • Posts: 1269
    • qbasicmichael.com
    • Email
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #9 on: September 22, 2011, 03:24:01 PM »
Thanks, Dav.

Clippy: Thanks for formatting that into a wiki article. It looks good. Thanks.

As I said in my PS above, this info applies specifically to Win32, not to Win64. For example, I think a DWORD on Win64 would be 64 bits instead of 32.

Regards,
Michael
« Last Edit: September 22, 2011, 06:36:46 PM by mcalkins »
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

Clippy

  • Hero Member
  • *****
  • Posts: 16446
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #10 on: September 22, 2011, 04:13:14 PM »
I fixed it. Currently QB64 only uses 32 bit even on a 64 bit PC.
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

mcalkins

  • Hero Member
  • *****
  • Posts: 1269
    • qbasicmichael.com
    • Email
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #11 on: September 22, 2011, 06:33:21 PM »
I think I may have been worrying about nothing. The data type list links to:

http://go.microsoft.com/FWLink/?LinkId=83930

which links to:

http://msdn.microsoft.com/en-us/library/cc953fe1.aspx

Quote
long, unsigned long
4 bytes

So I guess DWORD is always 32 bits. (that's a relief; I like consistency.)

Regards,
Michael
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

Dav

  • Hero Member
  • *****
  • Posts: 512
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #12 on: September 23, 2011, 06:05:22 AM »
Great idea putting this in the wiki.  Looks good too.

- Dav
(Visit Dav's Qbasic Site) (Grab my IDE)

mcalkins

  • Hero Member
  • *****
  • Posts: 1269
    • qbasicmichael.com
    • Email
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #13 on: September 24, 2011, 02:51:14 AM »
I include this post in this thread instead of starting a new thread, because this thread has become more about API usage than about COM ports, and this post provides an example to one of my previous posts.

My Assembly example of ReadConsoleW is in this thread:

http://www.network54.com/Forum/632471/message/1297132010/

I had already given the URL to the post itself, but the thread contains context if anyone is interested.

Anyway, here is a rough port of it to QB64, with some modification. The most substantial difference is that I've added an ASCII to Unicode function. By performing the a2u translation each time that I need to print a string, I am trading performance for convenience. (I am crazy for not just using WriteConsoleA instead. By the way, I am not aware of any reason why WriteConsoleA and WriteConsoleW couldn't both be used in the same program, as long as you're careful.)

When you run the program, the console window becomes active after 1 second. Try moving and clicking the mouse inside of it, and try pressing keys. Notice that pressing, holding, and releasing keys all cause events. Press ESC, then switch to the graphical window and press any key to terminate.

I'm using a number of CONSTs to represent the offsets of the elements of KEY_EVENT_RECORD and MOUSE_EVENT_RECORD. I could have used my peekw/peekd functions, but I chose to use CVI/CVL and MID$ instead. As is generally the case with programming, there are usually more ways than one to do something, and just because I choose one way doesn't mean that it is the only or best way.

I can't rule out bugs in either the Asm original, or this port. I've barely tested this port.

Regards,
Michael

Code: [Select]
' public domain, sept 2011, michael calkins
' http://www.qb64.net/forum/index.php?topic=4527.msg47200#msg47200

DECLARE DYNAMIC LIBRARY "kernel32"
 FUNCTION AllocConsole~& ()
 FUNCTION GetConsoleWindow~%& ()
 FUNCTION GetStdHandle~%& (BYVAL nStdHandle AS _UNSIGNED LONG)
 FUNCTION WriteConsoleW~& (BYVAL hConsoleOutput AS _UNSIGNED _OFFSET, BYVAL lpBuffer AS _UNSIGNED _OFFSET, BYVAL nNumberOfCharsToWrite AS _UNSIGNED LONG, BYVAL lpNumberOfCharsWritten AS _UNSIGNED _OFFSET, BYVAL lpReserved AS _UNSIGNED _OFFSET)
 FUNCTION ReadConsoleInputW~& (BYVAL hConsoleInput AS _UNSIGNED _OFFSET, BYVAL lpBuffer AS _UNSIGNED _OFFSET, BYVAL nLength AS _UNSIGNED LONG, BYVAL lpNumberOfEventsRead AS _UNSIGNED _OFFSET)
 FUNCTION SetConsoleMode~& (BYVAL hConsoleHandle AS _UNSIGNED _OFFSET, BYVAL dwMode AS _UNSIGNED _OFFSET)
 FUNCTION GetLastError~& ()
END DECLARE

DECLARE DYNAMIC LIBRARY "user32"
 FUNCTION SetForegroundWindow~& (BYVAL hWnd AS _UNSIGNED _OFFSET)
END DECLARE

'ENABLE_EXTENDED_FLAGS OR ENABLE_INSERT_MODE OR ENABLE_MOUSE_INPUT
CONST mode = &HB0

'KEY_EVENT_RECORD
CONST bKeyDown = &H0
CONST wRepeatCount = &H4
CONST wVirtualKeyCode = &H6
CONST wVirtualScanCode = &H8
CONST UnicodeChar = &HA
CONST kdwControlKeyState = &HC 'k to distinguish keyboard

'MOUSE_EVENT_RECORD
CONST X = &H0
CONST Y = &H2
CONST dwButtonState = &H4
CONST mdwControlKeyState = &H8 'm to distinguish mouse
CONST dwEventFlags = &HC

TYPE INPUTRECORD
 EventType AS _UNSIGNED INTEGER
 padding AS INTEGER
 Event AS STRING * 16
END TYPE

DIM SHARED console AS _UNSIGNED _OFFSET
DIM SHARED stdin AS _UNSIGNED _OFFSET
DIM SHARED stdout AS _UNSIGNED _OFFSET
DIM SHARED trash AS _UNSIGNED LONG
DIM SHARED count AS _UNSIGNED LONG
DIM SHARED buffer AS INPUTRECORD

DIM SHARED crlf AS STRING 'carriage return, line feed
crlf = MKL$(&HA000D)
DIM SHARED tb AS STRING 'tab
tb = MKI$(&H9)
DIM SHARED htb AS STRING 'half tab
htb = MKL$(&H200020) + MKL$(&H200020)
DIM SHARED quote AS STRING
quote = MKI$(&H22)

DIM SHARED ulu AS STRING 'unicode lookup table
ulu = "263a263b2665266626632660202225d825cb25d926422640266a266b263c"
ulu = ulu + "25ba25c42195203c00b600a725ac21a82191219321922190221f219425b225bc"
ulu = ulu + "2302"
ulu = ulu + "00c700fc00e900e200e400e000e500e700ea00eb00e800ef00ee00ec00c400c5"
ulu = ulu + "00c900e600c600f400f600f200fb00f900ff00d600dc00a200a300a520a70192"
ulu = ulu + "00e100ed00f300fa00f100d100aa00ba00bf231000ac00bd00bc00a100ab00bb"
ulu = ulu + "259125922593250225242561256225562555256325512557255d255c255b2510"
ulu = ulu + "25142534252c251c2500253c255e255f255a25542569255625602550256c2567"
ulu = ulu + "2568256425652559255825522553256b256a2518250c25882584258c25902580"
ulu = ulu + "03b100df039303c003a303c300b503c403a6039803a903b4221e03c603b52229"
ulu = ulu + "226100b1226522642320232100f7224800b0221900b7221a207f00b225a000a0"

trash = AllocConsole~&
console = GetConsoleWindow~%&
SLEEP 1 'seems to be neccessary to make it the foreground window
trash = SetForegroundWindow~&(console)
stdout = GetStdHandle~%&(&HFFFFFFF5)
stdin = GetStdHandle~%&(&HFFFFFFF6)

prn a2u$("Setting stdin mode to:  ")
prndword mode
prn crlf
IF SetConsoleMode~&(stdin, mode) = 0 THEN
 prn a2u$("SetConsoleMode failed.") + crlf + a2u$("Error: ")
 prndword GetLastError~&
 END
END IF

DO
 prn crlf + a2u$("ESC to exit.") + crlf
 trash = ReadConsoleInputW~&(stdin, _OFFSET(buffer), 1, _OFFSET(count))
 prn a2u$("count: ") + tb + tb + tb
 prndword count
 prn crlf + a2u$("EventType: ") + tb + tb + htb
 prnword buffer.EventType
 prn crlf

 SELECT CASE buffer.EventType
  CASE &H1
   prn a2u$("bKeyDown: ") + tb + tb
   prndword CVL(MID$(buffer.Event, 1 + bKeyDown, 4))
   prn crlf + a2u$("wRepeatCount: ") + tb + tb + htb
   prnword CVI(MID$(buffer.Event, 1 + wRepeatCount, 2))
   prn crlf + a2u$("wVirtualKeyCode: ") + tb + htb
   prnword CVI(MID$(buffer.Event, 1 + wVirtualKeyCode, 2))
   prn tb + quote
   'ENABLE_WRAP_AT_EOL_OUTPUT
   trash = SetConsoleMode~&(stdout, &H2)
   trash = WriteConsoleW~&(stdout, _OFFSET(buffer.Event) + wVirtualKeyCode, 1, _OFFSET(trash), 0)
   'ENABLE_PROCESSED_OUTPUT OR ENABLE_WRAP_AT_EOL_OUTPUT
   trash = SetConsoleMode~&(stdout, &H3)
   prn quote + crlf + a2u$("wVirtualScanCode: ") + tb + htb
   prnword CVI(MID$(buffer.Event, 1 + wVirtualScanCode, 2))
   prn crlf + a2u$("UnicodeChar") + tb + tb + htb
   prnword CVI(MID$(buffer.Event, 1 + UnicodeChar, 2))
   prn tb + quote
   'ENABLE_WRAP_AT_EOL_OUTPUT
   trash = SetConsoleMode~&(stdout, &H2)
   trash = WriteConsoleW~&(stdout, _OFFSET(buffer.Event) + UnicodeChar, 1, _OFFSET(trash), 0)
   'ENABLE_PROCESSED_OUTPUT OR ENABLE_WRAP_AT_EOL_OUTPUT
   trash = SetConsoleMode~&(stdout, &H3)
   prn quote + crlf + a2u$("dwControlKeyState: ") + tb
   prndword CVL(MID$(buffer.Event, 1 + kdwControlKeyState, 4))
   prn crlf
   IF CVI(MID$(buffer.Event, 1 + UnicodeChar, 2)) = &H1B THEN END
  CASE &H2
   prn a2u$("X: ") + tb + tb + tb + htb
   prnword CVI(MID$(buffer.Event, 1 + X, 2))
   prn crlf + a2u$("Y: ") + tb + tb + tb + htb
   prnword CVI(MID$(buffer.Event, 1 + Y, 2))
   prn crlf + a2u$("dwButtonState: ") + tb + tb
   prndword CVL(MID$(buffer.Event, 1 + dwButtonState, 4))
   prn crlf + a2u$("dwControlKeyState: ") + tb
   prndword CVL(MID$(buffer.Event, 1 + mdwControlKeyState, 4))
   prn crlf + a2u$("dwEventFlags: ") + tb + tb
   prndword CVL(MID$(buffer.Event, 1 + dwEventFlags, 4))
   prn crlf
 END SELECT
LOOP

FUNCTION a2u$ (a AS STRING)
DIM i AS _UNSIGNED LONG
DIM n AS _UNSIGNED _BYTE
DIM t AS STRING
FOR i = 1 TO LEN(a)
 n = ASC(MID$(a, i, 1))
 SELECT CASE n
  CASE &H1 TO &H1F: t = t + MKI$(VAL("&h" + MID$(ulu, 1 + (4 * (n - 1)), 4)))
  CASE IS > &H7E: t = t + MKI$(VAL("&h" + MID$(ulu, 1 + (4 * (n - &H60)), 4)))
  CASE ELSE: t = t + MKI$(n)
 END SELECT
NEXT
a2u$ = t
END FUNCTION

SUB prn (a AS STRING)
trash = WriteConsoleW~&(stdout, _OFFSET(a), LEN(a) \ 2, _OFFSET(trash), 0)
END SUB

SUB prndword (a AS _UNSIGNED LONG)
DIM t AS STRING
t = LCASE$(HEX$(a))
t = a2u$("0x" + STRING$(8 - LEN(t), &H30) + t)
trash = WriteConsoleW~&(stdout, _OFFSET(t), LEN(t) \ 2, _OFFSET(trash), 0)
END SUB

SUB prnword (a AS _UNSIGNED INTEGER)
DIM t AS STRING
t = LCASE$(HEX$(a))
t = a2u$("0x" + STRING$(4 - LEN(t), &H30) + t)
trash = WriteConsoleW~&(stdout, _OFFSET(t), LEN(t) \ 2, _OFFSET(trash), 0)
END SUB
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

Clippy

  • Hero Member
  • *****
  • Posts: 16446
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Enumerating COM ports under Win32 (includes a brief API tutorial)
« Reply #14 on: December 18, 2011, 12:20:53 PM »
I finally got around to testing the COM port API and I got 2 COM ports although Windows only shows one in devices:

Quote
"COM 1" is mapped to "\Device\Serial0"

"COM 3" is mapped to "\Device\Winachsf0"

It would be nice if I could get the Base Address 03F8...03FF
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip