1(*
2    Title:      Standard Basis Library: Windows signature and structure
3    Author:     David Matthews
4    Copyright   David Matthews 2000, 2005, 2012
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10    
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15    
16    You should have received a copy of the GNU Lesser General Public
17    License along with this library; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19*)
20
21signature WINDOWS =
22sig
23    structure Key :
24    sig
25        include BIT_FLAGS
26        val allAccess : flags
27        val createLink : flags
28        val createSubKey : flags
29        val enumerateSubKeys : flags
30        val execute : flags
31        val notify : flags
32        val queryValue : flags
33        val read : flags
34        val setValue : flags
35        val write : flags
36    end
37    structure Reg :
38    sig
39        eqtype hkey
40        val classesRoot  : hkey
41        val currentUser  : hkey
42        val localMachine : hkey
43        val users        : hkey
44        val performanceData : hkey
45        val currentConfig : hkey
46        val dynData : hkey
47  
48        datatype create_result =
49              CREATED_NEW_KEY of hkey
50            | OPENED_EXISTING_KEY of hkey
51        val createKeyEx : hkey * string * Key.flags -> create_result
52        val openKeyEx : hkey * string * Key.flags -> hkey
53        val closeKey : hkey -> unit
54        val deleteKey : hkey * string -> unit
55        val deleteValue : hkey * string -> unit
56        val enumKeyEx : hkey * int -> string option
57        val enumValue : hkey * int -> string option
58        datatype value =
59              SZ of string
60            | DWORD of SysWord.word
61            | BINARY of Word8Vector.vector
62            | MULTI_SZ of string list
63            | EXPAND_SZ of string
64        val queryValueEx : hkey * string -> value option
65        val setValueEx : hkey * string * value -> unit
66    end
67
68    structure Config:
69    sig
70        val platformWin32s : SysWord.word
71        val platformWin32Windows : SysWord.word
72        val platformWin32NT : SysWord.word
73        val platformWin32CE : SysWord.word
74
75        val getVersionEx: unit ->
76            { majorVersion: SysWord.word, minorVersion: SysWord.word,
77              buildNumber: SysWord.word, platformId: SysWord.word,
78              csdVersion: string }
79
80        val getWindowsDirectory: unit -> string
81        val getSystemDirectory: unit -> string
82        val getComputerName: unit -> string
83        val getUserName: unit -> string
84    end
85
86    structure DDE :
87    sig
88        type info
89        val startDialog : string * string -> info
90        val executeString : info * string * int * Time.time -> unit
91        val stopDialog : info -> unit
92    end
93
94    val getVolumeInformation :
95                string -> {
96                            volumeName : string,
97                            systemName : string,
98                            serialNumber : SysWord.word,
99                            maximumComponentLength : int
100                          }
101
102    val findExecutable : string -> string option
103    val launchApplication : string * string -> unit
104    val openDocument : string -> unit
105    val simpleExecute : string * string -> OS.Process.status
106    type ('a,'b) proc
107    val execute : string * string -> ('a, 'b) proc
108    val textInstreamOf : (TextIO.instream, 'a) proc -> TextIO.instream
109    val binInstreamOf  : (BinIO.instream, 'a) proc -> BinIO.instream
110    val textOutstreamOf : ('a, TextIO.outstream) proc -> TextIO.outstream
111    val binOutstreamOf  : ('a, BinIO.outstream) proc -> BinIO.outstream
112    val reap : ('a, 'b) proc -> OS.Process.status
113
114    structure Status :
115    sig
116        type status = SysWord.word
117        val accessViolation        : status
118        val arrayBoundsExceeded    : status
119        val breakpoint             : status
120        val controlCExit           : status
121        val datatypeMisalignment   : status
122        val floatDenormalOperand   : status
123        val floatDivideByZero      : status
124        val floatInexactResult     : status
125        val floatInvalidOperation  : status
126        val floatOverflow          : status
127        val floatStackCheck        : status
128        val floatUnderflow         : status
129        val guardPageViolation     : status
130        val integerDivideByZero    : status
131        val integerOverflow        : status
132        val illegalInstruction     : status
133        val invalidDisposition     : status
134        val invalidHandle          : status
135        val inPageError            : status
136        val noncontinuableException: status
137        val pending                : status
138        val privilegedInstruction  : status
139        val singleStep             : status
140        val stackOverflow          : status
141        val timeout                : status
142        val userAPC                : status
143    end
144    val fromStatus : OS.Process.status -> Status.status
145    val exit : Status.status -> 'a
146
147end
148
149structure Windows :> WINDOWS =
150struct
151    local
152        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
153    in
154        fun getConst i = SysWord.fromInt(winCall (1006, i))
155    end
156
157    structure Key =
158    struct
159        type flags = SysWord.word
160        fun toWord f = f
161        fun fromWord f = f
162        val flags = List.foldl (fn (a, b) => SysWord.orb(a,b)) 0w0
163        fun allSet (fl1, fl2) = SysWord.andb(fl1, fl2) = fl1
164        fun anySet (fl1, fl2) = SysWord.andb(fl1, fl2) <> 0w0
165        fun clear (fl1, fl2) = SysWord.andb(SysWord.notb fl1, fl2)
166
167        val allAccess : flags = getConst 0
168        val createLink : flags = getConst 1
169        val createSubKey : flags = getConst 2
170        val enumerateSubKeys : flags = getConst 3
171        val execute : flags = getConst 4
172        val notify : flags = getConst 5
173        val queryValue : flags = getConst 6
174        val read : flags = getConst 7
175        val setValue : flags = getConst 8
176        val write : flags = getConst 9
177
178        (* all is probably equivalent to allAccess. *)
179        val all = flags[allAccess, createLink, createSubKey, enumerateSubKeys,
180                        execute, notify, queryValue, read, setValue, write]
181
182        val intersect = List.foldl (fn (a, b) => SysWord.andb(a,b)) all
183    end
184
185    structure Reg =
186    struct
187
188        datatype hkey =
189                PREDEFINED of int
190            |   SUBKEY of int (* Actually abstract. *)
191        val classesRoot  = PREDEFINED 0
192        val currentUser  = PREDEFINED 1
193        val localMachine = PREDEFINED 2
194        val users        = PREDEFINED 3
195        val performanceData = PREDEFINED 4
196        val currentConfig = PREDEFINED 5
197        val dynData      = PREDEFINED 6
198        datatype create_result =
199              CREATED_NEW_KEY of hkey
200            | OPENED_EXISTING_KEY of hkey
201        datatype value =
202              SZ of string
203            | DWORD of SysWord.word
204            | BINARY of Word8Vector.vector
205            | MULTI_SZ of string list
206            | EXPAND_SZ of string
207
208        local
209            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
210            (* Open one of the root keys. *)
211            (* QUESTION: Why is this an option?  The definition asks
212               the same question.  I've removed the option type. *)
213            fun openRoot args =
214                SUBKEY(winCall(1007, args))
215            (* Open a sub-key. *)
216            and openSubKey args =
217                SUBKEY(winCall(1008, args))
218        in
219            fun openKeyEx(PREDEFINED i, s, f) =
220                    openRoot(i, s, SysWord.toInt f)
221            |   openKeyEx(SUBKEY i, s, f) =
222                    openSubKey(i, s, SysWord.toInt f)
223        end
224
225        local
226            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
227
228            fun pairToResult (0, k) = CREATED_NEW_KEY (SUBKEY k)
229             |  pairToResult (_, k) = OPENED_EXISTING_KEY (SUBKEY k)
230
231            (* Open one of the root keys. *)
232            fun createRoot args =
233                pairToResult(winCall(1009, args))
234            (* Open a sub-key. *)
235            and createSubKey args =
236                pairToResult(winCall(1010, args))
237    
238        in
239            (* I've retained the third argument in this interface
240               which used to be used for VOLATILE (1) or
241               NON_VOLATILE (0).  Keys are now always non-volatile. *)
242            fun createKeyEx(PREDEFINED i, s, f) =
243                    createRoot(i, s, 0, SysWord.toInt f)
244            |   createKeyEx(SUBKEY i, s, f) =
245                    createSubKey(i, s, 0, SysWord.toInt f)
246        end
247
248        local
249            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
250        in
251            (* TODO: We wouldn't normally expect to close a
252               predefined key but it looks as though we might
253               have to be able to close HKEY_PERFORMANCE_DATA. *)
254            fun closeKey(PREDEFINED _) = ()
255            |   closeKey(SUBKEY i) =
256                    winCall(1011, i)
257        end
258
259        local
260            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
261
262            fun unpackString v =
263            let
264                val len = Word8Vector.length v
265            in
266                if len = 0 then ""
267                else Byte.unpackStringVec(Word8VectorSlice.slice(v, 0, SOME(len -1)))
268            end
269
270            fun unpackStringList v =
271            let
272                val len = Word8Vector.length v
273                fun unpack start i =
274                    if i >= len orelse Word8Vector.sub(v, i) = 0w0
275                    then if i = start then []
276                    else Byte.unpackStringVec(Word8VectorSlice.slice(v, start, SOME(i - start))) ::
277                            unpack (i+1) (i+1)
278                    else unpack start (i+1)
279            in
280                unpack 0 0
281            end
282
283            fun queryResultToValues(t, v) =
284                (* Decode the type code and the value.  Strings are null terminated so
285                   the last character must be removed. *)
286                case t of
287                    1 => SZ(unpackString v)
288                |   4 => DWORD(PackWord32Little.subVec(v, 0))
289                |   2 => EXPAND_SZ(unpackString v)
290                |   7 => MULTI_SZ(unpackStringList v)
291                |   _ => BINARY v
292                
293            val errorFileNotFound = valOf(OS.syserror "ERROR_FILE_NOT_FOUND")
294        in
295            (* The queryValue functions simply return a type and a vector of bytes.
296               The type code is decoded and the bytes unpacked appropriately. *)
297            fun queryValueEx(key, s) =
298                SOME(queryResultToValues(
299                    case key of
300                        PREDEFINED i => winCall(1012, (i, s))
301                    |   SUBKEY i => winCall(1013, (i, s))
302                    ))
303                    handle ex as OS.SysErr(_, SOME err) =>
304                        if err = errorFileNotFound
305                        then NONE
306                        else raise ex
307        end
308
309        local
310            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
311        in
312            fun deleteValue(PREDEFINED i, s) =
313                    (winCall(1022, (i, s)))
314            |   deleteValue(SUBKEY i, s) =
315                    (winCall(1023, (i, s)))
316        end
317
318        local
319            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
320            fun packString s =
321            let
322                val len = String.size s
323                val arr = Word8Array.array(len+1, 0w0)
324            in
325                Byte.packString(arr, 0, Substring.full s);
326                Word8Array.vector arr
327            end
328
329            fun packStringList sl =
330            let
331                (* The string list is packed as a set of null-terminated strings
332                   with a final null at the end. *)
333                (* TODO: Check for nulls in the strings themselves? *)
334                fun totalSize n [] = n
335                 |  totalSize n (s::sl) = totalSize (n + String.size s + 1) sl
336                val len = totalSize 1 sl
337                val arr = Word8Array.array(len, 0w0)
338                fun pack _ [] = ()
339                  | pack n (s::sl) =
340                    (
341                    Byte.packString(arr, n, Substring.full s);
342                    pack (n + String.size s + 1) sl
343                    )
344            in
345                pack 0 sl;
346                Word8Array.vector arr
347            end
348
349            fun valuesToTypeVal(SZ s) = (1, packString s)
350              | valuesToTypeVal(EXPAND_SZ s) = (2, packString s)
351              | valuesToTypeVal(BINARY s) = (3, s)
352              | valuesToTypeVal(DWORD n) =
353                    let
354                        (* Pack the 32 bit value into an array, then extract that. *)
355                        val arr = Word8Array.array(4, 0w0)
356                    in
357                        PackWord32Little.update(arr, 0, n);
358                        (4, Word8Array.vector arr)
359                    end
360              | valuesToTypeVal(MULTI_SZ s) = (7, packStringList s)
361        in
362            fun setValueEx(key, name, v) =
363                let
364                    val (t, s) = valuesToTypeVal v
365                    val (call, k) =
366                        case key of
367                            PREDEFINED i => (1016, i)
368                        |   SUBKEY i => (1017, i)
369                in
370                    (winCall(call, (k, name, t, s)))
371                end
372        end
373
374        local
375            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
376        in
377            fun enumKeyEx(PREDEFINED i, n) =
378                    (winCall(1018, (i, n)))
379             |  enumKeyEx(SUBKEY i, n) =
380                    (winCall(1019, (i, n)))
381
382            fun enumValue(PREDEFINED i, n) =
383                    (winCall(1020, (i, n)))
384             |  enumValue(SUBKEY i, n) =
385                    (winCall(1021, (i, n)))
386        end
387
388        local
389            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
390            (* In Windows NT RegDeleteKey will fail if the key has subkeys.
391               To give the same behaviour in both Windows 95 and NT we have
392               to recursively delete any subkeys. *)
393            fun basicDeleteKey(PREDEFINED i, s) =
394                    (winCall(1014, (i, s)))
395            |   basicDeleteKey(SUBKEY i, s) =
396                    (winCall(1015, (i, s)))
397        in
398            fun deleteKey(k, s) =
399            let
400                val sk = openKeyEx(k, s, Key.enumerateSubKeys)
401                fun deleteSubKeys () =
402                    case enumKeyEx(sk, 0) of
403                        NONE => ()
404                    |   SOME name => (deleteKey(sk, name); deleteSubKeys())
405            in
406                deleteSubKeys() handle exn => (closeKey sk; raise exn);
407                closeKey sk;
408                basicDeleteKey(k, s)
409            end
410        end
411    end
412    
413    structure DDE =
414    struct
415        type info = int (* Actually abstract. *)
416
417        local
418            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
419        in
420            fun startDialog (service, topic) =
421                winCall(1038, (service, topic))
422        end
423
424        local
425            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
426        in
427            (* The timeout and retry count apply only in the case of
428               a busy result.  The Windows call takes a timeout parameter
429               as the length of time to wait for a response and maybe we
430               should use it for that as well. *)
431            fun executeString (info, cmd, retry, delay) =
432            let
433                fun try n =
434                    if winCall(1039, (info, cmd))
435                    then () (* Succeeded. *)
436                    else if n = 0
437                    then raise OS.SysErr("DDE Server busy", NONE)
438                    else
439                        (
440                        OS.IO.poll([], SOME delay);
441                        try (n-1)
442                        )
443            in
444                try retry
445            end
446        end
447
448        local
449            val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
450        in
451            fun stopDialog (info) = winCall(1040, info)
452        end
453    end (* DDE *)
454
455    (* No (longer?) in Basis library
456    local
457        val winCall = RunCall.run_call2 POLY_SYS_os_specific
458    in
459        fun fileTimeToLocalFileTime t = winCall(1030, t)
460        fun localFileTimeToFileTime t = winCall(1031, t)
461    end
462    *)
463
464    local
465        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
466    in
467        fun getVolumeInformation root =
468        let
469            val (vol, sys, serial, max) =
470                winCall(1032, root)
471        in
472            { volumeName = vol, systemName = sys,
473              serialNumber = SysWord.fromInt serial,
474              maximumComponentLength = max }
475        end
476    end
477
478    local
479        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
480    in
481        fun findExecutable s = SOME(winCall(1033, s)) handle OS.SysErr _ => NONE
482    end
483
484    local
485        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
486    in
487        fun openDocument s = winCall(1034, s)
488    end
489
490    local
491        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
492    in
493        fun launchApplication (command, arg) =
494            winCall(1035, (command, arg))
495    end
496
497    abstype ('a,'b) proc = ABS of int with end;
498
499    (* Run a process and return a proces object which will
500       allow us to extract the input and output streams. *)
501    local
502        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
503    in
504        fun execute(command, arg): ('a,'b) proc = RunCall.unsafeCast(winCall (1000, (command, arg)))
505    end
506
507    local
508        val doIo = RunCall.rtsCallFull3 "PolyBasicIOGeneral"
509    in
510        fun sys_get_buffsize (strm: OS.IO.iodesc): int = doIo(15, strm, 0)
511    end
512
513    local
514        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
515    in
516        fun textInstreamOf p =
517        let
518            (* Get the underlying file descriptor. *)
519            val n = winCall (1001, RunCall.unsafeCast p)
520            val textPrimRd =
521                LibraryIOSupport.wrapInFileDescr
522                    {fd=n, name="TextPipeInput", initBlkMode=true}
523            val streamIo = TextIO.StreamIO.mkInstream(textPrimRd, "")
524        in
525            TextIO.mkInstream streamIo
526        end
527        
528        fun textOutstreamOf p =
529        let
530            val n = winCall (1002, RunCall.unsafeCast p)
531            val buffSize = sys_get_buffsize n
532            val textPrimWr =
533                LibraryIOSupport.wrapOutFileDescr{fd=n, name="TextPipeOutput",
534                    appendMode=false, initBlkMode=true, chunkSize=buffSize}
535            (* Construct a stream. *)
536            val streamIo = TextIO.StreamIO.mkOutstream(textPrimWr, IO.LINE_BUF)
537        in
538            TextIO.mkOutstream streamIo
539        end
540
541        fun binInstreamOf p =
542        let
543            (* Get the underlying file descriptor. *)
544            val n = winCall (1003, RunCall.unsafeCast p)
545            val binPrimRd =
546                LibraryIOSupport.wrapBinInFileDescr
547                    {fd=n, name="BinPipeInput", initBlkMode=true}
548            val streamIo =
549                BinIO.StreamIO.mkInstream(binPrimRd, Word8Vector.fromList [])
550        in
551            BinIO.mkInstream streamIo
552        end
553        
554        fun binOutstreamOf p =
555        let
556            val n = winCall (1004, RunCall.unsafeCast p)
557            val buffSize = sys_get_buffsize n
558            val binPrimWr =
559                LibraryIOSupport.wrapBinOutFileDescr{fd=n, name="BinPipeOutput",
560                    appendMode=false, initBlkMode=true, chunkSize=buffSize}
561            (* Construct a stream. *)
562            val streamIo = BinIO.StreamIO.mkOutstream(binPrimWr, IO.LINE_BUF)
563        in
564            BinIO.mkOutstream streamIo
565        end
566    end
567
568    (* reap - wait until the process finishes and get the result.
569       Note: this is defined to be able to return the result repeatedly.
570       At present that's done by not closing the handle except in the
571       garbage collector.  That could cause us to run out of handles. *)
572    local
573        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
574    in
575        fun reap p = winCall (1005, RunCall.unsafeCast p)
576    end
577
578    local
579        val winCall = RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
580    in
581        (* Run a process and wait for the result.  Rather than do the
582           whole thing as a single RTS call we first start the process
583           and then call "reap" to get the result.  This allows this
584           to be run as a separate ML process if necessary without
585           blocking everything. 
586           This is similar to OS.Process.system but differs in that the
587           streams are directed to NUL and this runs the executable directly,
588           not via cmd.exe/command.com so cannot run DOS commands.
589           OS.Process.system waits for the result within the RTS call so
590           the whole of ML will be blocked until the process completes. *)
591        fun simpleExecute (command, arg) =
592        let
593            val process =
594                winCall(1037, (command, arg))
595        in
596            reap process
597        end
598    end
599
600
601    structure Status =
602    struct
603        type status = SysWord.word
604        
605        val accessViolation        = getConst 10
606        val arrayBoundsExceeded    = getConst 11
607        val breakpoint             = getConst 12
608        val controlCExit           = getConst 13
609        val datatypeMisalignment   = getConst 14
610        val floatDenormalOperand   = getConst 15
611        val floatDivideByZero      = getConst 16
612        val floatInexactResult     = getConst 17
613        val floatInvalidOperation  = getConst 18
614        val floatOverflow          = getConst 19
615        val floatStackCheck        = getConst 20
616        val floatUnderflow         = getConst 21
617        val guardPageViolation     = getConst 22
618        val integerDivideByZero    = getConst 23
619        val integerOverflow        = getConst 24
620        val illegalInstruction     = getConst 25
621        val invalidDisposition     = getConst 26
622        val invalidHandle          = getConst 27
623        val inPageError            = getConst 28
624        (* This was given as nocontinuableException *)
625        val noncontinuableException= getConst 29
626        val pending                = getConst 30
627        val privilegedInstruction  = getConst 31
628        val singleStep             = getConst 32
629        val stackOverflow          = getConst 33
630        val timeout                = getConst 34
631        val userAPC                = getConst 35
632    end
633
634    (* The status is implemented as an integer. *)
635    fun fromStatus (s: OS.Process.status): Status.status =
636        SysWord.fromInt(RunCall.unsafeCast s);
637
638    fun exit (s: Status.status) =
639        OS.Process.exit(RunCall.unsafeCast(SysWord.toInt s))
640
641    structure Config =
642    struct
643        local
644            val winCall: int*unit->int*int*int*int*string =
645                RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
646        in
647            fun getVersionEx () =
648            let
649                val (major, minor, build, platform, version) =
650                    winCall(1050, ())
651            in
652                { majorVersion = SysWord.fromInt major,
653                  minorVersion = SysWord.fromInt minor,
654                  buildNumber = SysWord.fromInt build,
655                  platformId = SysWord.fromInt platform,
656                  csdVersion = version }
657            end
658        end
659
660        local
661            val winCall: int*unit->string =
662                RunCall.rtsCallFull2 "PolyOSSpecificGeneral"
663        in
664            fun getWindowsDirectory () = winCall(1051, ())
665            and getSystemDirectory () = winCall(1052, ())
666            and getComputerName () = winCall(1053, ())
667            and getUserName () = winCall(1054, ())
668        end
669
670        val platformWin32s = getConst 36
671        val platformWin32Windows = getConst 37
672        val platformWin32NT = getConst 38
673        val platformWin32CE = getConst 39
674    end
675end;
676
677local
678    (* Add pretty printers to hide internals. *)
679    fun prettyRegKey _ _ (_: Windows.Reg.hkey) = PolyML.PrettyString "?"
680    and prettyDDEInfo _ _ (_: Windows.DDE.info) = PolyML.PrettyString "?"
681    and prettyProc _ _ (_: ('a, 'b) Windows.proc) = PolyML.PrettyString "?"
682in
683    val () = PolyML.addPrettyPrinter prettyRegKey
684    and () = PolyML.addPrettyPrinter prettyDDEInfo
685    and () = PolyML.addPrettyPrinter prettyProc
686end;
687