AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Need help with DllCall("ReadFile", ... ) and VarSe

 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Ask for Help
View previous topic :: View next topic  
Author Message
aobrien



Joined: 14 Feb 2008
Posts: 38

PostPosted: Sun Feb 24, 2008 6:46 am    Post subject: Need help with DllCall("ReadFile", ... ) and VarSe Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2737
Location: Australia, Qld

PostPosted: Sun Feb 24, 2008 7:25 am    Post subject: Re: Need help with DllCall("ReadFile", ... ) and V Reply with quote

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
View user's profile Send private message
aobrien



Joined: 14 Feb 2008
Posts: 38

PostPosted: Sun Feb 24, 2008 5:45 pm    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2737
Location: Australia, Qld

PostPosted: Mon Feb 25, 2008 12:46 am    Post subject: Reply with quote

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
View user's profile Send private message
aobrien



Joined: 14 Feb 2008
Posts: 38

PostPosted: Mon Feb 25, 2008 5:45 am    Post subject: Reply with quote

Smile 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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2737
Location: Australia, Qld

PostPosted: Mon Feb 25, 2008 10:21 am    Post subject: Reply with quote

aobrien wrote:
Smile Sorry I didn't write that last reply very clearly...
You were clear enough, I was just wrong. Embarassed 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.) Exclamation

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
View user's profile Send private message
aobrien



Joined: 14 Feb 2008
Posts: 38

PostPosted: Mon Feb 25, 2008 5:07 pm    Post subject: Reply with quote

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
View user's profile Send private message
TodWulff



Joined: 29 Dec 2007
Posts: 99

PostPosted: Tue Mar 11, 2008 3:07 am    Post subject: Reply with quote

Any update aobrien? I am anxiously awaiting your updated code... Smile

Please let us know. Thanks!

-t
Back to top
View user's profile Send private message
aobrien



Joined: 14 Feb 2008
Posts: 38

PostPosted: Fri Mar 21, 2008 4:48 am    Post subject: Reply with quote

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
View user's profile Send private message
TodWulff



Joined: 29 Dec 2007
Posts: 99

PostPosted: Fri Mar 21, 2008 6:11 am    Post subject: Reply with quote

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' Smile], 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
View user's profile Send private message
aobrien



Joined: 14 Feb 2008
Posts: 38

PostPosted: Fri Mar 28, 2008 9:54 pm    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2737
Location: Australia, Qld

PostPosted: Sat Mar 29, 2008 1:23 am    Post subject: Reply with quote

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. Smile
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
View user's profile Send private message
aobrien



Joined: 14 Feb 2008
Posts: 38

PostPosted: Wed Apr 02, 2008 4:19 pm    Post subject: Reply with quote

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
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Ask for Help All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group