Reading and writing to serial port using FTDI D2XX drivers with Visual Basic 6 - vb6

I am trying to establish a connection and read data from a virtual COM port using VB6, following on from my query here: Baud rate limits in software and serial communication with an external device . I am using an FTDI driver to communicate with an device via a USB VCP.
I am using the FTD2XX library on Visual Basic 6 to display the name and serial number of a device (this already works), set the number of stop bits, set baud rates and the number of data bits. I would like now like to read and write from the serial port and I have some code and questions. My code is below:
Public Class FTDI1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim DeviceIndex As Integer
Dim TempDevString As String
Dim Read_Result As Integer
Dim Read_Count As Integer
' Get serial number of device with index 0
' Allocate space for string variable
TempDevString = Space(16)
FT_Status = FT_GetDeviceString(DeviceIndex, TempDevString, FT_LIST_BY_INDEX Or FT_OPEN_BY_SERIAL_NUMBER)
If FT_Status <> FT_OK Then
Exit Sub
End If
FT_Serial_Number = Microsoft.VisualBasic.Left(TempDevString, InStr(1, TempDevString, vbNullChar) - 1)
' Display serial number on form
TextBox1.Text = FT_Serial_Number
' Get the model of the connected device
TempDevString = Space(64)
FT_Status = FT_GetDeviceString(DeviceIndex, TempDevString, FT_LIST_BY_INDEX Or FT_OPEN_BY_DESCRIPTION)
If FT_Status <> FT_OK Then
Exit Sub
End If
FT_Description = Microsoft.VisualBasic.Left(TempDevString, InStr(1, TempDevString, vbNullChar) - 1)
' Display serial number on form
TextBox2.Text = FT_Description
' Set baud rate of the connected device
' Set Baud Rate
FT_Status = FT_SetBaudRate(FT_Handle, 1000000)
If FT_Status <> FT_OK Then
Debug.Print("Baud rate set")
Exit Sub
End If
' Set the number of stop bits of the recorded device
' Set parameters
FT_Status = FT_SetDataCharacteristics(FT_Handle, FT_DATA_BITS_8, FT_STOP_BITS_2, FT_PARITY_NONE)
If FT_Status <> FT_OK Then
Debug.Print("Stop bits, parity and data bits set")
Exit Sub
End If
' Read bytes (not strings)
FT_Status = FT_Read_Bytes(FT_Handle, FT_In_Buffer(16), Read_Count, Read_Result)
If FT_Status <> FT_OK Then
Debug.Print(Read_Result)
Exit Sub
End If
' Display read bytes on form
TextBox3.Text = Read_Result
' Close device
FT_Status = FT_Close(FT_Handle)
If FT_Status <> FT_OK Then
Exit Sub
End If
End Sub
End Class
My questions are as follows:
1) I have set the baud rate, stop bits and number of data bits using the FD2XX library. Once this has been done, is it possible to connect to the serial port directly and send or receive data using functions that are not within the FTDI library? I ask this because I am not sure if the FTD2XX drivers are separate from the VCP and FTDI do not provide documentation on serial communication using a USB VCP.
2) Are there any well documented function libraries/code suggestions that would enable me to read from it? If this requires some form of conversion, please can a well documented function library for this be suggested too?
3) Are there any well documented function libraries for writing unsigned integers to the device I am communicating with via the USB VCP?

By the way, where did the FT_Write_String and FT_Write_Bytes functions presented in the question come from?
In FTDI Code Examples, it is FT_Write, FT_WriteByte, FT_W32_WriteFile.
Visual Basic Examples
D2XX Module
many of the Visual Basic examples posted on this page use a module to interface to the D2XX DLL. To download the unit (D2XX_Module.bas) for Visual Basic 6, click here.
Please note that the code examples below may already contain a module handling the D2XX DLL interface. There may be differences between the current module file and the ones distributed with the examples.
D2XX_Module.bas
Public Declare Function FT_Write Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByVal lpszBuffer As String, ByVal lngBufferSize As Long, ByRef lngBytesWritten As Long) As Long
Public Declare Function FT_WriteByte Lib "FTD2XX.DLL" Alias "FT_Write" (ByVal lngHandle As Long, ByRef lpszBuffer As Any, ByVal lngBufferSize As Long, ByRef lngBytesWritten As Long) As Long
Public Declare Function FT_W32_WriteFile Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByVal lpszBuffer As String, ByVal lngBufferSize As Long, ByRef lngBytesWritten As Long, ByRef lpftOverlapped As lpOverlapped) As Long
Note: However, the ByVal lpszBuffer As String parameter of FT_W32_WriteFile seems to be a mistake of ByRef lpszBuffer As Any.
Example 6
Our thanks go to Bob Freeth for providing this VB6 example of using the FT2232C MPSSE for SPI communication with a MAX187 ADC.
Source code and executable are available for free download. This code is provided "as-is" for illustration purposes only and as such neither FTDI or Bob Freeth provide technical support for this VB6 code.
Visual Basic's String variables are Unicode, so they are not suitable for handling binary data.
Instead of substituting String variables, it is better to store data in byte array variables explicitly and write using FT_WriteByte function.
Based on the above, it will be as follows.
Dim SendData(nnn) As Byte ' nnn is value of send data size - 1
SendData(0) = 121
SendData(1) = xxx
SendData(2) = yyy
.
.
.
FT_Status = FT_WriteByte(FT_Handle, SendData(0), Len(SendData), BytesWritten)

