Nikolay Sivov : shcore: Add CommandLineToArgvW().

Alexandre Julliard julliard at winehq.org
Mon Nov 26 16:20:10 CST 2018


Module: wine
Branch: master
Commit: e4115e36c4c42df1b1aa357bc8f172e2424b8b93
URL:    https://source.winehq.org/git/wine.git/?a=commit;h=e4115e36c4c42df1b1aa357bc8f172e2424b8b93

Author: Nikolay Sivov <nsivov at codeweavers.com>
Date:   Mon Nov 26 11:53:12 2018 +0300

shcore: Add CommandLineToArgvW().

Signed-off-by: Nikolay Sivov <nsivov at codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard at winehq.org>

---

 dlls/shcore/main.c      | 258 ++++++++++++++++++++++++++++++++++++++++++++++++
 dlls/shcore/shcore.spec |   2 +-
 2 files changed, 259 insertions(+), 1 deletion(-)

diff --git a/dlls/shcore/main.c b/dlls/shcore/main.c
index ccea518..8f674a2 100644
--- a/dlls/shcore/main.c
+++ b/dlls/shcore/main.c
@@ -29,6 +29,7 @@
 #include "ocidl.h"
 #include "shellscalingapi.h"
 #include "wine/debug.h"
+#include "wine/unicode.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(shcore);
 
@@ -234,3 +235,260 @@ HRESULT WINAPI GetCurrentProcessExplicitAppUserModelID(const WCHAR **appid)
     *appid = NULL;
     return E_NOTIMPL;
 }
