 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
aobrien
Joined: 14 Feb 2008 Posts: 38
|
Posted: Sun Feb 24, 2008 6:46 am Post subject: Need help with DllCall("ReadFile", ... ) and VarSe |
|
|
This code is used to read/write from a serial port. I will post it in the scripts section once it is cleaned up.
The script is basically funcitonal, however, there is some behavior that could cause problems in the future.
All the issues mentioned below occur in the "Read from COM Subroutine" section.
1) No matter what I do I can't get DLL ReadFile to return Data unless there is a non zero pre-fill in the VarSetCapacity. If you change 0x55 to 0x0 or 0x00 or 0 then the variable Data is empty. Can anyone explain why?
2) No matter what I do I can't seem to get Data_Length < 3. Can anyone explain why?
3) When I execute the command DllCall("ReadFile", ...) it sits there waiting FOREVER until it has received the number of bytes (Num_Bytes) that you specified. This one is the biggest problem because it will hang the script. Right-clicking on the tray icon in this condition won't work, so the only way that you can exit the script is the "Task Manager". Does anyone know why it behaves this way? Any ideas on a work-around?
To test this code I have two serial ports on my PC with a cross-voer cable connecting the two. I open one COM port with HyperTerminal and the other with the script. Then when the script is executed it will write UVWXYZ[\ to the HyperTerminal and then wait until the specified number of bytes have been received - to do this you need to type into HyperTerminal.
Thanks.
aobrien
| Code: |
MsgBox, Begin COM Test
;========================================================================
;====== User Variables ==================================================
;========================================================================
COM_Port = COM4
COM_Baud = 115200
COM_Parity = N
COM_Data = 8
COM_Stop = 1
;========================================================================
;====== Build COM DCB ===================================================
;========================================================================
COM_Settings = %COM_Port%:baud=%COM_Baud% parity=%COM_Parity% data=%COM_Data% stop=%COM_Stop% dtr=Off
VarSetCapacity(DCB, 28)
BCD_Result := DllCall("BuildCommDCB"
, "str" , COM_Settings
, "UInt", &DCB)
If (BCD_Result <> 1)
MsgBox, Failed Dll BuildCommDCB, result=%BCD_Result%
;========================================================================
;====== Create COM File =================================================
;========================================================================
COM_FileHandle := DllCall("CreateFile"
,"Str" , COM_Port ;File Name
,"UInt", 0xC0000000 ;Desired Access
,"UInt", 3 ;Safe Mode
,"UInt", 0 ;Security Attributes
,"UInt", 3 ;Creation Disposition
,"UInt", 0 ;Flags And Attributes
,"UInt", 0 ;Template File
,"Cdecl Int")
If (COM_FileHandle < 1)
MsgBox, Failed Dll CreateFile, result=%COM_FileHandle%
;========================================================================
;====== Set COM State ===================================================
;========================================================================
SCS_Result := DllCall("SetCommState"
,"UInt", COM_FileHandle ;File Handle
,"UInt", &DCB) ;Pointer to DCB structure
If (SCS_Result <> 1)
MsgBox, Failed Dll SetCommState, result=%SCS_Result%
;========================================================================
;====== Write to COM File ===============================================
;========================================================================
Test_Param = 0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C
Write_to_COM(Test_Param)
rr := Read_from_COM("0x5")
msgbox, rr=%rr%
;========================================================================
;====== Close COM File ==================================================
;========================================================================
CH_result := DllCall( "CloseHandle", "UInt",COM_FileHandle)
If (CH_result <> 1)
MsgBox, Failed Dll CloseHandle result=%CH_result%
return
;========================================================================
;====== Write to COM Subroutines ========================================
;========================================================================
Write_to_COM(Message)
{
Global COM_FileHandle
Global COM_Port
SetFormat, Integer, HEX
StringSplit, Byte0x, Message, `,
;msgbox, Byte0=%Byte0x0% b1=%Byte0x1% b2=%Byte0x2% b3=%Byte0x3%
Data_Length := VarSetCapacity(Data, Byte0x0, 0xFF)
;msgbox, sl=%Data_Length%
i=0x1
Loop %Byte0x0%
{
NumPut(Byte%i%, Data, (i-1) , "UChar")
;msgbox, %i%
i++
}
;msgbox, string=%Data%
WF_Result := DllCall("WriteFile"
,"UInt" , COM_FileHandle ;File Handle
,"UInt" , &Data ;Pointer to string to send
,"UInt" , Data_Length ;Data Length
,"UInt*", Bytes_Sent ;Returns pointer to num bytes sent
,"Int" , "NULL")
If (WF_Result <> 1 or Bytes_Sent <> Data_Length)
MsgBox, Failed Dll WriteFile to %COM_Port%, result=%WF_Result% `nData Length=%Data_Length% `nBytes_Sent=%Bytes_Sent%
}
;========================================================================
;====== READ from COM Subroutine ========================================
;========================================================================
Read_from_COM(Num_Bytes)
{
Global COM_FileHandle
Global COM_Port
SetFormat, Integer, HEX
;No matter what I do I can't get seem to get Data_Length < 3
;Also, no matter what I do I can't get DLL ReadFile to return a result unless there is a non zero pre-fill in the VarSetCapacity.
Data_Length := VarSetCapacity(Data, Num_Bytes, 0x55)
;msgbox, Data_Length=%Data_Length%
;msgbox, COM_FileHandle=%COM_FileHandle% `nNum_Bytes=%Num_Bytes%
Read_Result := DllCall("ReadFile"
,"UInt" , COM_FileHandle ; hFile
,"UInt" , &Data ; lpBuffer
,"Int" , Num_Bytes ; nNumberOfBytesToRead
,"UInt*", Bytes_Received ; lpNumberOfBytesReceived
,"Int" , 0) ; lpOverlapped
;MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData=%Data%
Return Data
}
|
|
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2737 Location: Australia, Qld
|
Posted: Sun Feb 24, 2008 7:25 am Post subject: Re: Need help with DllCall("ReadFile", ... ) and V |
|
|
| aobrien wrote: | | 1) No matter what I do I can't get DLL ReadFile to return Data unless there is a non zero pre-fill in the VarSetCapacity. If you change 0x55 to 0x0 or 0x00 or 0 then the variable Data is empty. Can anyone explain why? | Each variable maintains a counter for the length of its contents. If you modify the variable indirectly via DllCall and &var, its length is not updated. Either update the length manually with VarSetCapacity(var, -1) or pass the var as "str", var instead of "uint", &var.
| Quote: | | 2) No matter what I do I can't seem to get Data_Length < 3. Can anyone explain why? | Off the top of my head, so to speak:
Dynamic memory allocations typically have a granularity of 16 bytes, with an additional 16 bytes of overhead.
If a variable's first memory allocation is under 64 bytes, it is allocated memory that persists until the script exits. This avoids the overhead of dynamic memory, improving average-case memory usage. The exact amount allocated is 3, 7 or 63 bytes, plus one byte for the variable's null-terminator (which is excluded by VarSetCapacity.)
| Quote: | 3) When I execute the command DllCall("ReadFile", ...) it sits there waiting FOREVER until it has received the number of bytes (Num_Bytes) that you specified. ... Does anyone know why it behaves this way? Any ideas on a work-around?
| That is how ReadFile works by default. | ReadFile wrote: | The ReadFile function returns when one of the following conditions occur:
- A write operation completes on the write end of the pipe.
- The number of bytes requested is read.
- An error occurs.
| Recommended reading: Synchronization and Overlapped Input and Output. |
|
| Back to top |
|
 |