I am not really familiar with VB but I use the FT devices via C and python frequently. So here is what I know:
General comment to avoid missconcepts: VCP stands for Virtual COM port. So this actually enables the system to address the FT devices without the need to use a specific library like the D2XX. Most languages provide some "native" access to com ports. So there is no need to deal with the D2XX at all for regular com port operation. It is mainly meant for alternative operation modes and access to the MPSSE to the best of my knowledge.
1) If you open a port via the D2XX it will be unavailable for other access. If you release it and open it via another way (e.g. MSComm or IO.Ports.SerialPort in case auf .net) the settings will be overwritten (or at least should be automatically).
2) I am afraid that only the example projects by FT are your best bet. But maybe someone else can point out a better approach.
3) typically the native access (of C and python) allow you write and read plain byte strings. So the only thing you have to do is "transform" it in the correct type. ctype / CByte / CInt seems to be your cue.

Related

How to find correct values for HWND window handles?

WinRestore,% hwnd([1])
i have found in many programming language the use of hwnd. after searching on google it comes out to be handle. I didnt got more information on this. how programmer knows the value to put in, eg.
Const LB_GETTEXTLEN = &H18A
Const LB_GETTEXT = &H189
Const LB_GETCOUNT = &H18B
&h18a how he known, how will he use this?
this is the example program
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Const LB_GETTEXTLEN = &H18A
Const LB_GETTEXT = &H189
Const LB_GETCOUNT = &H18B
Private Function GetListItems(ByVal hList As Long) As Variant
Dim i As Long, nCount As Long, lItemLength As Long
Dim sItem() As String
nCount = SendMessage(hList, LB_GETCOUNT, 0, ByVal 0&)
For i = 0 To nCount - 1
lItemLength = SendMessage(hList, LB_GETTEXTLEN, i, ByVal 0&)
ReDim Preserve sItem(i)
sItem(i) = String(lItemLength, 0)
Call SendMessage(hList, LB_GETTEXT, i, ByVal sItem(i))
Next i
GetListItems = sItem
End Function
there are many such examples in all different languages but concept will be the same. so i want to learn it. what does it mean and how to use it.
another example from ahk
Gui,2:+hwndhwnd
hwnd(2,hwnd)
Those are all window messages that you can find information about on the MSDN Documentation by googling them. See below links:
LB_GETTEXTLEN
LB_GETTEXT
LB_GETCOUNT
You can find them and other related messages by checking the documentation for the native List Box control.
As for the numbers they're hexadecimal numbers which are (usually) mentioned in the documentation. But since these aren't you'll have to google them and check other websites/forums, or find their values on your own by experimenting with them in C or C++ .
In VB hexadecimal numbers are represented by prepending the number with &H, whereas in C, C++, C# or alike they're prepended with 0x.
In a forms editor each window/control has a hwnd property. For windows not created by your forms package you use the API calls FindWindow (easiest but not reliable) or EnumWindows. Also GetForegroundWindow and GetDesktopWindow.
To find out the value of constants you download the C header files as part of the Windows SDK https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk. It also has the documentation for all these API calls. This is online documentation listing all the windows' functions https://msdn.microsoft.com/en-us/library/windows/desktop/ms633505(v=vs.85).aspx.

Why is the result of VarPtr(ByVal str) the same as StrPtr(str) ? (VB6)