+
+/*************************************************************************
+ * CommandLineToArgvW            [SHCORE.@]
+ *
+ * We must interpret the quotes in the command line to rebuild the argv
+ * array correctly:
+ * - arguments are separated by spaces or tabs
+ * - quotes serve as optional argument delimiters
+ *   '"a b"'   -> 'a b'
+ * - escaped quotes must be converted back to '"'
+ *   '\"'      -> '"'
+ * - consecutive backslashes preceding a quote see their number halved with
+ *   the remainder escaping the quote:
+ *   2n   backslashes + quote -> n backslashes + quote as an argument delimiter
+ *   2n+1 backslashes + quote -> n backslashes + literal quote
+ * - backslashes that are not followed by a quote are copied literally:
+ *   'a\b'     -> 'a\b'
+ *   'a\\b'    -> 'a\\b'
+ * - in quoted strings, consecutive quotes see their number divided by three
+ *   with the remainder modulo 3 deciding whether to close the string or not.
+ *   Note that the opening quote must be counted in the consecutive quotes,
+ *   that's the (1+) below:
+ *   (1+) 3n   quotes -> n quotes
+ *   (1+) 3n+1 quotes -> n quotes plus closes the quoted string
+ *   (1+) 3n+2 quotes -> n+1 quotes plus closes the quoted string
+ * - in unquoted strings, the first quote opens the quoted string and the
+ *   remaining consecutive quotes follow the above rule.
+ */
+WCHAR** WINAPI CommandLineToArgvW(const WCHAR *cmdline, int *numargs)
+{
+    int qcount, bcount;
+    const WCHAR *s;
+    WCHAR **argv;
+    DWORD argc;
+    WCHAR *d;
+
+    if (!numargs)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return NULL;
+    }
+
+    if (*cmdline == 0)
+    {
+        /* Return the path to the executable */
+        DWORD len, deslen = MAX_PATH, size;
+
+        size = sizeof(WCHAR *) * 2 + deslen * sizeof(WCHAR);
+        for (;;)
+        {
+            if (!(argv = LocalAlloc(LMEM_FIXED, size))) return NULL;
+            len = GetModuleFileNameW(0, (WCHAR *)(argv + 2), deslen);
+            if (!len)
+            {
+                LocalFree(argv);
+                return NULL;
+            }
+            if (len < deslen) break;
+            deslen *= 2;
+            size = sizeof(WCHAR *) * 2 + deslen * sizeof(WCHAR);
+            LocalFree(argv);
+        }
+        argv[0] = (WCHAR *)(argv + 2);
+        argv[1] = NULL;
+        *numargs = 1;
+
+        return argv;
+    }
+
+    /* --- First count the arguments */
+    argc = 1;
+    s = cmdline;
+    /* The first argument, the executable path, follows special rules */
+    if (*s == '"')
+    {
+        /* The executable path ends at the next quote, no matter what */
+        s++;
+        while (*s)
+            if (*s++ == '"')
+                break;
+    }
+    else
+    {
+        /* The executable path ends at the next space, no matter what */
+        while (*s && *s != ' ' && *s != '\t')
+            s++;
+    }
+    /* skip to the first argument, if any */
+    while (*s == ' ' || *s == '\t')
+        s++;
+    if (*s)
+        argc++;
+
+    /* Analyze the remaining arguments */
+    qcount = bcount = 0;
+    while (*s)
+    {
+        if ((*s == ' ' || *s == '\t') && qcount == 0)
+        {
+            /* skip to the next argument and count it if any */
+            while (*s == ' ' || *s == '\t')
+                s++;
+            if (*s)
+                argc++;
+            bcount = 0;
+        }
+        else if (*s == '\\')
+        {
+            /* '\', count them */
+            bcount++;
+            s++;
+        }
+        else if (*s == '"')
+        {
+            /* '"' */
+            if ((bcount & 1) == 0)
+                qcount++; /* unescaped '"' */
+            s++;
+            bcount = 0;
+            /* consecutive quotes, see comment in copying code below */
+            while (*s == '"')
+            {
+                qcount++;
+                s++;
+            }
+            qcount = qcount % 3;
+            if (qcount == 2)
+                qcount = 0;
+        }
+        else
+        {
+            /* a regular character */
+            bcount = 0;
+            s++;
+        }
+    }
+
+    /* Allocate in a single lump, the string array, and the strings that go
+     * with it. This way the caller can make a single LocalFree() call to free
+     * both, as per MSDN.
+     */
+    argv = LocalAlloc(LMEM_FIXED, (argc + 1) * sizeof(WCHAR *) + (strlenW(cmdline) + 1) * sizeof(WCHAR));
+    if (!argv)
+        return NULL;
+
+    /* --- Then split and copy the arguments */
+    argv[0] = d = strcpyW((WCHAR *)(argv + argc + 1), cmdline);
+    argc = 1;
+    /* The first argument, the executable path, follows special rules */
+    if (*d == '"')
+    {
+        /* The executable path ends at the next quote, no matter what */
+        s = d + 1;
+        while (*s)
+        {
+            if (*s == '"')
+            {
+                s++;
+                break;
+            }
+            *d++ = *s++;
+        }
+    }
+    else
+    {
+        /* The executable path ends at the next space, no matter what */
+        while (*d && *d != ' ' && *d != '\t')
+            d++;
+        s = d;
+        if (*s)
+            s++;
+    }
+    /* close the executable path */
+    *d++ = 0;
+    /* skip to the first argument and initialize it if any */
+    while (*s == ' ' || *s == '\t')
+        s++;
+    if (!*s)
+    {
+        /* There are no parameters so we are all done */
+        argv[argc] = NULL;
+        *numargs = argc;
+        return argv;
+    }
+
+    /* Split and copy the remaining arguments */
+    argv[argc++] = d;
+    qcount = bcount = 0;
+    while (*s)
+    {
+        if ((*s == ' ' || *s == '\t') && qcount == 0)
+        {
+            /* close the argument */
+            *d++ = 0;
+            bcount = 0;
+
+            /* skip to the next one and initialize it if any */
+            do {
+                s++;
+            } while (*s == ' ' || *s == '\t');
+            if (*s)
+                argv[argc++] = d;
+        }
+        else if (*s=='\\')
+        {
+            *d++ = *s++;
+            bcount++;
+        }
+        else if (*s == '"')
+        {
+            if ((bcount & 1) == 0)
+            {
+                /* Preceded by an even number of '\', this is half that
+                 * number of '\', plus a quote which we erase.
+                 */
+                d -= bcount / 2;
+                qcount++;
+            }
+            else
+            {
+                /* Preceded by an odd number of '\', this is half that
+                 * number of '\' followed by a '"'
+                 */
+                d = d - bcount / 2 - 1;
+                *d++ = '"';
+            }
+            s++;
+            bcount = 0;
+            /* Now count the number of consecutive quotes. Note that qcount
+             * already takes into account the opening quote if any, as well as
+             * the quote that lead us here.
+             */
+            while (*s == '"')
+            {
+                if (++qcount == 3)
+                {
+                    *d++ = '"';
+                    qcount = 0;
+                }
+                s++;
+            }
+            if (qcount == 2)
+                qcount = 0;
+        }
+        else
+        {
+            /* a regular character */
+            *d++ = *s++;
+            bcount = 0;
+        }
+    }
+    *d = '\0';
+    argv[argc] = NULL;
+    *numargs = argc;
+
+    return argv;
+}
diff --git a/dlls/shcore/shcore.spec b/dlls/shcore/shcore.spec
index 2f3ca99..1266a02 100644
--- a/dlls/shcore/shcore.spec
+++ b/dlls/shcore/shcore.spec
@@ -1,5 +1,5 @@
 1 stub @
-@ stdcall CommandLineToArgvW(wstr ptr) shell32.CommandLineToArgvW
+@ stdcall CommandLineToArgvW(wstr ptr)
 @ stub CreateRandomAccessStreamOnFile
 @ stub CreateRandomAccessStreamOverStream
 @ stub CreateStreamOverRandomAccessStream




More information about the wine-cvs mailing list