The Win32 implementation is still in a state of development. It is expected that changes will be necessary when MIT Scheme is ported to Windows NT on the DEC Alpha architecture. In particular, the current system is not arranged in a way that adequately distinguishes between issues that are a consequence of the NT operating system and those which are a consequence of the Intel x86 architecture.
Thus this documentation is not definitive, it merely outlines how the current system works. Parts of the system will change and any project implemented using the win32 system must plan for a re-implementation stage.
The Win32 implementation has several components:
Note that all the names in the Win32 support are part of the
win32
package. The names are bound in the (win32)
environment, and do not appear as bindings in the user or root
environments.
An effect of this is that it is far easier to develop Win32 software in
the (win32)
package environment or a child environment.
The Win32 foreign function interface (FFI) is a primitive and fairly simple system for calling procedures written in C in a dynamically linked library (DLL). Both user's procedures from a custom DLL and system procedures (e.g. MessageBox) are called using the same mechanism.
Warning: The FFI as it stands has several flaws which make it difficult to use reliably. It is expected that both the interface to and the mechanisms used by the FFI will be changed in the future. We provide it, and this documentation, only to give people an early start in accessing some of the features of Win32 from Scheme. Should you use it in an experiment we welcome any feedback.
The FFI is designed for calling C procedures that use C data types rather than Scheme data objects. Thus it is not possible to write and call a C procedure that returns, for example, a Scheme list. The object returned will always be an integer (which may represent the address of a C data structure).
Warning: It is extremely dangerous to try to pass Scheme callback procedures to C procedures. It is only possible by passing integer `handles' rather than the actual procedures, and even so, if a garbage collection occurs during the execution of the callback procedure objects in Scheme's heap will have moved. Thus in a foreign procedure that has a callback and a string, after calling the callback the string value may no longer be valid. Playing this game requires a profound knowledge of the implementation.
The interface to the FFI has two main components: a language for declaring the types of values passed to and returned from the foreign procedures and a form for declaring foreign procedures.
Foreign types are designed to represent a correspondence between a Scheme data type that is used to represent an object within the Scheme world and a C data type that represents the data object in the C world. Thus we cannot manipulate true C objects in Scheme, nor can we manipulate Scheme objects in C.
Each foreign type has four aspects that together ensure that the correspondence between the Scheme and C objects is maintained. These aspects are all encoded as procedures that either check for validity or convert between representations. Thus a foreign type is not a declarative type so much as a procedural description of how to pass the type. The underlying foreign procedure call mechanism can pass integers and vector-like Scheme objects, and returns integer values. All other objects must be translated into integers or some other basic type, and must be recovered from integers.
The aspects are:
#t
if the argument is of an acceptable
Scheme type, otherwise returns #f
.
The check procedure is used for type-checking.
#f
.
A #f
means use the default value, which in the second form means
use the definition provided for model.
The defaults are
(lambda (x) #t)
, i.e. unchecked.
(lambda (x) x)
, i.e. no translation performed.
(lambda (x) x)
, i.e. no translation performed.
(lambda (x y) unspecific)
, i.e. no update performed
The unchecked
windows type (see below) is defined as:
(define-windows-type unchecked #f #f #f #f)
Windows types are not first class values, so they cannot be
stored in variables or defined using define
:
(define my-type unchecked) error--> Unbound variable (define-similar-windows-type my-type unchecked) ;; the correct way
Scheme characters must be converted to integers. This is accomplished as follows:
(define-windows-type char char? ; check char->integer ; convert integer->char ; convert return value #f ; cannot be passed by reference )
unchecked
values are returned as integers.
0
and 1
.
Windows type bool
have been defined as:
(define-windows-type bool boolean? (lambda (x) (if x 1 0)) (lambda (x) (if (eq? x 0) #f #t)) #f)
char
,
which are indistinguishable from small integers.
char*
to the first
character in the string.
#f
. The string is passed as a pointer to characters.
The string is correctly null-terminated. #f
is passed as the null
pointer. This is an example where there is a more complex mapping
between C objects and Scheme objects. C's char*
type is
represented as one of two Scheme types depending on its value. This
allows us us to distinguish between the C string (pointer) that points
to the empty sequence of characters and the null pointer (which doesnt
point anywhere).
hmenu?
) and
sensible interlocking with the garbage collector to free the programmer
of the current tedious allocation and deallocation of handles.
Foreign procedures are declared as callable entry-points in a module, usually a dynamically linked library (DLL).
windows-procedure
. Name is a string which is the name of a
DLL file. Internally, find-module
uses the LoadLibrary
Win32 API, so name should conform to the specifications for this
call. Name should be either a full path name of a DLL, or the
name of a DLL that resides in the same directory as the Scheme binary
`SCHEME.EXE' or in the system directory.
The module returned is a description for the DLL, and the DLL need not necessarily be linked at or immediately after this call. DLL modules are linked on need and unlinked before Scheme exits and when there are no remaining references to entry points after a garbage-collection. This behaviour ensures that the Scheme system can run when a DLL is absent, provided the DLL is not actually used (i.e. no attempt is made to call a procedure in the DLL).
LineTo
.
MessageBox
and SetWindowText
.
find-module
.
These are the only parts of the form that are evaluated at procedure
creation time.
Name is the name of the procedure and is for documentation
purposes only. This form does not define a procedure called
name. It is more like lambda
. The name might be used for
debugging and pretty-printing.
A windows procedure has a fixed number of parameters (i.e. no `rest' parameters or `varargs'), each of which is named and associated with a windows type type. Both the name parameter and the windows type type must be symbols and are not evaluated. The procedure returns a value of the windows type return-type.
The following example creates a procedure that takes a window handle
(hwnd
) and a string and returns a boolean (bool
) result.
The procedure does this by calling the SetWindowText
entry in the
module that is the value of the variable user32.dll
. The
variable set-window-title
is defined to have this procedure as
it's value.
(define set-window-title (windows-procedure (set-window-text (window hwnd) (text string)) bool user32.dll "SetWindowText")) (set-window-title my-win "Hi") => #t ;; Changes window's title/text set-window-title => #[compiled-procedure ...] set-window-text error--> Unbound variable
When there are no options the created procedure will (a) check its arguments against the types, (b) convert the arguments, (c) call the C procedure and (d) convert the returned value. No reversion is performed, even if one of the types has a reversion defined. (Reverted types are rare [I have never used one], so paying a cost for this unless it is used seems silly).
The following options are allowed:
with-reversions
expand
with-reversions
.
If both options (i.e. with-reversions
and Scheme code) are used,
with-reversions
must appear first. There can be arbitrarily many
Scheme expression.
This section is a moving target.
The #define
values from `wingdi.h' and `winuser.h' are
available as bindings in the (win32)
package environment. The
#define
symbols are all uppercase; these have been translated to
all lowercase Scheme identifiers, thus WM_LBUTTONUP
is the scheme
variable wm_lbuttonup
. As Scheme is case insensitive, the
upper-case version may be used and probably should to make the code look
more like conventional Windows code. The Scheme bindings have been
produced automagically. Most of the #define
-symbols contain an
underscore so there are not many name clashes. There is one very
notable name clash, however: ERROR
is #define
d to 0, which
shadows the scheme procedure error
in the root package
environment. To signal an error, use access
to get error
from the system global environment:
(declare (usual-integrations)) ... ((access error system-global-environment) "Complain" ...)
The set of procedures is incomplete because procedures have been added on a by-need basis for the implementation of other parts of the system, e.g. Scheme Graphics. Look in the implementation for further details.
Win32 API procedure names have been uniformly converted into Scheme identifiers as follows:
Is
finally have a
question-mark appended.
Example: applying these rules to IsWindow
yields
is-window?
, and GetDC
is translated into get-dc
.
The Device Independent Bitmap (DIB) utilities library `DIBUTILS.DLL' and the associated procedures in `dib.scm' in the Win32 system source is an example of how to use the foreign function interface to access and manipulate non-Scheme objects.
In the Scheme world a DIB is a structure containing information
about the bitmap (specifically the integer that represents the handle).
We also include #f
in the dib
windows type to mirror the
null handle error value.
(define dib-result (lambda (handle) (if (= handle 0) #f (make-dib handle)))) (define dib-arg (lambda (dib) (if dib (cell-contents (dib-handle dib)) 0))) (define-windows-type dib (lambda (thing) (or (dib? thing) (eq? thing #f))) dib-arg dib-result)
The following procedures have typed parameters, using the same
convention as windows-procedure
.
OpenDIB
entry of
`DIBUTILS.DLL'. If the return value is not #f
then the file
filename was found, successfully opened, and the contents were
suitable for loading into memory as a device independent bitmap.
WriteDIB
entry of
`DIBUTILS.DLL'. Returns #t
if the file filename could
be opened and written to. After this operation the file contains the
bitmap data in a standard format that is understood by open-dib
and various system utilities like the bitmap editor. Any problems
resulting in failure are signalled by a #f
return value.
BitmapFromDib
entry of `DIBUTILS.DLL'. The returned
value is a device dependent bitmap. The colours from the DIB are
matched against colors in palette.
DibFromBitmap
entry of `DIBUTILS.DLL'.
DibBlt
entry of
`DIBUTILS.DLL'. Similar to the Win32 API BitBlt
call, but
draws a DIB rather than a piece of another device context. Draws the
dib on device context hdc at position (x,y). A
rectangle of width w and height h is copied from position
(src-x,src-y) of dib.
Raster-op is supposed to allow the source and destination to be
combined but I don't think I got this right so stick to SRCCOPY
.
DeleteDIB
entry of `DIBUTILS.DLL'.
Note that the parameter is a handle, and not a dib.
This allows us to destroy a DIB and reclaim its memory by knowing only
the handle value, and not needing the dib
record.
The importance of this is that if the dib
record is GC-ed then a
GC hook can reclaim the storage knowing only the handle.
%delete-dib
to reclaim the storage occupied
by a DIB.
After being deleted, the DIB should not be used.
This procedure allows the programmer to reclaim external heap storage
rather than risking it running out before the next garbage collection.
DibHeight
expand entry of `DIBUTILS.DLL', which returns
the height of the bitmap in pixels.
DibWidth
entry of `DIBUTILS.DLL', which returns
the width of the bitmap in pixels.
CopyBitmap
of `DIBUTILS.DLL', which creates a new
bitmap with the same size and contents as the original.
CreateDIB
entry of `DIBUTILS.DLL'.
Creates a DIB of width by height pixels and depth bits
of colour information.
The style parameter determines how the bitmap is stored.
I have only ever used BI_RGB
.
If depth<=8 then the palette determines the DIB's colour table.
CropBitmap
entry of `DIBUTILS.DLL'.
Returns a new bitmap containing the image from a region of the original.
DIBSetPixelsUnaligned
entry of `DIBUTILS.DLL'. Stuffs
bytes from pixels into the bitmap. There are no alignment
constraints on pixels (the usual way of doing this is to use the
SetDIBits
function which requires that every scan line of the
bitmap is 32-bit word aligned, even if the scan lines are not a multiple
of 4 bytes long). doing this
The `DIBUTILS.DLL' library is an ordinary DLL. See the standard Microsoft Windows documentation on how to create DLLs. Look at the code in the `WIN32/DIBUTILS' directory of the Scheme source.
Please note:
find-module
Scheme function.
Look at `WIN32/DIB.SCM' to see how this is done.
__stdcall
and
__cdecl
calling conventions but not the __fastcall
calling convention.