SQLite Forum

[PATCH] Use ReadConsoleW and WriteConsoleW on Windows
Login

[PATCH] Use ReadConsoleW and WriteConsoleW on Windows

(1) By anonymous on 2021-02-27 23:51:38 [link] [source]

Index: src/shell.c.in
==================================================================
--- src/shell.c.in
+++ src/shell.c.in
@@ -437,21 +437,24 @@
 static char continuePrompt[20]; /* Continuation prompt. default: "   ...> " */
 
 /*
 ** Render output like fprintf().  Except, if the output is going to the
 ** console and if this is running on a Windows machine, translate the
-** output from UTF-8 into MBCS.
+** output from UTF-8 into UTF-16.
 */
 #if defined(_WIN32) || defined(WIN32)
 void utf8_printf(FILE *out, const char *zFormat, ...){
   va_list ap;
   va_start(ap, zFormat);
   if( stdout_is_console && (out==stdout || out==stderr) ){
     char *z1 = sqlite3_vmprintf(zFormat, ap);
-    char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0);
+    wchar_t *z2 = sqlite3_win32_utf8_to_unicode(z1);
+    int outfd = _fileno(out);
+    HANDLE outHandle = (HANDLE)_get_osfhandle(outfd);
     sqlite3_free(z1);
-    fputs(z2, out);
+    fflush(out);
+    WriteConsoleW(outHandle, z2, (DWORD)wcslen(z2), 0, 0);
     sqlite3_free(z2);
   }else{
     vfprintf(out, zFormat, ap);
   }
   va_end(ap);
@@ -634,10 +637,56 @@
   rc = stat(zFile, &x);
   return rc || !S_ISREG(x.st_mode);
 }
 #endif
 
+#if defined(_WIN32) || defined(WIN32)
+static char *win32_console_getline(char *zLine, FILE *in){
+  wchar_t* zWLine = (wchar_t*)zLine;
+  DWORD nLine = zWLine==0 ? 0 : 50;
+  DWORD n = 0;
+  DWORD nRead;
+  int infd = _fileno(in);
+  HANDLE inHandle = (HANDLE)_get_osfhandle(infd);
+  char *zTrans;
+  while( 1 ){
+    if( n+50>nLine ){
+      nLine = nLine*2 + 50;
+      zLine = realloc(zLine, nLine*2);
+      if( zLine==0 ) shell_out_of_memory();
+      zWLine = (wchar_t*)zLine;
+    }
+    if( ReadConsoleW(inHandle, &zWLine[n], nLine - n, &nRead, 0)==0 ){
+      if( n==0 ){
+        free(zLine);
+        return 0;
+      }
+      zWLine[n] = 0;
+      break;
+    }
+    n += nRead;
+    if( n>0 && zWLine[n-1]==L'\n' ){
+      n--;
+      if( n>0 && zWLine[n-1]==L'\r' ) n--;
+      zWLine[n] = 0;
+      break;
+    }
+  }
+  zTrans = sqlite3_win32_unicode_to_utf8(zWLine);
+  if( zTrans ){
+    DWORD nTrans = (DWORD)strlen30(zTrans)+1;
+    if( nTrans>nLine*2 ){
+      zLine = realloc(zLine, nTrans);
+      if( zLine==0 ) shell_out_of_memory();
+    }
+    memcpy(zLine, zTrans, nTrans);
+    sqlite3_free(zTrans);
+  }
+  return zLine;
+}
+#endif /* defined(_WIN32) || defined(WIN32) */
+
 /*
 ** This routine reads a line of text from FILE in, stores
 ** the text in memory obtained from malloc() and returns a pointer
 ** to the text.  NULL is returned at end of file, or if malloc()
 ** fails.
@@ -646,11 +695,15 @@
 ** a previous call to this routine that may be reused.
 */
 static char *local_getline(char *zLine, FILE *in){
   int nLine = zLine==0 ? 0 : 100;
   int n = 0;
-
+#if defined(_WIN32) || defined(WIN32)
+  if( stdin_is_interactive && in==stdin ){
+    return win32_console_getline(zLine, in);
+  }
+#endif /* defined(_WIN32) || defined(WIN32) */
   while( 1 ){
     if( n+100>nLine ){
       nLine = nLine*2 + 100;
       zLine = realloc(zLine, nLine);
       if( zLine==0 ) shell_out_of_memory();
@@ -669,26 +722,10 @@
       if( n>0 && zLine[n-1]=='\r' ) n--;
       zLine[n] = 0;
       break;
     }
   }
-#if defined(_WIN32) || defined(WIN32)
-  /* For interactive input on Windows systems, translate the
-  ** multi-byte characterset characters into UTF-8. */
-  if( stdin_is_interactive && in==stdin ){
-    char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
-    if( zTrans ){
-      int nTrans = strlen30(zTrans)+1;
-      if( nTrans>nLine ){
-        zLine = realloc(zLine, nTrans);
-        if( zLine==0 ) shell_out_of_memory();
-      }
-      memcpy(zLine, zTrans, nTrans);
-      sqlite3_free(zTrans);
-    }
-  }
-#endif /* defined(_WIN32) || defined(WIN32) */
   return zLine;
 }
 
 /*
 ** Retrieve a single line of input text.
@@ -710,11 +747,11 @@
   if( in!=0 ){
     zResult = local_getline(zPrior, in);
   }else{
     zPrompt = isContinuation ? continuePrompt : mainPrompt;
 #if SHELL_USE_LOCAL_GETLINE
-    printf("%s", zPrompt);
+    utf8_printf(stdout, "%s", zPrompt);
     fflush(stdout);
     zResult = local_getline(zPrior, stdin);
 #else
     free(zPrior);
     zResult = shell_readline(zPrompt);

(2) By anonymous on 2021-02-28 12:46:35 in reply to 1 [source]

It may be good practice to call SetConsoleMode() before ReadConsoleW() to make sure at least ENABLE_LINE_INPUT (and maybe other desired modes, such as ENABLE_ECHO_INPUT) are enabled, and restore the previous modes when done.

Also, not sure if there can be "short writes" with WriteConsoleW(), i.e. maybe it's okay to omit checking lpNumberOfCharsWritten, instead of repeating in a loop until all of nNumberOfCharsToWrite have been written. But at least on some versions of Windows, the amount of data that WriteConsoleW() can process in a chunk is limited, see for example:

Fossil SCM source code, file src/utf8.c, lines 398-407