Skip to content

Latest commit

 

History

History

win

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
		   Building Yorick under MS Windows
		   --------------------------------

These notes explain how to build yorick with the MSVC++ programming
environment.  If you want to use cygwin, read the "rationale" notes at
the end of this file.  (If you are successful, send me your cygwin
Makefile.)

All paths are relative to the top level distribution directory; this
README file is therefore win\README.  You should be able to build
yorick by following these steps:

(1) open the workspace win\yorick.dsw
(2) set the active project to setup and build it
    (this copies some .i files into their proper directories)
(2) set the active project to yorick
(3) build

The yorick.exe file will be in the Debug\ or Release\ directory.
These directories are children of the top level distribution directory
instead of the win\ directory containing the workspace and projects,
in order for the executable to be in a sibling of the i\, i0\, and g\
directories.  (See the top level README for an explanation of why that
is important.)

By default, yorick is organized into a number of separate projects:

numfmt.dsp -- computes primitive data formats, creates yorick\prmtyp.h
codger.dsp -- builds the yorick initialization/wrapper code generator
play.dsp   -- builds the portability layer C-code library
  source is in play\all and play\win
  does not include the MFC source in play\win, as MSVC++ won't link
  this code properly if it is in a separate library
gist.dsp   -- builds the gist 2D graphics library
drat.dsp   -- builds a 2D raytrace extension library for yorick
hex.dsp    -- builds a 3D raytrace extension library for yorick
yorlib.dsp -- builds the library containing yorick itself
yorick.dsp -- builds the MFC interface to yorick in play\win
  and links it to the yorlib, drat, hex, gist, and play libraries

There are also four projects which test the play and gist libraries:

test2d.dsp -- builds a test code for debugging play\win\
bench.dsp  -- builds a test code for debugging gist\
yordbg.dsp -- builds a test code for debugging all of yorick
test3d.dsp -- builds a test code for debugging opengl (play\win\oglw.c)

If you want to add an extension package to yorick, you should read the
chapter in the user manual, then study the drat and hex packages.  A
simple example of an extension is provided in the extend\ directory,
which you can use to practice the mechanics.  Here are the basic
steps:

(0) Open the yorick.dsw workspace.

(1) Put your code in a sibling of the yorick\, i\, i0\, and g\
directories (like drat\ and hex\), say myext\.

(2) Do not attempt to use MSVC++ to create your project; its GUI does
not allow you to set all of the path names in your switches (at least
I haven't been able to figure out how to set, for example, PROP BASE
Output_Dir).  Instead, copy pkg.dsp to myext.dsp, then edit myext.dsp,
changing all occurrences of "pkg" to "myext".

(3) Insert myext.dsp into the workspace.  (Either using insert project
into workspace from the Project menu, or by right-clicking on the
Workspace line at the top of the Workspace panel.)

(4) Select the FileView tab in the workspace panel.  Right click on
the "myext files" line, and select "Add Files to Project".  Navigate
to your myext\ directory and select all your source and header files
(by holding down the Ctrl key and left clicking on each one).

(5) Set the active project to myext and build it.  This will create
a Win32 static library myext.lib.

(6) Select "Dependencies..." from the Project menu, and make myext a
dependency of yorlib.

(7) Edit the file win\makeinit.bat, adding ../myext/myext.i to the
codger execute line, imediately after ../hex/hex.i.  DO NOT use
backslashes as directory separators (they represent an escape sequence
in C).

(8) Copy myext\myext.i into the i0\ startup directory.

(9) Set the active project to yorick and build it.  The version you
get will have your myext.i extensions.



------------------------------------------------------------------------
------------------------------------------------------------------------
Rationale for MS Windows PLAY Implementation
--------------------------------------------

Three goals constrain the overall structure I chose for the MS Windows
implementation of my portability layer (play):

(G1) Provide terminal emulation and a text editor for source files.

(G2) Allow emacs users to run in the same development environment I
provide for UNIX systems.

(G3) Allow Cygwin developers to build a version of play compatible at
least with (G2), while at the same time targeting the MSVC++
development environment.

The (G1) and (G2) goals both must include the ability to interrupt a
running play-based program, in a manner analogous to SIGINT
(control-c) on UNIX or POSIX systems.

