« Serial COM port IO | Main | Embedded Resources, ImageLists »

TAPI & Serial Com, Threading, Random Reverse Engineering Notes

Using “USEHIDDENWINDOW” with lineInitializeEx ties you to a windows message loop. Using “EVENTHANDLE” is much more flexible. The simplest way is to call lineGetMessage from a thread that can afford to be blocked. A slightly more efficient way is to couple waiting on the tapi event along with other events, potentially reducing the number of threads needed.

Call lineInitializeEx to get a tapi handle, the number of available devices, the tapi version, and to request event handle asynchronous notification.

Start a thread that calls lineGetMessage, optionally waiting on the event handle.

Figure out which device number to use.

Call lineOpen to get a line handle giving it the device number and media mode (DATAMODEM).

Call lineMakeCall to place a call. The returned call handle isn’t valid until a corresponding LINE_REPLY message is received asynchronously that matches the requestId (positive lineMakeCall return value).

Call lineGetID to retrieve a com port file handle to the com port associated with the call.

Use WriteFile to write bytes to the com port. It takes a file handle, pointer to bytes, number of bytes, and an OVERLAPPED structure containing an event handle. The event is signaled when the write ends. The number of bytes written is available by calling GetOverlappedResult.

WriteFileEx is like WriteFile except it will call a callback function  when the thread “is in an alertable wait state” and ignore the event handle.

Use ReadFile to read bytes from the com port. It takes a file handle, number of bytes to be read (max),  and an OVERLAPPED structure containing an event handle. It returns a pointer to the bytes read and the count.

SetCommTimeouts may be needed for ReadFile to work reliably with a communications device.

The SerialCommBase code needs to be extended to attach to an open port handle, also modifying cleanup to reflect that it won’t be responsible for closing the handle but the OVERLAPPED buffer must be released (and other resources). Current implementation blocks a second write until the first completes. This is reasonable for us.

Writes are asynchronous but who/when does the GetOverlappedResult get called? In CheckResult. Passes an event to the WriteFile function and relies on GetOverlappedResult waiting on it if checkSends is true (the default). CheckResult is called by Flush and Send. So write results are determined by the next send or flush on the main thread.

Reading is done by waiting on a variety of comm. events and then reading one byte at a time in a loop when a byte available event occurs. This design required either that a read could be canceled when out of data or that an extra read of an unavailable byte would immediately timeout, which is the approach taken by the code after trying the canceling route and failing to make it robust.

Bytes are delivered by OnRxChar which is invoked on the read thread rather than the main thread?

The read thread is also used to invoke the send complete virtual OnTxDone. That’s a bit counter intuitive.

Why not just have a read and write thread? Have the read thread block until data is available and then retrieve all of it in one go. Have a virtual that gets all of them with a default implementation in terms of another virtual that gets individual bytes. Could be coupled with a mechanism that determines how many bytes are desired before being notified.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)