2.6 Architecture-specific: Windows

#Q 2.6-1) The reported file position on Windows seems wrong. Why?
#Q 2.6-2) How do I get the functionality of the ~/time/ and ~/date/ format directives available in 3.0.2 in 5.0?
#Q 2.6-3) Why can't I use `dir' with run-shell-command?
#Q 2.6-4) How do I get the functionality of the 3.0.2 function allegro:read-lisp-line?
#Q 2.6-5) update.bat (the program for creating patched images) doesn't work on Windows 98. Why not?
#Q 2.6-6) Can I deliver the directory created by generate-application? That is, can a user of my application simply install the directory and run the .exe file?
#Q 2.6-7) How do I control the stack size on Windows?


Q 2.6-1) The reported file position on Windows seems wrong. Why?

A 2.6-1) The end-of-line convention on ms-dos (and thus Windows 95/98 and Windows NT) is that lines end with two characters: a carriage return and a linefeed. Common Lisp requires that programs only see one character (#\newline) as a line separator. Many programs ported from Unix also expect to see just one character separating lines.   Thus the Microsoft C library has a "text mode" feature in their stream package that will make it appear that lines are separated by just the #\newline character regardless of whether they are separated by carriage return and linefeed, or just linefeed. Allegro Common Lisp uses this "text mode" when reading and writing text files. However when a file is opened in "text mode" the file-position function in the Windows C library returns answers that reflect the true contents of the file, not the linefeed-separated view.  This confuses Lisp since it expects the C library stream's file-position and read functions to be in sync. The result is that Lisp's file-position function may return incorrect results if the stream is reading from a text file and that file uses the carriage return and linefeed convention for end of line.

We plan on fixing this problem in a future release but for now here are some workarounds:   If you will be randomly accessing a text file using file-position, then pass the :mode :binary arguments to Lisp's open function.   When you do this you'll have to be prepared for these unusual behaviors:

  1. Lines may end with #\return #\newline instead of just #\newline, so your program should be prepared to deal with both.
  2. When you write a new line from lisp (using the terpri or ~% from format) only a #\newline will be written.  If you want to maintain the ms-dos line ending convention, you'll have to write a #\return there yourself.
  3. Using Lisp's read function to read from such a stream will work except that if a string is continued over more than one line then the string may contain extra #\return characters in it.  This is probably not what you want. For example a format string ~<newline> will turn into ~<return><newline> and format will signal an error when that is used.

Q 2.6-2) How do I get the functionality of the ~/time/ and ~/date/ format directives available in 3.0.2 in 5.0?

A 2.6-2) What follows below is a quick port, with all its features, of the aclwin version running in acl5.  To prevent symbol clashes, the code is called as ~/aclwin:format-date/ and ~/aclwin:format-time/.

Examples: (Note that where it reports timezone, it erroneously uses "PST" instead of "PDT" -- this bug also occurs with the same code in aclwin.)

CG-USER(93): (format t "~/aclwin:format-date/" (get-universal-time))
7/17/98
NIL
CG-USER(94): (format t "~:/aclwin:format-date/" (get-universal-time))
July 17, 1998
NIL
CG-USER(95): (format t "~@/aclwin:format-date/" (get-universal-time))
17/Jul/1998
NIL
CG-USER(96): (format t "~@:/aclwin:format-date/" (get-universal-time))
Friday, July 17, 1998
NIL
CG-USER(97): (format t "~/aclwin:format-time/" (get-universal-time))
14:19:15
NIL
CG-USER(98): (format t "~:/aclwin:format-time/" (get-universal-time))
14:19:41 PST
NIL
CG-USER(99): (format t "~@/aclwin:format-time/" (get-universal-time))
2:19:57 pm
NIL
CG-USER(100): (format t "~@:/aclwin:format-time/" (get-universal-time))
2:20:48 pm PST
NIL
CG-USER(101): 
;;
;; beginning of code file
;; 
(defpackage :aclwin
  (:export
   *format-date-style*
   format-date
   format-time))