MS Windows actually runs programs within several different runtime
environments called "subsystems".  The two most relevant are the
"windows" subsystem, in which all the programs familiar to most users
run, and the "console" subsystem, which is designed to be a crippled
version of a command line environment like the "MSDOS Prompt".  The
linker marks every executable file with the Windows subsystem in which
it will run, and it is impossible for a program to change subsystems
after it has been started.

Originally, I planned to distribute two separate yorick.exe files with
the distribution -- one marked for the windows subsystem (G1) and a
second marked for the console subsystem (G2 and G3).  The idea was
that both could be small programs linked against a common much larger
yorick.dll containing all the interesting parts of yorick.  The show
stopper for that plan is that no program running within the console
subsystem is allowed access to the Windows clipboard (at least on my
Win95 box), making the p_scopy and p_spaste APIs impossible.

Therefore, I reverted to a single yorick.exe file running in the
windows subsystem.  In order to satisfy (G2), the program looks for an
environment variable NO_MDI.  If NO_MDI is present and not "0", then
yorick.exe uses the GetStdHandle functions to retrieve stdin, stdout,
and stderr streams, and yorick graphics windows are created as
top-level windows.  Thus, emacs can setenv NO_MDI=1, then start yorick
and the combination of the emacs terminal/editor windows and the
yorick graphics windows will present the same user interface as under
the X Window system on UNIX systems.  Conversely, if yorick.exe is
started by simply double-clicking, then NO_MDI is not set, and an MDI
(multiple document interface) frame window appears, with a terminal
emulator window and support for text editor windows; yorick graphics
windows appear as MDI client windows in this mode.

(Originally, I attempted to "autodetect" how yorick.exe was started.
The idea was that if GetStdHandle returned a valid stdin handle, then
I would drop into no-MDI mode, while if there was no stdin, I would
start in full MDI mode.  This worked under Win95, but under WinNT,
GetStdHandle returns a valid stdin even when yorick.exe is started by
double-clicking on its icon.  I then tried using a -nomdi command line
switch, but I couldn't get emacs to pass command line arguments
correctly, so I wound up with the NO_MDI environment variable.)

Linking a program with MFC in the cygwin development environment is
either difficult or impossible, so I provide a small alternative
WinMain for that environment, which produces an executable that only
runs in the the non-MDI mode.  This must still be linked in the
windows subsystem in order for the clipboard functions to work.  If
neither -nomdi nor NO_MDI is set in this mode, an "MSDOS Prompt"
console is allocated.

The major drawback of running in the windows subsystem instead of
under the console subsystem is that the OS provides no equivalent for
the SIGINT (control-c) signal for the windows subsystem.  Hence, there
is ordinarily no way to interrupt a windows-subsystem program when it
gets caught in an infinite loop, short of ctrl-alt-del and terminating
the task entirely.

In order to solve this interrupt problem, and also to allow source
text editor windows to function while yorick is performing a lengthy
calculation, I went to a multithreaded implementation.  The basic idea
is that a "boss thread" controls all the MDI frame and client windows
(terminal emulator, text editor, and child frames around the graphics
windows), while the yorick interpreter runs entirely within a separate
"worker thread".  That way, the boss works like a typical windows
application, never doing any work that requires more than a second of
real time, and the terminal and text editor windows remain responsive
independent of the duration of any interpreted task.  The boss thread
also controls the MDI menu bar, which includes a "STOP" button that
generates a fake SIGINT signal to interrupt the worker thread.

Because the mouse and keyboard event callbacks generated within yorick
graphics windows must run in the worker thread, the worker thread must
create the graphics windows.  Therefore, yorick graphics windows are
mostly created as children of an MDI child (client) window owned by
the boss thread.  Events generated in the graphics windows are thus
posted to the worker thread as they should be.

The overall structure of yorick looks like this (indentation indicates
ancestry):

      thread                  window                    C++ class
   ----------------- ------------------------------ ----------------
   boss thread       MDI frame window (main window) [mfc_frame_wnd]
                        MDI child window            [mfc_edit_child]
                           terminal window          [mfc_term_view]
                        MDI child window            [mfc_edit_child]
                           history window           [mfc_term_view]
                        MDI child window            [mfc_edit_child]
                           text editor windows      [mfc_edit_view]
                        MDI child window            [mfc_child]
      worker thread        graphics windows         <no C++ class>
                     p_menu windows                 <no C++ class>
                     P_DIALOG windows               <no C++ class>
         IO thread
      SIGINT thread

