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
environment, and do not appear as bindings in the user or root
An effect of this is that it is far easier to develop Win32 software in
(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:
#tif the argument is of an acceptable Scheme type, otherwise returns
#f. The check procedure is used for type-checking.
#fmeans 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
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 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 )
uncheckedvalues are returned as integers.
1. Windows type
boolhave 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.
#fis 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,
LoadLibraryWin32 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).
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 (
The procedure does this by calling the
SetWindowText entry in the
module that is the value of the variable
set-window-title is defined to have this procedure as
(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:
If both options (i.e.
with-reversions and Scheme code) are used,
with-reversions must appear first. There can be arbitrarily many
This section is a moving target.
#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
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:
#defined to 0, which
shadows the scheme procedure
error in the root package
environment. To signal an error, use
access to get
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:
Isfinally have a question-mark appended.
Example: applying these rules to
GetDC is translated into
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
OpenDIBentry of `DIBUTILS.DLL'. If the return value is not
#fthen the file filename was found, successfully opened, and the contents were suitable for loading into memory as a device independent bitmap.
WriteDIBentry of `DIBUTILS.DLL'. Returns
#tif 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-diband various system utilities like the bitmap editor. Any problems resulting in failure are signalled by a
BitmapFromDibentry of `DIBUTILS.DLL'. The returned value is a device dependent bitmap. The colours from the DIB are matched against colors in palette.
DibFromBitmapentry of `DIBUTILS.DLL'.
DibBltentry of `DIBUTILS.DLL'. Similar to the Win32 API
BitBltcall, 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
DeleteDIBentry 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
dibrecord. The importance of this is that if the
dibrecord is GC-ed then a GC hook can reclaim the storage knowing only the handle.
%delete-dibto 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.
DibHeightexpand entry of `DIBUTILS.DLL', which returns the height of the bitmap in pixels.
DibWidthentry of `DIBUTILS.DLL', which returns the width of the bitmap in pixels.
CopyBitmapof `DIBUTILS.DLL', which creates a new bitmap with the same size and contents as the original.
CreateDIBentry 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.
CropBitmapentry of `DIBUTILS.DLL'. Returns a new bitmap containing the image from a region of the original.
DIBSetPixelsUnalignedentry 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
SetDIBitsfunction 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.
find-moduleScheme function. Look at `WIN32/DIB.SCM' to see how this is done.
__cdeclcalling conventions but not the