Inkey$, CVI(), and CHR$(0)

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
coderJeff
Site Admin
Posts: 4351
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Inkey$, CVI(), and CHR$(0)

Post by coderJeff »

If you've coded in QB before coding in FB then you will have undoubtedly come across a variant of this trick before:

Code: Select all

KeyCode% = CVI(Inkey$ + Chr$(0) + Chr$(0))
In QB, this expression returns zero (0) if no key was pressed, an ascii value (1-255) if a normal key was pressed, and a value over 255 if an extended key was pressed. We do this so we can check for key presses using an integer constant. It makes the code more readable and we don't have to do all those string comparisons. If we are doing a lot of key processing and checking for many cases, working with integers instead of strings can speed things up. This method uses functions native to QB and therefore does not rely on including a library to get this performance boost.

For example, instead of

Code: Select all

k$ = Inkey$
If k$ = Chr$(0) + "M" Then
	Print "Right Arrow Presed"
End If
We could use,

Code: Select all

Const ESCAPE = 27
Const LEFTARROW = &H4B00
Const RIGHTARROW = &H4D00

Do
	KeyCode% = CVI(Inkey$ + Chr$(0) + Chr$(0))
	Select Case KeyCode%
	Case ESCAPE
		Exit Do
	Case LEFTARROW
		Print "Left Arrow Pressed"
	Case RIGHTARROW
		Print "Right Arrow Pressed"
	Case 32 to 127
		Print "'" + Chr$(KeyCode%) + "' Presed"
	Case <> 0
		Print "Other Key Pressed : "; Str$(KeyCode%)
	End Select
Loop
End
However, if we try to run this in FB, it won't work. The problems are caused by what we get from Inkey$ and what we are passing to CVI().

Before we figure out why this won't work in FB, let's look at CHR$(0)

EDIT: The behaviour described here regarding Print and chr$(0) has been fixed in fbc 0.15b win32 and you can skip this section if you like.

OLD_STUFF

Even though the FB docs and many posts state that FB does not allow null terminators (CHR$(0)) in strings, this would seem only partially correct. If we try:

Code: Select all

a = "ABCD" + Chr$(0) + "EFGH"
Print a
in FB we get the output of "ABCD" only. In QB we get the whole string. QB 'Prints' the null character and continuesto the end of the string. I see this as one of those things that are fixed/different in FB.

Now if we actually look at what's in the string variable a, we will see *all* the bytes:

Code: Select all

For i = 1 to Len(a)
	Print Asc(Mid$(a, i, 1))
Next i
We will get a list of the all the bytes in 'a' including the null. The Len(a) function correctly returns 9. This makes sense since there are times when we want to use a string as a byte buffer and maybe we are reading blocks of data from a file. If we use the Get/Put file functions on a file opened in Binary mode, we get the expected results even if the file contains null characters. (i.e. same as QB)

END OLD_STUFF

So why doesn't our KeyCode% expression work in FB?

First thing is that Inkey$ returns a Chr$(255) for the extended character in FB instead of a Chr$(0) as in QB. We can fix this problem by changing our constants.

Code: Select all

Const ESCAPE = 27
Const LEFTARROW = &H4BFF
Const RIGHTARROW = &H4DFF
The second thing is that the CVI function in FB returns a 32 bit integer and therefore expects a string that is *exactly* 4 bytes long. Pass CVI() anything else and we get zero (0) on the return. In QB, the CVI() function returns a 16 bit integer and it only used the first two bytes even if the string was longer.

We can fix that problem in FB with:

Code: Select all

Dim KeyCode as Integer
KeyCode = CVI(Left$(Inkey$ + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0), 4)
So with just a couple of modifications we have something that is similar to our QB original with minimal changes.

Processor Compatibility

But wait, if we read the docs, we will see that CVI() should only be used with strings returned from MKI$(). CVI() and MKS$() will return different results depending on the endianness of our processor. The above code will work fine on little endian (intel type) processors but will not on big endian processors. (looking at the hCV() and fb_CVI() functions in libfb_str_cvmk.c of the run-time library confirm this behaviour)

Now, I could just quit here and say, well it works on my system, so to heck with anyone not using intel type processors. But that would not be a Good thing.

Actually, the fix is quite simple:

Code: Select all

Dim KeyCode as Integer, k as String
k = Inkey$
KeyCode = (ASC(k,2) shl 8) + ASC(k,1)
This will correctly on any type of processor (even middle-endian) and can be easily used with our FB code so far. The problem we have now is that this is no longer a simple expression. We now have an extra variable assignment. We can keep it simple by putting the 'key getting' code in a function. And our simple demo program now becomes:

Code: Select all

Declare Function GetKeyCode () As Integer

Const ESCAPE = 27
Const LEFTARROW = &H4BFF
Const RIGHTARROW = &H4DFF

Dim KeyCode AS INTEGER