In NO_MDI-mode, the MDI frame window is created, but never shown.
This provides a window for SendMessage calls from the worker thread,
which are by far the easiest way for the worker to communicate with
the boss.  The PostThreadMessage API could be used instead of
SendMessage, but its asynchronous semantics means considerable extra
programming effort in order for the worker to receive a reply to its
query.  (The boss never expects an immediate reply from the worker,
because the worker might be doing some lengthy job; however, the boss
is never occupied for very long, so the worker generally waits for
replies.)  None of the MDI child windows is ever created in
NO_MDI-mode, and the graphics windows are top-level in that case.

The IO thread only exists in NO_MDI-mode; it solves the following
synchronization problem: Input to the worker thread can arrive in the
form of windows messages concerning events in its graphics windows, or
from command lines typed in the emacs process which acts as its
terminal window in NO_MDI-mode.  The Windows API provides an analog of
the UNIX select/poll functions called MsgWaitForMultipleObjects.
Unfortunately, MsgWaitForMultipleObjects cannot detect new input on
the pipe to emacs returned by GetStdHandle (although it can detect new
input on a true "console" handle).  The solution is to spawn a
separate IO thread which is always waiting for input from stdin.  It
collects this until it finds a newline, at which time the IO thread
sets a Windows event-object, which MsgWaitForMultipleObjects can
detect, which wakes up the worker thread.  The worker picks up the
message, then sets a second event-object to tell the IO thread (which
has returned to listening to stdin in the meantime).  When new input
arrives, the IO thread skips delivery if the worker has not signalled
that it has accepted the previous line.

Because SIGINT delivery does not work in the windows subsystem, the IO
thread simulates it as follows: If the first character on a new input
line is control-c, the IO thread initiates an interrupt sequence, in
the same way the boss thread does when the "STOP" button on its
menubar is pressed.

The interrupt sequence temporarily creates a fourth thread; this
SIGINT thread is spawned by the boss when the STOP button is pressed,
or by the IO thread when a control-c arrives.  The SIGINT thread sets
p_signalling, posts a special "wm_exception" message to the worker
thread, then goes to sleep for either one second, or until another
event-object is signalled by the worker thread acknowledging the
interrupt.  If no acknowledgement has arrived, the SIGINT thread
suspends the worker thread, retrieves its "thread context", sets its
program counter to point to p_abort, then resumes the worker thread.
Executing p_abort immediately raises an exception and the forcible
interrupt of the worker is accomplished.

For both the MFC and cygwin codes, all play APIs and callbacks execute
in the worker thread.  However, in the MFC implementation, some APIs
-- in particular p_stdout and p_stderr -- call SendMessage to the
main window (the MDI frame window owned by the boss thread) and block
execution of the worker until the boss processes the message.

The cygwin program structure is different; it omits the boss thread
altogether.  The worker, IO, and SIGINT threads work exactly as in the
MDI-mode.  The worker creates a hidden main window, since programs in
the windows subsystem seem to work better if there is a "main window"
for the application.

One additional complexity is the implementation of p_abort and other
signal handlers.  MS documentation warns that POSIX signal handling
may not work; MS instead provides a non-standard "SEH" (structured
exception handling) mechanism which it claims to support.  SEH is
difficult or impossible to use under the cygwin environment.  Hence, I
provide both an ANSI standard C signal handler implementation of
p_abort (sigansi.c) for cygwin developers, and an SEH implementation
of p_abort (sigseh.c) for MSVC++ developers.  The sigansi.c version
seems to work, but I am leery of the warning in the documentation.  At
this writing, cygwin (or mingw) maintainers are trying to include SEH
support, so hopefully this problem will be temporary.

Windows GDI problems
--------------------