In VB6 VarPtr should return the address of the variable, in this case the address of the variable str which is allocated on stack, and holds a pointer to a string in memory. StrPtr (or StrPtr) should return the address of the allocated string in the memory. ByVal should just create a copy, but in this case, it works strangely:
Dim str As String
str = "asd"
Debug.Print VarPtr(str)
Debug.Print VarPtr(ByVal str)
Debug.Print StrPtr(str)
The result is:
1636452
110882980
110882980
Why is the result of VarPtr(ByVal str) the same as StrPtr(str) ?
Strings passed ByVal pass the address of the first character of the containing C string in the BStr. StrPtr does the same.
There are two reasons that spring to mind for doing this. Passing Unicode to API calls and string building.
Passing Unicode to API calls
You could use StrPtr on a string rather than a byte array when sending Unicode strings to API functions.
Dim ByteArr() as Byte
Var1="My Text"
ByteArr = Var1
APICall(ByteArr(0))
APICall(StrPtr(Var1))
Should both pass a Unicode string to an API functions. Unicode strings are converted to ANSI strings when using the declare statement as Win 95 didn't do unicode.
String Building
On the other hand if you are string building, then that is built in to VBA using the Left, Right, and Mid statements, not functions (they are overloaded).
Sub Main()
Dim Var As String
Var = "gggggggggggg"
MsgBox StrPtr(Var)
Mid(Var, 1, 2) = "xx"
MsgBox StrPtr(Var) & " - " & Var
End Sub
ByVal Versus ByRef
Some authors like to say that the ByVal keyword is overloaded for
strings, meaning that it takes on a different meaning when applied to
strings than when applied to other variables. Frankly, I don't see it.
Writing:
ByVal str As String
tells VB to pass the contents of the BSTR (actually the ABSTR), which is the pointer to the character array. Thus, ByVal is acting
normally--it just happens that the content of the BSTR is a pointer to
another object, so this simulates a pass by reference. Similarly:
ByRef str As String
passes the address of the BSTR, as expected.
Win32 API Programming with Visual Basic, Chapter 6 Strings, O'Reilly, from MSDN Library October 2001
StrPtr
Strings in Visual Basic are stored as BSTR's. If you use the VarPtr on
a variable of type String, you will get the address of the BSTR, which
is a pointer to a pointer of the string. To get the address of the
string buffer itself, you need to use the StrPtr function. This
function returns the address of the first character of the string.
Take into account that Strings are stored as UNICODE in Visual Basic.
To get the address of the first character of a String, pass the String
variable to the StrPtr function.
Example:
Dim lngCharAddress as Long
Dim strMyVariable as String
strMyVariable = "Some String"
lngCharAddress = StrPtr(strMyVariable)
You can use this function when you need to pass a pointer to a
UNIOCODE string to an API call.
HOWTO: Get the Address of Variables in Visual Basic Q199824 Microsoft Knowledge Base, MSDN October 2001.
VarPtr is not part of the VBA/VB6 language, therefore companies that implement VBA (like Corel) may not implement it in their VBA. The VBA spec is here https://msdn.microsoft.com/en-us/library/dd361851.aspx

How to represent 64-bit integer in VB6?