(in-package :aclwin)
(defvar *format-date-style* :us
  "The style for printing dates with ~/format-date/ directive. 
Possible values are: :european, :us")
(defun format-date (stream object &optional colon at-sign &rest args)
  (let ((mode 'short-numeric)
	(separator #\/))
    (when args
      (setq separator (car args)))
    (if* (and at-sign colon)
       then (setq mode 'long-alphabetic)
     elseif at-sign
       then (setq mode 'long-numeric)
     elseif colon
       then (setq mode 'short-alphabetic))
    (print-date stream mode separator object)))
(defun format-time (stream object &optional colon at-sign &rest args)
  (let ((mode 'twenty-four-hour)
	(separator #\:))
    (when args
      (setq separator (car args)))
    (if* (and at-sign colon)
       then (setq mode 'twelve-hour-with-time-zone)
     elseif at-sign
       then (setq mode 'twelve-hour)
     elseif colon
       then (setq mode 'twenty-four-hour-with-time-zone))
    (print-time stream mode separator object)))
(defun print-date (stream mode separator time)
  (multiple-value-bind (second minute hour date month year day-of-week 
			daylight-saving time-zone)
      (decode-universal-time time)
    (declare (ignore time-zone daylight-saving hour minute second))
    (let ((day-string 
	   (case day-of-week
	     (0 "Monday")
	     (1 "Tuesday")
	     (2 "Wednesday")
	     (3 "Thursday")
	     (4 "Friday")
	     (5 "Saturday")
	     (6 "Sunday")))
	  (month-string 
	   (case month
	     (1 "January")
	     (2 "February")
	     (3 "March")
	     (4 "April")
	     (5 "May")
	     (6 "June")
	     (7 "July")
	     (8 "August")
	     (9 "September")
	     (10 "October")
	     (11 "November")
	     (12 "December"))))
      (case mode
	(short-numeric 
	 (format stream "~d~c~2,'0d~c~2,'0d"
		 (case *format-date-style*
		   (:us month)
		   (t date))
		 separator
		 (case *format-date-style*
		   (:us date)
		   (t month))
		 separator
		 (irem year 100)))
	(long-numeric 
	 (format stream "~d~c~a~c~3,' d"
		 date
		 separator
		 (subseq month-string 0 3)
		 separator
		 year))
	(short-alphabetic
	 (format stream "~a ~d, ~3,' d"
		 month-string
		 date
		 year))
	(long-alphabetic
	 (format stream "~a, ~a ~d, ~3,' d"
		 day-string
		 month-string
		 date
		 year))))))
(defun print-time (stream mode separator time)
  (multiple-value-bind (second minute hour date month year day-of-week 
			daylight-saving time-zone)
      (decode-universal-time time)
    (declare (ignore day-of-week year month date))
    (case mode
      (twenty-four-hour (output-time stream hour minute second separator t))
      (twelve-hour (output-time stream hour minute second separator nil))
      (twenty-four-hour-with-time-zone 
       (output-time stream hour minute second separator t) 
       (format stream "~c" #\Space)
       (output-time-zone stream time-zone daylight-saving))
      (twelve-hour-with-time-zone 
       (output-time stream hour minute second separator nil) 
       (format stream "~c" #\Space)
       (output-time-zone stream time-zone daylight-saving)))))
(defun output-time (stream hour minute second separator |24HOURP|)
  (format stream "~:[~d~;~2,'0d~]~c~2,'0d~c~2,'0d"
	  |24HOURP|
	  (if |24HOURP| hour
	    (let ((hours (irem hour 12)))
	      (if (zerop hours) 12 hours)))
	  separator
	  minute
	  separator
	  second)
  (unless |24HOURP|
    (format stream "~c" #\Space) 
    (format stream "~a" (if (i>= hour 12) "pm" "am"))))
(defun output-time-zone (stream time-zone daylight-saving)
  (let ((time-zone-string 
	 (case time-zone
	   (0 (if daylight-saving "BST" "GMT"))
	   (1 "WET")
	   (5 "EST")
	   (6 "CST")
	   (7 "MST")
	   (8 "PST")
	   ;; there could be lots more time zone stuff here 
	   (otherwise nil))))
    (if time-zone-string (format stream "~a" time-zone-string) 
      (progn
	(format stream "~d" (i- time-zone))
	(format stream "hr")))))
;;
;; end of code file
;;

Q 2.6-3) Why can't I use `dir' with run-shell-command?

A 2.6-3) On Windows 95 and NT, excl:run-shell-command executes programs but does not invoke shell commands. The function is therefore misnamed for Windows. It is called run-shell-command to provide cross-platform compatibility between Windows and Unix and back compatibility for ACL4.3.x users. It does behave differently on Windows and Unix. The difference is related to differences between UNIX and Windows.

On Unix run-shell-command spawns a Unix process and runs the executable program or Unix shell command provided as argument to the function. On Windows, the argument to run-shell-command command is started directly and so works with programs but the Windows command shell is not invoked and so run-shell-command does not work with shell commands. An example of a program is Notepad. Either of these forms will start Notepad on Windows 95 and NT:

(run-shell-command "notepad") 
(run-shell-command "notepad.exe") 

To run a DOS shell command, the argument to run-shell-command has to start the shell and tell it to run the command. On Windows 95 the name of the shell is command.com so this form will run a shell command on Windows 95:

(run-shell-command "start command /k dir") 

A more complete example (where the results are read into Lisp) is:

(let ((str (run-shell-command "start command /k dir" :wait nil :output :stream))) 
  (do ((ch (read-char str nil nil) (read-char str nil nil))) 
      ((null ch) (close str) (sys:os-wait)) (write-char ch))) 

On Windows NT the name of the shell is cmd.exe but it is possible to run command.com. An example of using run-shell-command on NT 4.0:

(run-shell-command "cmd /c start cmd /k dir") 

Running shell commands on Windows 95 works less well than doing so in NT, possibly because the Windows 95 shell is older technology than the shell in NT.

A final comment: in almost all cases, you can use Lisp tools to achieve the same result as using run-shell-command. Instead of dir, use cl:directory, for example.


Q 2.6-4) How do I get the functionality of the 3.0.2 function allegro:read-lisp-line?

A 2.6-4) Although the symbol aclwin:read-lisp-line exists, it has no function binding. (In ACL 3.0.2, that function read a line of the optional stream argument and returned a list of the lisp objects on that line. It returned nil for blank lines and also when the end of the file was reached.)

This code mostly replicates the action of that function. The stream argument is required (rather than optional) and it returns :eof when the end of the file is reached (rather than nil). If an actual line is read, nil or a non-empty list is returned so testing whether the result is listp distinguishes between reading a line and reading an end-of-file.

The function here is named my-read-lisp-line. While we do not usually recommend this, you can define aclwin:read-lisp-line by changing the function name to aclwin:read-lisp-line and wrapping the form in excl:without-package-locks form.

(defun my-read-lisp-line (st)
  (let ((str (read-line st  nil :eof)))
    (if (eq str :eof) :eof ;; instead of nil
      (with-input-from-string (s str)
	(loop as x = (read s nil s)
	    until (eq x s)
	    collect x)))))

Q 2.6-5) update.bat (the program for creating patched images) doesn't work on Windows 98. Why not?

A 2.6-5) The update.bat program is used on Windows to build a new image containing patches for Allegro CL and related products. You get patches from the Franz Inc. web site, as described in introduction.htm#4.2 Patches. To create new images with patches loaded, you run the update.bat program in the Allegro directory. It creates new image (.dxl) files based on existing image files.

However, the update.bat program does not work on Windows 98. (It does work on Windows 95 and Windows NT. Windows 98 became available after Allegro CL 5.0 was in fabrication and this specific problem was only noticed later.) A change in the way programs are started in a batch file between Windows 95 and Windows 98 is the cause of the problem. You can fix it by editing the update.bat program as we describe now:

  1. Copy update.bat to updatebat.orig.
  2. Open update.bat in a convenient editor (Notepad, e.g.)
  3. Change the line

lisp +s update.cl -I update.dxl -qq

to

start /wait lisp +s update.cl -I update.dxl -qq

(That is, add start /wait to the beginning of the line.)

  1. Save the modified file.

Q 2.6-6) Can I deliver the directory created by generate-application? That is, can a user of my application simply install the directory and run the .exe file?

A 2.6-6) This answer applies to Allegro CL 5.0 (and some of the 5.0.1 beta versions). In version 5.0.1, there are new tools for handling this issue and mfc42.dll is no longer needed. See doc/cl/delivery.htm#4.9 Windows specific information in the 5.0.1 final documentation for details.

In 5.0, the answer is no; unfortunately it is not that simple.

The directory created by generate-application also contains the system DLLs msvcrt.dll and mfc42.dll, user32.dll (if you use CLIM), and possibly others (note: starting with Allegro CL 5.0.1 final, mfc42.dll is no longer needed). These files, if present in both the Windows `system' directory and the application directory can cause subtle failures.

user32.dll will be present if you use CLIM. If you execute your application with a copy of this DLL in the application directory, you will experience problems, and possibly even crashes of Windows 95/98.

If mfc42.dll is duplicated, when the user closes the application, the application will appear to exit but then an error will be signaled (on Windows NT) and perhaps a machine crash on Windows 95/98.

For this reason, you should `install' the files in the directory created by generate-application. You have two choices here:

  1. Use an installation creation program, like InstallShield. This is a very popular choice, one even endorsed by Microsoft.
  2. Do the installation by hand, as we describe next.

Hand installation: you (or your user) will need to ensure that the Windows `system' directory (<windir>/system on Windows 95/98 and <windir>/system32 on Windows NT and Windows 2000) contains a copy of the system DLLs needed by your application. If the a copy of the DLL  is present in the Windows system directory, you need to check that it has a later version than the one in your application directory (see below for how). If it does, then you need not copy your version of the DLL into the Windows `system' directory, and you can discard the copy in your application directory. If the version in the Windows `system' directory is older than the one in your application directory, then you should copy your version of the DLL into the Windows `system' directory. It is best to do this from DOS without Windows 95/98/NT/2000 running.

Determining which system dll is newer. This is not easy. File timestamps are no good, because they usually (or can) reflect when the file was placed in a location rather than when it was created. However, there is a MicroSoft program for this purpose:

http://support.microsoft.com/support/kb/articles/q167/5/97.asp

It contains a program getvers.exe (source included), that tells how to find the version information in a DLL or executable.


Q 2.6-7) How do I control the stack size on Windows?

A 2.6-7) The stack-size process attribute was supposed to control the stack allocation for the process. What the Windows documentation did not make clear, unfortunately, was that the running process can control the amount of swap space initially committed to the stack, but cannot select the amount of address space reserved for the stack.

The address space to be reserved for each thread's stack is a property of the executable image being used. The property is specified at link time, but it can be altered without relinking, through the EDITBIN program that is part of the VC+ development package.

To make each thread allocate a 4Meg stack, the command would be

editbin /stack:4000000 lisp.exe 

Unfortunately, MS Windows offers no way to have different reserve sizes for different threads in the same process, so all the threads have to be big enough to support the deepest computation on any thread.


© Copyright 1998, Franz Inc., Berkeley, CA.  All rights reserved.
$Revision: 1.1.2.15 $