I tried setting the buffer to none and turning off blocking mode. Still no joy with the patch. If I put a breakpoint at the call for getFileAttributes and wait a few seconds before continuing the bluetooth device will connect and work.
I noted that changing the order didn't set the flags variable. When I was testing for just opening and closing the port everything was fine. However after attempting to send data it didn't work as expected. I initially set flags to the value defined as FILE_ATTRIBUTE_NORMAL. However this didn't work with 8.6. I still don't have an understanding of what the flag is for but I found that the hardware serial ports on my workstation would respond back with xFFFFFFFF for the getFileAttributes call so I intentially set flags to 0 and it seems to be working.
I will need help building 8.6 to the equivalent Activestate binaries. I cannot get libraries such as Itcl to work. I'm not sure why... It would definitely help with testing.
For now I made a quick test, with the patches I've attached it should work. I've tested 8.5.15 and 8.6.1 across Windows XP and Windows 7 with these devices:
proc commTest {comm loop} {
set fp [open $comm r+]
fconfigure $fp -blocking 0
fconfigure $fp -buffering none
flush $fp
puts "RX and TX (pins 2 & 3) on the serial device should be bridged"
set failCnt 0
for {set i 0} {$i < $loop} {incr i} {
set testStr "This is a test of the serial communications"
puts -nonewline $fp $testStr
after 300
set compStr [read $fp]
if {[string equal $testStr $compStr]} {
puts "Test $i Passed"
} else {
puts "Test $i Failed"
puts $compStr
incr failCnt
}
update
}
close $fp
puts "Ran $i times, had $failCnt test(s) fail"
}
I'll post the patch here for now to review
8.6.1 patch
Index: win/tclWinChan.c
===================================================================
--- win/tclWinChan.c (revision 299)
+++ win/tclWinChan.c (working copy)
@@ -95,6 +95,7 @@
static int FileTruncateProc(ClientData instanceData,
Tcl_WideInt length);
static DWORD FileGetType(HANDLE handle);
+static int NativeIsComPort(CONST TCHAR *nativeName);
/*
* This structure describes the channel type structure for file based IO.
@@ -902,6 +903,36 @@
}
/*
+ * [2413550] Avoid double-open of serial ports on Windows
+ * Special handling for Windows serial ports by a "name-hint"
+ * to directly open it with the OVERLAPPED flag set.
+ */
+
+ if( NativeIsComPort(nativeName) ) {
+
+ flags = 0;
+
+ handle = TclWinSerialOpen(INVALID_HANDLE_VALUE, nativeName, accessMode);
+ if (handle == INVALID_HANDLE_VALUE) {
+ TclWinConvertError(GetLastError());
+ if (interp != (Tcl_Interp *) NULL) {
+ Tcl_AppendResult(interp, "couldn't open serial \"",
+ TclGetString(pathPtr), "\": ",
+ Tcl_PosixError(interp), NULL);
+ }
+ return NULL;
+ }
+
+ /*
+ * For natively named Windows serial ports we are done.
+ */
+ channel = TclWinOpenSerialChannel(handle, channelName,
+ channelPermissions);
+
+ return channel;
+ }
+
+ /*
* If the file is being created, get the file attributes from the
* permissions argument, else use the existing file attributes.
*/
@@ -919,7 +950,7 @@
}
}
- /*
+ /*
* Set up the file sharing mode. We want to allow simultaneous access.
*/
@@ -952,11 +983,15 @@
switch (FileGetType(handle)) {
case FILE_TYPE_SERIAL:
/*
+ * Natively named serial ports "com1-9", "\\\\.\\comXX" are
+ * already done with the code above.
+ * Here we handle all other serial port names.
+ *
* Reopen channel for OVERLAPPED operation. Normally this shouldn't
* fail, because the channel exists.
*/
- handle = TclWinSerialReopen(handle, nativeName, accessMode);
+ handle = TclWinSerialOpen(handle, nativeName, accessMode);
if (handle == INVALID_HANDLE_VALUE) {
TclWinConvertError(GetLastError());
if (interp != (Tcl_Interp *) NULL) {
@@ -1497,6 +1532,122 @@
}
/*
+ *----------------------------------------------------------------------
+ *
+ * NativeIsComPort --
+ *
+ * Determines if a path refers to a Windows serial port.
+ * A simple and efficient solution is to use a "name hint" to detect
+ * COM ports by their filename instead of resorting to a syscall
+ * to detect serialness after the fact.
+ * The following patterns cover common serial port names:
+ * COM[1-9]:?
+ * //./COM[0-9]+
+ * \\.\COM[0-9]+
+ *
+ * Results:
+ * 1 = serial port, 0 = not.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NativeIsComPort(
+ const TCHAR *nativePath) /* Path of file to access, native encoding. */
+{
+ /*
+ * Use wide-char or plain character case-insensitive comparison
+ */
+ if (1) {
+ const WCHAR *p = (const WCHAR *) nativePath;
+ int i, len = wcslen(p);
+
+ /*
+ * 1. Look for com[1-9]:?
+ */
+
+ if ( (len >= 4) && (len <= 5)
+ && (_wcsnicmp(p, L"com", 3) == 0) ) {
+ /*
+ * The 4th character must be a digit 1..9 optionally followed by a ":"
+ */
+
+ if ( (p[3] < L'1') || (p[3] > L'9') ) {
+ return 0;
+ }
+ if ( (len == 5) && (p[4] != L':') ) {
+ return 0;
+ }
+ return 1;
+ }
+
+ /*
+ * 2. Look for //./com[0-9]+ or \\.\com[0-9]+
+ */
+
+ if ( (len >= 8) && (
+ (_wcsnicmp(p, L"//./com", 7) == 0)
+ || (_wcsnicmp(p, L"\\\\.\\com", 7) == 0) ) )
+ {
+ /*
+ * Charaters 8..end must be a digits 0..9
+ */
+
+ for ( i=7; i<len; i++ ) {
+ if ( (p[i] < '0') || (p[i] > '9') ) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+ } else {
+ const char *p = (const char *) nativePath;
+ int i, len = strlen(p);
+
+ /*
+ * 1. Look for com[1-9]:?
+ */
+
+ if ( (len >= 4) && (len <= 5)
+ && (strnicmp(p, "com", 3) == 0) ) {
+ /*
+ * The 4th character must be a digit 1..9 optionally followed by a ":"
+ */
+
+ if ( (p[3] < '1') || (p[3] > '9') ) {
+ return 0;
+ }
+ if ( (len == 5) && (p[4] != ':') ) {
+ return 0;
+ }
+ return 1;
+ }
+
+ /*
+ * 2. Look for //./com[0-9]+ or \\.\com[0-9]+
+ */
+
+ if ( (len >= 8) && (
+ (strnicmp(p, "//./com", 7) == 0)
+ || (strnicmp(p, "\\\\.\\com", 7) == 0) ) )
+ {
+ /*
+ * Charaters 8..end must be a digits 0..9
+ */
+
+ for ( i=7; i<len; i++ ) {
+ if ( (p[i] < '0') || (p[i] > '9') ) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
* Local Variables:
* mode: c
* c-basic-offset: 4
Index: win/tclWinInt.h
===================================================================
--- win/tclWinInt.h (revision 299)
+++ win/tclWinInt.h (working copy)
@@ -49,7 +49,7 @@
int permissions, int appendMode);
MODULE_SCOPE Tcl_Channel TclWinOpenSerialChannel(HANDLE handle,
char *channelName, int permissions);
-MODULE_SCOPE HANDLE TclWinSerialReopen(HANDLE handle, const TCHAR *name,
+MODULE_SCOPE HANDLE TclWinSerialOpen(HANDLE handle, const TCHAR *name,
DWORD access);
MODULE_SCOPE int TclWinSymLinkCopyDirectory(const TCHAR *LinkOriginal,
const TCHAR *LinkCopy);
Index: win/tclWinSerial.c
===================================================================
--- win/tclWinSerial.c (revision 299)
+++ win/tclWinSerial.c (working copy)
@@ -1410,23 +1410,22 @@
/*
*----------------------------------------------------------------------
*
- * TclWinSerialReopen --
+ * TclWinSerialOpen --
*
- * Reopens the serial port with the OVERLAPPED FLAG set
+ * Opens or Reopens the serial port with the OVERLAPPED FLAG set
*
* Results:
- * Returns the new handle, or INVALID_HANDLE_VALUE. Normally there
- * shouldn't be any error, because the same channel has previously been
- * succeesfully opened.
+ * Returns the new handle, or INVALID_HANDLE_VALUE.
+ * I an existing channel is specified it is closed and reopened.
*
* Side effects:
- * May close the original handle
+ * May close/reopen the original handle
*
*----------------------------------------------------------------------
*/
HANDLE
-TclWinSerialReopen(
+TclWinSerialOpen(
HANDLE handle,
const TCHAR *name,
DWORD access)
@@ -1434,17 +1433,25 @@
SerialInit();
/*
- * Multithreaded I/O needs the overlapped flag set otherwise
- * ClearCommError blocks under Windows NT/2000 until serial output is
- * finished
+ * If an open channel is specified, close it
*/
- if (CloseHandle(handle) == FALSE) {
- return INVALID_HANDLE_VALUE;
+ if ( handle != INVALID_HANDLE_VALUE ) {
+ if (CloseHandle(handle) == FALSE) {
+ return INVALID_HANDLE_VALUE;
+ }
}
+
+ /*
+ * Multithreaded I/O needs the overlapped flag set otherwise
+ * ClearCommError blocks under Windows NT/2000 until serial output is
+ * finished
+ */
+
handle = CreateFile(name, access, 0, 0, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, 0);
- return handle;
+
+ return handle;
}
/*