I am having to augment a legacy app to handle 64-bit integers. However, VB6 doesn't have a data type for that. The recommendation online that I've found has been to use the Currency data type.
However, I've found that I am running into some overflow issues.
Example - Results in Overflow during CCur call:
dim c as currency
' set maximum value of int64
c = CCur("9223372036854775807")
However, if I apply a smaller number (but still much larger than int32), it does work:
dim c as currency
' Remove the last 4 digits
c = CCur("922337203685477")
So what am I missing here? How can I handle a 64-bit value?
The only thing that I need to do with the 64-bit values is to read them from a SQL Server stored procedure (it comes as sql type bigint) and then display it to the form.
ADO Field.Value is type Variant. When you retrieve an adBigInt in VB6 the Variant will be of subtype Decimal.
You can use Variant datatype with CDec() conversion.
dim c as variant
' set maximum value of int64
c = CDec("9223372036854775807")
Now you can even use standard vb6 math operations, or string conversion functions on c.
Dim c As Variant, d As Variant
c = CDec("9223372036854775807")
Dim i As Integer
i = 1000
d = 10
Debug.Print c + i
Debug.Print c / d
Debug.Print CStr(c)
Results
9223372036854776807
922337203685477580,7
9223372036854775807
Just be aware that Decimal type Variant is wider than 64 bits, so you don't get the 'Overflow' on the server side :)
The answer is, it depends on what you're going to do with the 64 bit value. If you simply want to hold a value without doing any arithmetic on it, then it may be better to create a byte array or long array. For example:
Dim SixtFourBit(7) As Byte
or
Dim SixtyFourBit(1) As Long
Using the currency type is a simpler solution since you can apply arithmetic to it. But the Currency type is a fixed format representation, always having four decimal places. That means the lower bytes of the 64 bit representation go to make up the fractional part of the Currency value (sort of).
To coerce between Currency and arrays use the devilish CopyMemory windows API function:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Integer)
Sub SomeFunction()
Dim AnArray(7) As Byte
Dim ACurrency as Currency
ACurrency = 123.4567
CopyMemory AnArray(0), VarPtr(ACurrency), 8&
' Inspecting AnArray in the watch window will show you the byte representation of ACurrency
End Sub
With the caveat, that this sort of trickery is to be generally avoided. Incorrect use of CopyMemory can kill your program.
VB6 can be used with variant type of I8 to provide a 64-bit signed integer. (UI8 does not work with VB6). There are some limitations, for example TypeName does not work, whilst VarType does.
Example of function cInt64 to create a 64-bit integer variant which first creates a decimal variant then converts this to an I8 variant:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Enum VARENUM
VT_I8 = &H14
End Enum ' } VARENUM;
Private Type Dec_Hdr
DecType As Integer
DecScale As Byte
DecSign As Byte
End Type
Function cInt64(v As Variant) As Variant
Dim DecHdr As Dec_Hdr
cInt64 = CDec(v) ' first create a Decimal Variant
CopyMemory DecHdr, ByVal VarPtr(cInt64), Len(DecHdr)
'Correct possible CDec conversion errors with Hex strings
If VarType(v) = vbString Then
If InStr(1, v, "&h", vbTextCompare) = 1 Then DecHdr.DecSign = 0
End If
'Remove any decimal places
If DecHdr.DecScale Then cInt64 = Fix(cInt64)
'Convert Decimal to Int64, setting sign and scale to zero
CopyMemory ByVal VarPtr(cInt64), CLng(VT_I8), 4
'Adjust for Decimal Sign
If (DecHdr.DecSign <> 0) And (cInt64 > 0) Then cInt64 = -cInt64
'Finally check variant is of type I8
If (VarType(cInt64) <> VT_I8) Then Err.Raise 6
End Function

Is there a way to erase string completely in VB6?

I want to erase confidential string like password, creditcard number from memory.
In C#, there is a class named SecureString.
But In VB6, I didn't find any solution.
Is there a way to erase string completely from memory?
A SecureString is encrypted while at rest which is more than just providing a way to prevent it remaining in memory.
Is there a way to erase string completely from memory?
Yes, you need to modify the string in-situ and overwrite its contents.
You can do this using mid$() in LHS mode:
Dim i As Long
For i = 1 To Len(secret)
Mid$(secret, i, 1) = "0"
Next
Or with the ZeroMemory or CopyMemory API:
ZeroMemory ByVal StrPtr(secret), LenB(secret)
...
CopyMemory ByVal StrPtr(secret), ByVal StrPtr(String$(Len(secret), "0")), LenB(secret)
For encryption you could implement the DPAPI CryptProtectData API (which is what SecureString is based on).

VB6, File Doesn't Exist, How do I handle Gracefully?

