Browse Source

Allocate argvs at once.

Rika Ichinose 1 year ago
parent
commit
3cfec5e6a4
1 changed files with 117 additions and 122 deletions
  1. 117 122
      rtl/win/syswin.inc

+ 117 - 122
rtl/win/syswin.inc

@@ -213,149 +213,144 @@ end;
                               Parameter Handling
 *****************************************************************************}
 
-var
-  argvw: PPWideChar;
+function WideCharToMultiByte(CodePage:UINT; dwFlags:DWORD; lpWideCharStr:PWideChar; cchWideChar:longint;
+  lpMultiByteStr:LPSTR;cchMultiByte:longint; lpDefaultChar:PAnsiChar; lpUsedDefaultChar:PLongBool):longint; stdcall; external 'kernel32' name 'WideCharToMultiByte';
+function GetCommandLineA : pansichar; stdcall;external KernelDLL name 'GetCommandLineA';
 
-procedure setup_arguments;
+type
+  { nargs — argument count (without first and without null terminator),
+    nchars — total widechar count in arguments (with null terminators),
+    nachars — total ansichar count in arguments (with null terminators), counted only if args = chars = nil. }
+  ParseCommandLineResult = record
+    nargs, nchars, nachars: SizeInt;
+  end;
+
+function ParseCommandLine(cmdLine: PWideChar; args: PPWideChar; chars: PWideChar): ParseCommandLineResult;
 var
-  CmdLineW, pw: PWideChar;
-  c: WideChar;
-  buf: array[0..MaxPathLen] of WideChar;
-  i, len, argvw_size: longint;
-  s: RawByteString;
-  quote: char;
+  argStartInCmdLine: PWideChar;
+  nArgChars, nArgCharsPlusQuotes, nachars: SizeInt;
+  c, quote: WideChar;
+  skippingFirstArg: boolean;
 begin
-  // Get the unicode command line
-  CmdLineW:=GetCommandLineW;
-  // Create the ansi command line
-  s:=ansistring(CmdLineW);
-  len:=Length(s) + 1;
-  CmdLine:=SysGetMem(len);
-  Move(PAnsiChar(s)^, CmdLine^, len);
-  // Alloc initial space for argvw
-  if CmdLineW^ = #0 then
-    argvw_size:=2
-  else
-    argvw_size:=10;
-  argvw:=SysGetMem(argvw_size*SizeOf(pointer));
-  // Get the full module name to be used as the first argument
-  len:=GetModuleFileNameW(0, @buf, Length(buf));
-  // Alloc maximum possible space for all arguments
-  pw:=SysGetMem((len + IndexWord(CmdLineW^, High(longint), 0) + 2)*SizeOf(WideChar));
-  // Copy the module name as the first argument. It will be nil terminated later
-  Move(buf, pw^, len*SizeOf(WideChar));
-  argvw[0]:=pw;
-  Inc(pw, len);
-  // Parse the command line
-  argc:=0;
+  argStartInCmdLine:=cmdLine;
+  nArgChars:=0;
+  FillChar(result,sizeof(result),0);
+  skippingFirstArg:=true;
   quote:=' ';