aobrien
Joined: 14 Feb 2008 Posts: 38
|
Posted: Sun Feb 24, 2008 5:45 pm Post subject: |
|
|
Thank you lexiKos! I've only begun to delve into the world of dll's the past couple of weeks.
follow-up to #3... so how do you prevent yourself from reading more bytes than are in the file?
aobrien |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2737 Location: Australia, Qld
|
Posted: Mon Feb 25, 2008 12:46 am Post subject: |
|
|
| You can't read more bytes than are in the file. Bytes_Received will tell you how many bytes were actually read. |
|
| Back to top |
|
 |
aobrien
Joined: 14 Feb 2008 Posts: 38
|
Posted: Mon Feb 25, 2008 5:45 am Post subject: |
|
|
Sorry I didn't write that last reply very clearly... I'll try again.
If the file you are trying to read from has 8 bytes in it and you attempt to read 9 bytes with dllcall("ReadFile",... then your script will hang until the file gets one more byte so that the dllcall can complete. So really what I meant to ask is: how do you determine the size of a file before you use ReadFile?
I searched the forum and found a script by "corrupt" called CMDret. He/she used DllCall("PeekNamedPipe",... to get the file size before executing the ReadFile dll call. I also noticed someone use DllCall(GetFileSize,...
I modified my Read_from_COM subroutine as shown below, but I seem to be having problems getting anything back from PeekNamedPipe and GetFileSize -I tried several different combinations but pnp, bSize, gfs, and pSize always come back 0x0.
Do you see what I am doing wrong or have a different method of reading the number of bytes in a file?
| Code: | Read_from_COM(Num_Bytes)
{
Global COM_FileHandle
Global COM_Port
SetFormat, Integer, HEX
pnp := DllCall("PeekNamedPipe"
,"UInt" , COM_FileHandle ;hNamedPipe
,"UInt" , 0 ;lpBuffer
,"UInt" , 0 ;nBufferSize
,"UInt" , 0 ;lpBytesRead
,"UInt*", bSize ;lpTotalBytesAvail
,"UInt" , 0 ) ;lpBytesLeftThisMessage
msgbox, pnp=%pnp%, bSize=%bSize%
gfs := DllCall("GetFileSize"
,"UInt" , COM_FileHandle ;hFile
,"UInt*" , pSize) ;lpFileSizeHigh
msgbox, gfs=%gfs%, pSize=%pSize%
;No matter what I do I can't get seem to get Data_Length < 3
;Also, no matter what I do I can't get DLL ReadFile to return a result unless there is a non zero pre-fill in the VarSetCapacity.
Data_Length := VarSetCapacity(Data, Num_Bytes, 0)
msgbox, COM_FileHandle=%COM_FileHandle% `nNum_Bytes=%Num_Bytes% `nDL=%Data_Length%
Read_Result := DllCall("ReadFileEx"
,"UInt" , COM_FileHandle ; hFile
,"Str" , Data ; lpBuffer
,"Int" , Num_Bytes ; nNumberOfBytesToRead
,"UInt*", Bytes_Received ; lpNumberOfBytesReceived
,"Int" , 0) ; lpOverlapped
MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData=%Data%
Return Data
} |
[EDIT] I just tried the GetFilesizeEx and can get a return value of 0x1, but pSize seems to always come back as NULL
| Code: | gfs := DllCall("GetFileSizeEx"
,"UInt" , COM_FileHandle ;hFile
,"Str" , pSize) ;lpFileSize
msgbox, gfs=%gfs%, pSize=%pSize% |
|
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2737 Location: Australia, Qld
|
Posted: Mon Feb 25, 2008 10:21 am Post subject: |
|
|
| aobrien wrote: | Sorry I didn't write that last reply very clearly... | You were clear enough, I was just wrong. I've only used ReadFile with pipes, which are the exception. (See my previous "ReadFile" quote.)
PeakNamedPipe is for named pipes, not communications devices (serial ports, etc.) GetFileSize won't work either.
| GetFileSize wrote: | | You cannot use the GetFileSize function with a handle of a nonseeking device such as a pipe or a communications device. |
| Quote: | I just tried the GetFilesizeEx and can get a return value of 0x1, but pSize seems to always come back as NULL
| If pSize isn't initialized beforehand, you would be passing a pointer to an empty string (probably 1 byte.)
The second parameter of GetFileSizeEx should be a pointer to a LARGE_INTEGER (i.e. Int64*.) Even so, I think GetFileSizeEx won't work any more than GetFileSize.
Perhaps this will help?
| ReadFile wrote: | | When reading from a communications device, the behavior of ReadFile is governed by the current communication time-out as set and retrieved by using the SetCommTimeouts and GetCommTimeouts functions. Unpredictable results can occur if you fail to set the time-out values. For more information about communication time-outs, see COMMTIMEOUTS. |
|
|
| Back to top |
|
 |
aobrien
Joined: 14 Feb 2008 Posts: 38
|
Posted: Mon Feb 25, 2008 5:07 pm Post subject: |
|
|
I tried UInt64* and SetVarCapacity(pSize, 16, 0x55) to no avail.
I will experiment with SetCommTimeout in about a week. I'll post the results once I have them.
At the moment, I need to show some progress on this automation and luckily for me the serial communication is predictable, so this ReadFile behavior isn't a show stopper at the moment.
Thanks for all your help, lexiKos.
aobrien |
|
| Back to top |
|
 |
TodWulff
Joined: 29 Dec 2007 Posts: 99
|
Posted: Tue Mar 11, 2008 3:07 am Post subject: |
|
|
Any update aobrien? I am anxiously awaiting your updated code...
Please let us know. Thanks!
-t |
|
| Back to top |
|
 |
aobrien
Joined: 14 Feb 2008 Posts: 38
|
Posted: Fri Mar 21, 2008 4:48 am Post subject: |
|
|
Yes, sorry. It's been in the back of my mind, but I've been up against a deadline which ends Monday 3/24/08 and I never actually solved the ReadFile problem.
For my application, however, I know how many bytes are received and I'm careful not to read too many then I close the file and re-open next time that I need to read/write. It is really crappy way of doing things, and it is slow, but it is 10x better than the port.dll method - except for the ReadFile problem... I just thought of something... If you were really pressed for time and you need to read all the bytes then you could use port.dll for the reads and WriteFile for the writes.
I should have some time next week to try and get the ReadFile working. |
|
| Back to top |
|
 |
TodWulff
Joined: 29 Dec 2007 Posts: 99
|
Posted: Fri Mar 21, 2008 6:11 am Post subject: |
|
|
| aobrien wrote: | | ...If you were really pressed for time and you need to read all the bytes then you could use port.dll for the reads and WriteFile for the writes... |
That's actually a great idea, however, I am not too pressed for time. As I posted in another thread, I too have worked up an app that is 100% working just as expected with port.dll - but mahn, it is gawd awful slow. I did some timing and it's like 30+ times slower than one would expect...
I'd like to work through this (with you, if you please) using native Win32 OS resources, so that all could benefit from it in the future. It seems that there are a lot of queries about it, and then the various solicitors either figure it out and move on without providing a library for #inclusion by others, or simply give up and move on. It would sure be nice to end that, once and for all...
I have experienced success using an activex object via COM, but the tool I am using is a trial, and ultimately, the AHK community needs something that leverages existing native resources... To get a primer on my trials and tribulations which, like you in this thread, were worked through with the help of a couple of salty dawgs - Lexikos and Bourbaki [a.k.a. 'G' ], read this.
Frankly, once I got my arms around some concepts and syntax, the use of COM objects in the AHK environment is pretty darned easy via the use of the COM.ahk library...
At any rate, thanks for taking the time to reply. Looking forward to possibly working with you on this. Good luck with the struggles during 11th hour - been there and done that WAY TOO MANY TIMES...
-t |
|
| Back to top |
|
 |
aobrien
Joined: 14 Feb 2008 Posts: 38
|
Posted: Fri Mar 28, 2008 9:54 pm Post subject: |
|
|
Lexikos, you were right!!! SetCommTimeouts did the trick. ReadFile no longer hangs there waiting for the correct number of bytes. It simply returns whatever is in the COM port buffer.
FYI... I don't know the correct way to set up the COMMTIMEOUTS structure. I created my own COMMTIMEOUTS structure and wrote it to SetCommTimeouts but ReadFile behaves the same regardless if I put all 0's or all 1's in the sturcture. In reality, this is a non-issue because ReadFile is not hanging anymore.
ToddWulff, check the link below. There are two different scripts. The first post uses the port.dll method and the second post uses one MSWindows dll calls. Let me know how you like it.
I placed the finished code here...
http://www.autohotkey.com/forum/viewtopic.php?t=28703 |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2737 Location: Australia, Qld
|
Posted: Sat Mar 29, 2008 1:23 am Post subject: |
|
|
Here is the correct way to fill a COMTIMEOUTS structure:
| Code: | VarSetCapacity(Data, 20, 0) ; 5 * sizeof(DWORD)
NumPut(ReadIntervalTimeout, Data, 0, "UInt")
NumPut(ReadTotalTimeoutMultiplier, Data, 4, "UInt")
NumPut(ReadTotalTimeoutConstant, Data, 8, "UInt")
NumPut(WriteTotalTimeoutMultiplier, Data, 12, "UInt")
NumPut(WriteTotalTimeoutConstant, Data, 16, "UInt")
SCT_result := DllCall("SetCommTimeouts"
,"UInt", COM_FileHandle
,"UInt", &Data) | It is important to note that UInt* is a pointer to a single UInt. AutoHotkey does conversions between a numeric string in the variable and a temporary integer value whose address is passed to the function. A pointer to a structure should be passed as either UInt, &Structure or Str, Structure. Str automatically updates the internal length property of the variable. Since it implies the contents are a string, I prefer the UInt method.
StructParser may be of some help if you ever deal with structures in the future.
| COMMTIMEOUTS wrote: | ReadIntervalTimeout
A value of MAXDWORD, combined with zero values for both the ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies that the read operation is to return immediately with the bytes that have already been received, even if no bytes have been received.
|
| Code: | | MAXDWORD = 0xffffffff |
| Quote: | | In reality, this is a non-issue ... | Passing UInt* could in fact cause an access violation (crash) or stack corruption since it passes a 4-byte value where a 20-byte value is expected. |
|
| Back to top |
|
 |
aobrien
Joined: 14 Feb 2008 Posts: 38
|
Posted: Wed Apr 02, 2008 4:19 pm Post subject: |
|
|
Damn... It's a good thing that someone knows what they are doing.
Lexikos, thanks for reviewing my code and having patience with me. Not many people would have taken the time.
I've tested the code and you are, of course, correct. It is mind boggling how lucky I am to have gotten my original code to work.
Well, AHK now has some functional Serial ( COM ) Port functions. I think that we can finally close this thread.
Thanks again Lexikos
aobrien |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|