Implementing PLAY graphics primitives with the Win32 GDI is difficult
because the various versions of Windows behave slightly differently,
and the API does not specify the precise effects of its graphical
primitives.  (For example, the MS documentation for its line drawing
primitives claims that the first point on a line segment is "always
drawn", while the final point on a segment is "never drawn".  So what
happens when the first point and final point coincide?  Apparently,
the answer is different on various versions of Windows.  Furthermore,
while my Win95 box seems to indeed omit the final point of segments,
Langer's WinNT box does not.  Hence, different versions of Windows
require different graphical primitives to produce the same output on
the screen.  Variation in rendering of enhanced metafiles is even
greater, since various commercial packages render the same file
differently even on a single platform.)

One other gotcha is that rotated text does not actually work for
screen device contexts (at least on many Windows versions) --
CreateFont silently ignores its text angle parameter (or more likely
SelectObject ignores it).  Since under Win9x, patterned brushes are
restricted to 8x8 pixels, the trick I used to get rotated text under
the X Window system does not work under MS Windows.  Hence, I gave up
and rotated text under Windows is always opaque -- the bounding
rectangle is erased to the window background color.  Hopefully, this
won't be a terrible problem.

Issues for I/O under MS Windows
-------------------------------

The difference between UNIX and MS Windows filenames is handled in
pathnm.c.  The only adjustment is to change "/" to "\" in all
filenames passed to play functions, in order to guarantee that UNIX
delimiters are acceptable.  The reverse translation is performed for
the p_getcwd result, so that UNIX delimiters are output.  Note that
this means non-UNIX-like names of the forms

  C:/path/name
or
  //server_name/share_name/path
or
  //./physical_drive:

may appear or be required in order to access some files.  The
backslash delimited names are also acceptable on input.

(Note: perhaps I should not change slash to backslash -- as far as I
can tell, the Windows file functions, both in the runtime C libraries
and the native Win32 API, allow either type of slash as a directory
separator.)

There are several unavoidable idiosyncrasies of (some?) Windows
filesystems:

(1) Path names are not case sensitive.  However, capitalization is
remembered for listing purposes.  (But if you overwrite an existing
file with another of different capitalization, it sometimes remembers
the original capitalization.)

(2) A trailing period "." in a filename is ignored.  (This has to do
with the fact that the first three characters after the final period
in a filename are significant to Windows.)

(3) There may be some additional restrictions on "-"; in particular I
have trouble making it the first character of a filename.

The p_rename function may not work like the UNIX version in some
cases.  In particular, if the new name exists and is a directory, I am
unsure what happens (in either Windows or UNIX).


Limitations of PLAY API functions under MS Windows
--------------------------------------------------

(1) rotated text is opaque (see above discussion)

(2) p_rgb_read only works when window is in foreground
    -- if window is covered, then reads back covering region
    -- could use this as "feature" to get contents of other
       windows as bitmaps...

(3) p_spaste cannot be called from on_deselect method

(4) palettes only used in 256-color mode
    - p_palette should only be called at beginning of redraw
    - p_palette should not be called for metafiles or offscreens
    - p_palette ignored for menus (always use RGB colors)
    even with all these precautions, still misbehave somewhat, especially
    when using emacs as terminal


------------------------------------------------------------------------
------------------------------------------------------------------------
notes for finding startup files on Windows
------------------------------------------

Three different problems: (a) startup by user using start menu or
double clicking on executable, (b) startup by user by double clicking
on a .i file, (c) startup by development environment for debugging.

(1) Installation puts executable in known location relative to
    startup files.
(2) Application looks for startup files relative to its initial
    current working directory.  Fallback to a text file containing
    the startup path located in the initial cwd.  This should handle
    (a) and (c).
(3) Fallback to reading registry to handle (b)?

REGISTRY
    possible uses include:
  (1) non-default installation directory name
  (2) environment variables and other user preferences
      (e.g.- a "home" directory name, colors, fonts, sizes, etc)
RESOURCES
    possible alternative to REGISTRY
  (1) find out if resource compiler (rc.exe) and program to bind
      resulting .res file to application .exe is present on all
      Windows systems (or can reasonably be shipped with a distribution)
      - if so, it is probably a superior solution for installation-time
        choices
  (2) find out whether setting "environment variables" and other user
      customizations by this mechanism is plausible