-  while True do
-    begin
-      c:=CmdLineW^;
-      Inc(CmdLineW);
-      case c of
-        #0..#32:
-          if (quote = ' ') or (c = #0) then
-            begin
-              // Are there any chars of an argument?
-              if argvw[argc] <> pw then
-                begin
-                  // End of an argument found
-                  pw^:=#0;
-                  Inc(pw);
-                  Inc(argc);
-                  if argc = argvw_size then
-                    begin
-                      // Increase the argvw space
-                      Inc(argvw_size, argvw_size shr 1);
-                      SysReAllocMem(argvw, argvw_size*SizeOf(pointer));
-                    end;
-                  if c = #0 then
-                    break;
-                  argvw[argc]:=pw;
-                  continue;
-                end
-              else
-                if c = #0 then
-                  break
-                else
-                  continue; // Skip whitespace
-            end;
-        '"':
+  repeat
+    c:=cmdLine^;
+    inc(cmdLine);
+    case c of
+      #0..#32:
+        if (quote=' ') or (c=#0) then
           begin
-            if quote<>'''' then
+            if (nArgChars<>0) then
               begin
-                if CmdLineW^<>'"' then
-                  begin
-                    if quote='"' then
-                       quote:=' '
-                     else
-                       quote:='"';
-                     continue;
-                  end
+                // End of an argument found
+                if Assigned(chars) then
+                  chars[result.nchars]:=#0
                 else
-                  Inc(CmdLineW);
-              end;
-          end;
-        '''':
-          begin
-            if quote<>'"' then
-              begin
-                if CmdLineW^<>'''' then
                   begin
-                    if quote='''' then
-                       quote:=' '
-                     else
-                       quote:='''';
-                    continue;
-                  end
-                else
-                  Inc(CmdLineW);
+                    { Number of widechars in the argument, including quotes: cmdLine - 1 - argStartInCmdLine. Avoid implicit signed div. }
+                    nArgCharsPlusQuotes:=SizeUint(pointer(cmdLine-1)-pointer(argStartInCmdLine)) div sizeof(widechar);
+                    nachars:=
+                      { Count of ANSI characters in the argument, including quotes. }
+                      WideCharToMultiByte(DefaultSystemCodePage, 0, argStartInCmdLine, nArgCharsPlusQuotes, nil, 0, nil, nil)
+                      { Assume each quote is 1 ANSI character. Subtract the amount of quotes. }
+                      -(nArgCharsPlusQuotes-nArgChars);
+                    if nachars<0 then
+                      nachars:=0; { Paranoia WideCharToMultiByte fail. }
+                    inc(result.nachars, nachars+1{null terminator});
+                  end;
+                inc(result.nchars); { Null terminator. }
+                nArgChars:=0;
               end;
+            skippingFirstArg:=false;
+            if c = #0 then
+              break;
+            continue; // Skip whitespace
           end;
-      end;
-      // Ignore the first argument, it is already copied
-      if argc <> 0 then
-        begin
-          // Copy the argument's AnsiChar
-          pw^:=c;
-          Inc(pw);
-        end;
+      '"', '''':
+        if (c='"') and (quote<>'''') or (c='''') and (quote<>'"') then
+          if cmdLine^<>c then
+            begin
+              if quote=c then
+                 quote:=' '
+               else
+                 quote:=c;
+               continue;
+            end
+          else
+            inc(cmdLine);
     end;
+    if skippingFirstArg then
+      continue;
+    if nArgChars=0 then
+      begin
+        if Assigned(args) then
+          args[result.nargs]:=chars+result.nchars;
+        inc(result.nargs);
+        argStartInCmdLine:=cmdLine-1;
+      end;
+    if Assigned(chars) then
+      chars[result.nchars]:=c;
+    inc(nArgChars);
+    inc(result.nchars);
+  until false;
+end;
 
-  // Finalization
-  // argvw is terminated by nil
-  argvw[argc]:=nil;
-  // Trim the memory
-  SysReAllocMem(argvw, (argc + 1)*SizeOf(pointer));
-  SysReAllocMem(argvw[0], ptruint(pw) - ptruint(argvw[0]));
+var
+  argvw: PPWideChar; { Start of the memory region. Should very preferably be private as argv can (and WILL, by LazUTF8) be changed from outside. }
 
-  // Construct the ansi argv
-  argv:=SysGetMem((argc + 1)*SizeOf(pointer));
-  for i:=0 to argc - 1 do
+procedure setup_arguments;
+var
+  CmdLineW, wchars: PWideChar;
+  buf: array[0..MaxPathLen] of WideChar;
+  iarg, nArg0W, nArg0A: SizeInt;
+  pc: ParseCommandLineResult;
+  achars, acharse: PAnsiChar;
+begin
+  CmdLine:=GetCommandLineA;
+  CmdLineW:=GetCommandLineW;
+  nArg0W:=GetModuleFileNameW(0, PWideChar(buf), Length(buf));
+  nArg0A:=WideCharToMultiByte(DefaultSystemCodePage, 0, PWideChar(buf), nArg0W, nil, 0, nil, nil);
+  pc:=ParseCommandLine(CmdLineW, nil, nil);
+  argc:=pc.nargs+1;
+
+  { Memory region layout:
+    argc × PWideChar: argvw (internal, not terminated with nil).
+    (argc + 1) × PAnsiChar: argv (terminated with nil).
+    Nw × widechar: chars for argvw.
+    Na × ansichar: chars for argv. }
+  argvw:=nil;
+  repeat { First iteration calculates region size (by adding to nil). Second iteration calculates pointers to region parts (by adding to region start). }
+    argv:=PPAnsiChar(argvw+argc);
+    wchars:=PWideChar(argv+argc+1);
+    achars:=PAnsiChar(wchars+nArg0W+1+pc.nchars);
+    if Assigned(argvw) then
+      break;
+    argvw:=SysGetMem(PtrUint(achars+nArg0A+1+pc.nachars));
+  until false;
+
+  Move(PWideChar(buf)^, wchars^, nArg0W*sizeof(widechar));
+  wchars[nArg0W]:=#0;
+  argvw[0]:=wchars;
+  ParseCommandLine(CmdLineW, argvw+1, wchars+nArg0W+1);
+
+  { Convert argvw to argv. }
+  acharse:=achars+nArg0A+1+pc.nachars;
+  for iarg:=0 to pc.nargs do
     begin
-      // Convert argvw[i] to argv[i]
-      s:=ansistring(argvw[i]);
-      len:=Length(s) + 1;
-      argv[i]:=SysGetMem(len);
-      Move(s[1], argv[i]^, len);
+      argv[iarg]:=achars;
+      inc(achars, WideCharToMultiByte(DefaultSystemCodePage, 0, argvw[iarg], length(argvw[iarg]), achars, acharse-achars, nil, nil)+1);
+      achars[-1]:=#0;
     end;
-  // argv is terminated by nil
   argv[argc]:=nil;
 end;
 
 procedure finalize_arguments;
-var
-  i: longint;
 begin
-  SysFreeMem(CmdLine);
-  // Free unicode arguments
-  SysFreeMem(argvw[0]);
   SysFreeMem(argvw);
-  // Free ansi arguments
-  for i:=0 to argc - 1 do
-    SysFreeMem(argv[i]);
-  SysFreeMem(argv);
 end;
 
 function paramcount : longint;