I am testing an application that checks if a file exists across a network. In my testing, I am purposefully pulling the network plug so the file will not be found. The problem is this causes my app to go unresponsive for at least 15 seconds. I have used both the FileExists() and GetAttr() functions in VB6. Does anyone know how to fix this problem? (No, I can't stop using VB6)
Thanks,
Charlie
Unfortunately, VB doesn't make this easy, but luckily the Win32 API does, and it's quite simple to call Win32 functions from within VB.
For the LAN/WAN, you can use a combination of the following Win32 API calls to tell you whether the remote connection exists without having to deal with a network time-out:
Private Declare Function WNetGetConnection Lib "mpr.dll" Alias _
"WNetGetConnectionA" (ByVal lpszLocalName As String, _
ByVal lpszRemoteName As String, ByRef cbRemoteName As Long) As Long
Private Declare Function PathIsNetworkPath Lib "shlwapi.dll" Alias _
"PathIsNetworkPathA" (ByVal pszPath As String) As Long
Private Declare Function PathIsUNC Lib "shlwapi.dll" Alias "PathIsUNCA" _
(ByVal pszPath As String) As Long
For the Internet, you can use the Win32 API call:
Private Declare Function InternetGetConnectedState Lib "wininet.dll" _
(ByRef lpdwFlags As Long, ByVal dwReserved As Long) As Long
Const INTERNET_CONNECTION_MODEM = 1
Const INTERNET_CONNECTION_LAN = 2
Const INTERNET_CONNECTION_PROXY = 4
Const INTERNET_CONNECTION_MODEM_BUSY = 8
This VB site has more discussion on path oriented functions you can call in the Win32 API through VB.
use this too
Dim FlSize as long
flsize=filelen("yourfilepath")
if err.number=53 then msgbox("file not found")
if err.number=78 then msgbox("Path Does no Exist")
I'm not sure you can handle this much more gracefully - if the network is having problems it can take a while for timeouts to indicate that the problem is severe enough that things aren't working.
If VB6 supports threading (I honestly don't recall) you could spin the file open into a background thread, and have the UI allow the user to cancel it (or perform other operations if that makes sense), but that introduces a pretty significant amount of additional complexity.
VB6 has some networking functions that can test to see if the network is connected. You should be able to add in under 'References' the 'NetCon 1.0 Type Library'. This adds for you the NETCONLib. Once implemented, you should be able to test for network connectivity first, then test for the FileExists and GetAttr.
Let me know if this helps!
VB is inherently single threaded, but you can divert work to a COM component to do an asynchronous file check and flag an event when it is done. This way the UI thread stays at responsive at least. Trouble is - this is all theory, I don't know such a component.
But wait! Google just turned up this: Visual Basic 6 Asynchronous File I/O Using the .NET Framework. Does that help, maybe?
Also, they have something similar over at CodeProject: Asynchronous processing - Basics and a walkthrough with VB6/ VB.NET
this code only used for check connection (maybe can help you) for one of your problems :
Private Declare Function InternetGetConnectedState Lib "wininet.dll" (ByRef dwFlags As Long, ByVal dwReserved As Long) As Long
Private Const CONNECT_LAN As Long = &H2
Private Const CONNECT_MODEM As Long = &H1
Private Const CONNECT_PROXY As Long = &H4
Private Const CONNECT_OFFLINE As Long = &H20
Private Const CONNECT_CONFIGURED As Long = &H40
Public Function checknet() As Boolean
Dim Msg As String
If IsWebConnected(Msg) Then
checknet = True
Else
If (Msg = "LAN") Or (Msg = "Offline") Or (Msg = "Configured") Or (Msg = "Proxy") Then
checknet = False
End If
End If
End Function
Private Function IsWebConnected(Optional ByRef ConnType As String) As Boolean
Dim dwFlags As Long
Dim WebTest As Boolean
ConnType = ""
WebTest = InternetGetConnectedState(dwFlags, 0&)
Select Case WebTest
Case dwFlags And CONNECT_LAN: ConnType = "LAN"
Case dwFlags And CONNECT_MODEM: ConnType = "Modem"
Case dwFlags And CONNECT_PROXY: ConnType = "Proxy"
Case dwFlags And CONNECT_OFFLINE: ConnType = "Offline"
Case dwFlags And CONNECT_CONFIGURED: ConnType = "Configured"
Case dwFlags And CONNECT_RAS: ConnType = "Remote"
End Select
IsWebConnected = WebTest
End Function
in your event :
If checknet = False Then
...
else
...
end if
I agree with Will. Something like this is simple to handle with Script.FileSystemObject:
Dim objFSO As New FileSystemObject
If objFSO.FileExists("C:\path\to\your_file.txt") Then
' Do some stuff with the file
Else
' File isn't here...be nice to the user.
EndIf
Accessing files over a network can cause these hangs.
It's been a while, but I remember multi-threading in VB6 being relatively painful to implement. A quick solution would be to have a small .exe (perhaps also coded in VB) that can handle this. You could use DDE for inter-app communication or the ever so easy but kludgey file-based pipe, by which I mean a file that both apps will mutually read/write to handle inter-app communication. Of course, using file-based pipes, depending on the details of this scenario, may simply exaggerate the File I/O lag.
If there's a reasonable degree with which you can predict where the user will be selecting files from, you may consider preemptively caching a directory listing and reading that rather than the file directly - assuming the directory contents aren't expected to change frequently. Note: getting a directory listing over a network will cause the same lag issues as individual file I/O over a network. Keep that in mind.

Resources