Go to the previous, next section.
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.
special form+: define-windows-type name check convert return revert
special form+: define-similar-windows-type name model [check [convert [return [revert]]]]
Both forms define a windows type.
The first form defines a type in terms of its aspects as described
above.
The second defines the type as being like another type, except for
certain aspects, which are redefined.
Name is the name of the type.
Model is the name of a type.
Check, convert, return and revert are
procedures or the value #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 )
The type which is not checked and undergoes only the basic conversion
from a Scheme integer to a C integer or from a Scheme string to a C
pointer to the first byte of the string.
Returned unchecked
values are returned as integers.
Scheme booleans are analogous to C 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)
Scheme characters are converted into C objects of type char
,
which are indistinguishable from small integers.
Various integer types that are passed without conversion.
A string that is passed as a C pointer of type char*
to the first
character in the string.
A string or #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).
Various kinds of Win32 handle. These names correspond to the same, but
all uppercase, names in the Windows C language header files. Win32 API
calls are the source of values of this type and the values are
meaningless except as arguments to other Win32 API calls. Currently
these values are represented as integers but we expect that Win32
handles will in future be represented by allocated Scheme objects
(e.g. records) that will allow predicates (e.g. hmenu?
) and
sensible interlocking with the garbage collector to free the programmer
of the current tedious allocation and deallocation of handles.
A Windows resource identifier is either a small integer or a string. In C, this distinction is possible because pointers look like larger integers, so a machine word representing a small integer can be distinguished from a machine word that is a pointer to the text of the name of the resource.
Foreign procedures are declared as callable entry-points in a module, usually a dynamically linked library (DLL).
Returns a module suitable for use in creating procedures with
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).
This variable is bound to the module describing the `GDI32.DLL'
library, which contains the Win32 API graphics calls, e.g.
LineTo
.
This variable is bound to the module describing the `KERNEL32.DLL' library.
This variable is bound to the module describing the `USER32.DLL'
library. This module contains many useful Win32 API procedures, like
MessageBox
and SetWindowText
.
special form+: windows-procedure (name (parameter type) ...) return-type module entry-name [options]
This form creates a procedure, and could be thought of as
"foreign-named-lambda". The form creates a Scheme procedure that
calls the C procedure identified by the exported entry point
entry-name in the module identified by the value of module.
Both entry-name and module are evaluated at procedure
creation time, so either may be expression. Entry-name must
evaluate to a string and module must evaluate to a module as
returned by 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
:
((access error ()) "Whine moan" ...)
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.
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 C world a DIB is a handle to a piece of memory containing the bits that represent information about the image and the pixels of the image. The handle is a machine-word sized piece of data which may be thought of as a 32 bit integer. The handle may be null (i.e. zero), indicating that there is no block of memory describing the DIB. The null value is usually returned by C functions that are supposed to create a DIB but failed, for some reason like the memory could not be allocated or a file could not be opened.
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
.
procedure+: open-dib (filename string)
Return type: dib. Calls the 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.
procedure+: write-dib (filename string) (dib dib)
Return type: bool. Calls the 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.
procedure+: bitmap-from-dib (dib dib) (palette hpalette)
Return type: hbitmap.
Calls the BitmapFromDib
entry of `DIBUTILS.DLL'. The returned
value is a device dependent bitmap. The colours from the DIB are
matched against colors in palette.
procedure+: dib-from-bitmap (bitmap hbitmap) (style dword) (bits word) (palette hpalette)
Return type: dib.
Returns a DIB containing the same image as the device dependent bitmap
bitmap.
Style determines the kind of DIB, e.g. compression style.
Calls the DibFromBitmap
entry of `DIBUTILS.DLL'.
procedure+: dib-blt (dest hdc) (x int) (y int) (w int) (h int) (src dib) (src-x int) (src-y int) (raster-op long)
Return type: bool. Calls the 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
.
procedure+: %delete-dib (dib-handle handle)
Return type: bool.
Calls the 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.
procedure+: delete-dib (dib dib)
Return type: bool.
This procedure calls %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.
procedure+: dib-height (dib dib)
Return type: int.
Calls the DibHeight
expand entry of `DIBUTILS.DLL', which returns
the height of the bitmap in pixels.
procedure+: dib-width (dib dib)
Return type: int.
Calls the DibWidth
entry of `DIBUTILS.DLL', which returns
the width of the bitmap in pixels.
procedure+: copy-bitmap (bm hbitmap)
Return type: hbitmap.
Calls the CopyBitmap
of `DIBUTILS.DLL', which creates a new
bitmap with the same size and contents as the original.
procedure+: create-dib (width int) (height int) (style int) (depth int) (palette hpalette)
Return type: dib.
Calls the 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.
procedure+: crop-bitmap (bm hbitmap) (left int) (top int) (right int) (bottom int)
Return type: hbitmap.
Calls the CropBitmap
entry of `DIBUTILS.DLL'.
Returns a new bitmap containing the image from a region of the original.
procedure+: dib-set-pixels-unaligned dib (pixels string)
Return type: bool.
Calls the 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.
Go to the previous, next section.