Do
	KeyCode = GetKeyCode()
	Select Case KeyCode
	Case ESCAPE
		EXIT DO
	Case LEFTARROW
		PRINT "Left Arrow Pressed"
	Case RIGHTARROW
		PRINT "Right Arrow Pressed"
	Case 32 TO 127
		PRINT "'" + CHR$(KeyCode) + "' Presed"
	Case IS <> 0
		Print "Other Key Pressed : "; STR$(KeyCode); " = &H"; Hex$(KeyCode)
	End Select
Loop
End

Function GetKeyCode () As Integer
	Dim k AS String
	k = InKey$
	GetKeyCode = (Asc(k, 2) Shl 8) + Asc(k, 1)
End Function
I use this method for key processing because it makes for readable code, spends as little time as possible in getting keyboard characters, and the keycode comparisons are all integer. (Faster than string comparisons).

So if we used the CVI(Inkey$...) trick in our old QB code, we can fairly easily move the code to our new FB code. The only draw back is that our new function is not compatible with QB. Personally, I think this is reasonable considering that most likely there are lots of other things I am going to have in my FB code that won't run in QB.

FB/QB Compatibility

Now, if we really wanted to, we could write the GetKeyCode() function so that it is compatible in both FB and QB *and* still be processor indenpendant. But we have to sacrifice some performance. We will keep the new 'FB style' keycode constants and only change the function. The compatible but slower GetKeyCode function would be as follows:

Code: Select all

Function GetKeyCode%
	Dim k AS String, n as Integer, ch as Integer
	k = InKey$
	n = len(k)
	Select Case n
	Case 1
		GetKeyCode% = Asc(k)
	Case 2
		ch = Asc(Mid$(k, 2, 1))
		If ch > 127 Then
			GetKeyCode% = ((ch And &h7f) * 256) Or &H80FF
		Else
			GetKeyCode% = (ch * 256) Or &HFF
		End If
	Case Else
		GetKeyCode% = 0
	End Select
End Function
It's ugly but it is compatible with QB and FB, and handles endianness properly. The function definition has to have the QB style since QB doesn't understand the "Function x() as DataType" syntax. Also, the logic doesn't actually check the extended character byte; it doesn't care if it's Chr$(0) or Chr$(255). It just assumes that if it got 2 bytes from Inkey$ then it must be extended. This might cause a problem if some future version of FB uses additional extended character codes. (i.e. in addition to chr$(255)). But it that case we would absolutely need to break compatibility with QB. So even though at the moment it doesn't really matter if our extended keycode constants end in '00' or 'FF', I have decided to use 'FF' for two reasons. 1) if I only want to support FB and not QB I can use my earlier, faster GetKeyCode function, 2) if FB defines new extended keycodes (e.g. buffered mouse button clicks) I can easily add new constants.

And finally ...

If you need to check key states there's MutliKey(). There are also other key getting routines in various libraries which may be suited to your purpose.

Like with most problems there are usually several solutions. I think the CVI(Inkey$...) method I used with QB is still a good method in FB even though a few changes are needed.

Jeff Marshall
Last edited by coderJeff on Nov 07, 2005 1:17, edited 2 times in total.
mjs
Site Admin
Posts: 842
Joined: Jun 16, 2005 19:50
Location: Germany

Re: Inkey$, CVI(), and CHR$(0)

Post by mjs »

coderJeff wrote:Even though the FB docs and many posts state that FB does not allow null terminators (CHR$(0)) in strings, this would seem only partially correct. If we try:

Code: Select all

a = "ABCD" + Chr$(0) + "EFGH"
Print a
in FB we get the output of "ABCD" only. In QB we get the whole string. QB 'Prints' the null character and continuesto the end of the string.
... and so does FB 0.15b.

Example:

Code: Select all

a$ = "ABCD" + Chr$(0) + "EFGH"
Print a$
Output:
ABCD EFGH
The " " between ABCD and EFGH is an ASCII #0 character.

What FB version do you use?

Regards,
Mark
coderJeff
Site Admin
Posts: 4351
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Inkey$, CVI(), and CHR$(0)

Post by coderJeff »

I was using version 0.14b - DOS.

Of course, after installing the FB-v0.15b-oct-22-testing-win32 version I now see the rest of the string with the print command.

Jeff
Jerry Fielden
Posts: 165
Joined: May 27, 2005 14:14
Location: Marshall, Oklahoma, USA
Contact:

Post by Jerry Fielden »

Thanks Jeff,

I used to use a semilar method in QB. Then when I switched to PBdos, I could still use it. Then when I switched to PBCC, it still worked. But when I tried it in FB console screen mode 0, you know the story there.

Thanks again, that's good to know.

Later.........Jerry Fielden
v1ctor
Site Admin
Posts: 3804
Joined: May 27, 2005 8:08
Location: SP / Bra[s]il
Contact:

Post by v1ctor »

Jeff, if you can help improving the current documentation that would be great, i really liked your writing style.

Anybody can edit the wiki (http://www.freebasic.net/wiki), you just have register (using a CapitalizeCapitalized nick). The formatting rules are quite simple, pressing Ctrl+F1..F9 will insert the {{fbdoc tags automatically when editing some page.

Thanks..
Post Reply