home *** CD-ROM | disk | FTP | other *** search
/ CD-ROM Magazin 1995 July/August / CDROMMAG.ISO / share / tool / winversi / win_ver.c < prev    next >
Text File  |  1995-04-17  |  83KB  |  2,194 lines

  1.  
  2. /*
  3.    Copyright (c) 1993-1995, UK, John M.Howells
  4.  
  5.    CIS ID: 100031,353
  6.  
  7.    A whole slew of terms such as Microsoft, MS, MS-DOS, Windows,
  8.    Win32, Win32s, Windows NT, Visual C++, Borland, etc., are
  9.    probably trademarks of someone or other.
  10.  
  11.    If you have ever read any disclaimer about using the software at
  12.    your own risk, and such, then assume you read it here also.
  13.  
  14.    Program to display the version of Windows on which it is run.
  15.    The program can be compiled for both 16-bit and 32-bit versions,
  16.    and although most of the code is unique to one version or the
  17.    other it's easier to keep it all together. And its operation is
  18.    quite crude, not registering a Window, and such, but displaying
  19.    the results in a Message Box.
  20.  
  21.    On its own the program is not very useful, and the source is
  22.    presented here for the benefit of others writing programs that
  23.    need to know the version of Windows their programs are running
  24.    on, as it tries to collect together the many documents produced
  25.    by Microsoft, and other techniques, into a single working example.
  26.  
  27.    You may think this is all way over the top, and so it is, but it
  28.    just shows how many ways I have so far found to determine the
  29.    version of Windows hosting a program.
  30.  
  31.    It will report:
  32.  
  33.     16-bit version:     either Windows 3.1 'proper' or WOW (i.e. Windows on
  34.              Windows, the 16-bit emulation provided by Windows NT)
  35.              and Windows 95. if running on WOW it also reports the
  36.              version and build of the underlying OS. if running on
  37.              a system with Win32s it also reports the version and
  38.              build. On Windows NT it will try and report the service
  39.              pack identity. On a 16-bit host it will report the file
  40.              version of the USER.EXE file.
  41.  
  42.    or
  43.  
  44.     32-bit version:     either Windows NT 'proper' or Windows 95 (aka Chicago)
  45.              or Win32s (the extension to allow 32-bit programs to
  46.              run on the 16-bit version of Windows 3.1x). if Windows
  47.              NT is the host OS whether it is the Workstation product
  48.              or a Server.
  49.  
  50.    The source of the information is as follows:
  51.  
  52.     Detect Windows-on-Windows            Knowledgebase Articles Q92395
  53.                               (as at 95-02-03) and Q96404
  54.  
  55.     Detect presence of Win32s            Knowledgebase Articles Q92395
  56.         from a 32-bit program              (as at 95-02-03) and Q96404
  57.  
  58.     Detect the Version of Win32s            Win32s Programmer's Reference
  59.         from a 16-bit program
  60.  
  61.     Detect other Version information        Knowledgebase Article Q92395
  62.                               (as at 95-02-03)
  63.  
  64.     Detect version reported to a DOS program    DPMI specification
  65.  
  66.     Detect type and version of 32-bit host        on-line help for GetVersionEx()
  67.  
  68.     Detect version of underlying Windows NT 3.1    from information in NT 3.5 SDK CD
  69.         from 16-bit program              \SCT\SAMPLES\INTEROP\README.TXT
  70.                               & Knowledgebase Article Q114341
  71.  
  72.     Detect version of underlying Windows NT 3.5    from information in NT 3.5 SDK CD
  73.         from 16-bit program              \DOC\MISC\GENTHUNK.TXT
  74.                               & Knowledgebase Article Q104009
  75.  
  76.     Detect Windows for Workgroups is running    Knowldegebase Article Q114470
  77.  
  78.     Detect file version from USER.EXE for          based on Knowledgebase Article
  79.         WFWG (&Win) 3.11              Q113892
  80.  
  81.     Distinguish Windows NT Workstation        Note from John Lawniczak of
  82.         from Windows NT Server              Microsoft Developer Support
  83.                               in the MSWIN32 forum on CIS
  84.  
  85.     Determine Workgroups from WINVER.EXE        My own guess (!) after seeing output
  86.                                from the 16-bit CONFIGSP that it
  87.                                called a "Windows Version String"
  88.  
  89.     Determine Windows NT 3.1 Service Pack        My own code, and examining which
  90.                                key the WINMSD program uses
  91.  
  92.     Detect if Win32s is running            All my own work
  93.  
  94.     Detect numeric Service Pack number        All my own work
  95.         (CSD Version) for Windows NT 3.5
  96.  
  97.     Detect Windows 95 from 16-bit mode        Programmer's Guide for Windows 95
  98.                                states Generic Thunk available.
  99.  
  100.    According to Pietrek, in MSJ Extra (UK edition), October '94, Page 24,
  101.    in the Beta for Windows 95 [aka Windows 4.x, aka Chicago] GetVersion()
  102.    returns with the top two bits (31 and 30) set, so in a 32-bit program
  103.    code of the form:
  104.  
  105.     DWORD version = GetVersion();
  106.  
  107.     if( 0 == ( version & 0x80000000 ) )            // bit 31 not set
  108.     {
  109.         // it's NT
  110.     }
  111.     else
  112.     {
  113.         // it's not NT
  114.         if( ( version & 0xC0000000 ) == 0xC0000000 )    // bits 31 & 30 set
  115.         {
  116.             // it's Chicago
  117.         }
  118.            else
  119.            {
  120.                // it's Win32s
  121.            }
  122.        }
  123.  
  124.    will detect the platform, but we do not use that here, because Pietrek
  125.    notes that the meaning of bit 30 may change [even though he then uses
  126.    this code to detect Win32s in the API Spy program in the December '94
  127.    issue (UK edition) of MSJ] and instead we use code recommeded by Robert
  128.    Hess of MS in the March 1994 issue of the MSDN newsletter, but only
  129.    after GetVersionEx() has failed.
  130.                                 
  131.    When the program is running on a 16-bit version of Windows 3.1x or later
  132.    it uses the DOS interface to check the version of Windows. It does this
  133.    because Windows for Workgroups 3.11 tells lies (see MS Knowledgebase
  134.    articles Q106271 and Q113892), and reports that it is Windows 3.10, but
  135.    the DOS interface returns the correct version number. Even this does not
  136.    work on Windows 3.11 (*not* Windows for Workgroups 3.11), as both
  137.    interfaces, Windows and DOS, report 3.10, so the string from USER.EXE
  138.    is also obtained! Also, the recommended File Version from USER.EXE is
  139.    now reported.
  140.  
  141.    <PERSONAL OPINION ON>
  142.  
  143.    Development of this mixed-mode program is easier under the Borland environment,
  144.    in my opinion, because the GUI can be used under 16-bit and 32-bit Windows to
  145.    compile both 16-bit and 32-bit versions of the program, whereas the 32-bit GUI
  146.    from Microsoft only works under WIndows NT, and you have to resort to the
  147.    command-line tools to build a 32-bit version under 16-bit Windows (though even
  148.    that is not possible with Visual C++ 2) and you have to develop the program in
  149.    effectively four modes, since the results are different from 16-bit and 32-bit
  150.    versions of the program for the 16-bit and 32-bit OS's.
  151.    
  152.    <PERSONAL OPINION OFF>
  153.  
  154.    Modification History:
  155.  
  156.    1.0    94-03-31    JMH    Initial Version
  157.  
  158.    1.1    94-04-28    JMH    There are only two significant corrections. The first
  159.                 is for a silly mistake in omitting to initialize the
  160.                 bld_id string, though the original worked by accident!
  161.                 The other correction is for a better check for Version
  162.                 3.10 or later. The previous version checked for the
  163.                 major version greater than 3 and the minor version
  164.                 greater than 10, which is not *quite* correct. The
  165.                 other changes are cosmetic. Changed the check to set up
  166.                 bld_id so no 'else' is needed.
  167.  
  168.                 Corrected the spelling of 'both'. Changed the protected
  169.                 mode check in the 16-bit function to check for 3.10
  170.                 version (3.11?) from the use of a DPMI function to use
  171.                 of the GetWinFlags() function.
  172.  
  173.                 Changed the 32-bit check for NT to use a macro, rather
  174.                 than an embedded constant.
  175.  
  176.                 Defined a macro    for the cases where the DOS version
  177.                 check returns an 'unknown' result.
  178.  
  179.                 Added 'argsused' pragma for WinMain() when compiling
  180.                 with Borland C++ 4.0.
  181.  
  182.                 Corrected spelling of 'Windows'!
  183.  
  184.                 Added the mode (Enhanced/Standard/Real) to the output
  185.                 in 16-bit mode.
  186.  
  187.    1.2    94-05-09    JMH    Modified to report the underlying OS version number
  188.                 when running on Windows NT. On the Windows NT 3.5
  189.                 (aka Daytona) Beta (build 612) the WOW GetVersion
  190.                 returns 3.10, presumably to kid 16-bit applications
  191.                 that they are running on 3.10, in case they might
  192.                 get confused if    they saw 3.50 returned. Here the
  193.                 underlying 32-bit version of GetVersion is called,
  194.                 as described on the Daytona Beta 612 CD in the
  195.                 \SCT\SAMPLES\INTEROP directory,    to get the true
  196.                 underlying Windows NT OS version and build.
  197.  
  198.    1.3    94-08-10    JMH    Modified to report the Build identity from USER.EXE
  199.                 on Windows 3.10 and later. The Windows 3.11 OS/2 kicker
  200.                 (*NOT* WFWG 3.11) still reports 3.10 to both Windows
  201.                 and DOS programs that ask what version, but the build
  202.                 string in USER.EXE actually says 3.11 correctly. Ain't
  203.                 it clever! The program cannot analyze the string and
  204.                 not print it out if it is the same as the version
  205.                 reported by GetVersion(), as it contains (incorrectly
  206.                 in my opinion) the string "3.1" and not "3.10". Also
  207.                 changed the string for the "True" version report to
  208.                 say "DOS reports ..." because who knows what may be
  209.                 going on in future versions! Are there any more places
  210.                 where a version    number is made available? Rearranged
  211.                 the output.
  212.  
  213.    1.4    94-10-27    JMH    Added the 32-bit GetVersionEx() interface to both
  214.                 16-bit and 32-bit versions. In each case the program
  215.                 does not assume that the function exists, so we have
  216.                 to use LoadLibrary(), and so on, to find the function
  217.                 and do run-time linking to it. The GetversionEx()
  218.                 function was not present in Windows NT 3.1 or in
  219.                 version 1.10 of Win32s, but is present in Windows
  220.                 NT 3.5, and in Version 1.15 of Win32s.
  221.  
  222.                 Changed the 16-bit version so that it detects whether
  223.                 or not the underlying OS supports the 32-bit thunk
  224.                 interface when trying to work out whether we are on a
  225.                 32-bit OS for getting underlying version information,
  226.                 rather than relying on any flags. so that we *should*
  227.                 be independent of any particular OS, though probably
  228.                 it will only be valid on Windows NT.
  229.  
  230.                 Changed the flow of the code slightly so that for each
  231.                 of the versions all output goes through a single call
  232.                 to MessageBox().
  233.  
  234.                 Used conditional compilation to    allow the 'basic'
  235.                 version of the interfaces to be    tested on systems that
  236.                 support the Extended interface.
  237.  
  238.                 Changed so that the 16-bit version always reports the
  239.                 DOS version, if available, when running on 16-bit host.
  240.  
  241.                 Altered the Base Address of the Microsoft-built 32-bit
  242.                 version to 400000 (the Borland tools already have this
  243.                 address as the default) to make the program a bit more
  244.                 Chicago    compatible.
  245.  
  246.                 Who would believe 'they' could invent so many ways of
  247.                 finding a simple thing like a version number! Must
  248.                 remember to get dates right this time!
  249.  
  250.    1.5    94-11-06    JMH    Slight change for compatibility with Visual C++ 2.0
  251.                 (and presumably later versions of other tool suites),
  252.                 to only define the OSVERSIONINFOA structure and its
  253.                 associated VER_PLATFORM_WIN32... #defines when the
  254.                 VER_PLATFORM_WIN32_NT macro is not already defined.
  255.  
  256.                 Added version number to Dialog Box text.
  257.  
  258.                 Added a header file for common version information.
  259.  
  260.    1.6    95-01-01    JMH    Changed name in output from Chicago to Windows 95.
  261.                 Should have done this for previous version! Silly me!
  262.  
  263.                 Added code in 16-bit version to try and detect Windows
  264.                 for Workgroups (see below), even when NT is the host,
  265.                 though on NT the network always appears to be running
  266.                 to a 16-bit program.
  267.  
  268.                 Changed the method used to report the 32-bit platform
  269.                 name when GetVersionEx() exists so that cases other
  270.                 than those recognized at the time of writing are
  271.                 reported.
  272.  
  273.                 Minor changes to a couple of comments to try and
  274.                 clarify the way things are done.
  275.  
  276.                 Corrected comment for origin of detection of underlying
  277.                 version of NT 3.5, and alter both underlying detection
  278.                 comments to reflect the final SDK issue.
  279.  
  280.                 Changed the name of the function pointer in the 16-bit
  281.                 program that holds the address of the function that
  282.                 gets the Win32s version information to more closely
  283.                 match that of the actual function name, and slightly
  284.                 simplified its use, as modern compilers don't have to
  285.                 be told exactly when to use a pointer to call a function.
  286.  
  287.                 Changed field names in the WIN32SINFO structure to those
  288.                 used in the Win32s Programmer's Reference.
  289.  
  290.                 Deleted pointer to extended version informnation in
  291.                 16-bit and 32-bit code.
  292.  
  293.                 Added File Version information, which is the method that
  294.                 is recommended by Microsoft to get the true version in
  295.                 Knowledgebase article Q113892, to all the other stuff,
  296.                 from USER.EXE for 16-bit and USER32.DLL for 32-bit.
  297.  
  298.                 Corrected a good many format strings to use %u for
  299.                 version info not %d, as pedantically the numbers are
  300.                 (D)WORD's, and therefore unsigned.
  301.  
  302.                 Added report of debug or retail version of Win32s to
  303.                 16-bit version.
  304.  
  305.                 changed the name bld_id to dos_bld_id for clarity.
  306.  
  307.                 Corrected an error in the name of the Windows 95
  308.                 definition for the platform.
  309.  
  310.                 Added a check for which version of Windows NT,
  311.                 Workstation or Server, and made the 32-bit version
  312.                 UNICODE/ASCII portable. NOTE, a UNICODE build does
  313.                 not make any sense as a released version, since the
  314.                 result would only run on Windows NT (and hardly
  315.                 therefore qualifies as a portable 32-bit version
  316.                 detector!) but it is done as a guide to those who
  317.                 know their programs are going to run only on NT
  318.                 and want to use UNICODE.
  319.  
  320.                 Changed slightly the local definition of
  321.                 OSVERSIONINFO to make it UNICODE/ASCII portable.
  322.  
  323.                 I don't know the difference between the _UNICODE and
  324.                 UNICODE defines, and when to use each, so if only
  325.                 _UNICODE is defined the program defines    UNICODE.
  326.                 *NOTE* that I have only tested this code on Windows
  327.                 NT Workstation 3.5 release version, which does *not*
  328.                 have a string in the szCSDVersion field, so I *hope*
  329.                 I have got that right, and the correct strings for a
  330.                 Server. Note that the program does not distinguish
  331.                 the names used for NT 3.1 (nothing and Advanced
  332.                 Server) from those for NT 3.5 (Workstation and
  333.                 Server), but uses the NT 3.5 names only. I cannot
  334.                 find the Workstation/Server key and its value in the
  335.                 NT 3.1 Resoruce Kit documentation to confirm the
  336.                 values used here.
  337.  
  338.                 The 16-bit version does *not* try and get at the
  339.                 registry to determine if the NT host is a Workstation
  340.                 or a Server because the master key needed is not
  341.                 readily available to a 16-bit program.
  342.  
  343.                 The projects do not use pre-compiled headers only
  344.                 because several sets for Borland and MS gets rather
  345.                 large.
  346.  
  347.                 Changed the output format for 32-bit versions so
  348.                 that all now go <platform> <version> <build>
  349.                 consistently.
  350.  
  351.                 Added the reasons for failure to the 16-bit Win32s
  352.                 report if the GetWin32sInfo function is found but
  353.                 returns a non-zero result.
  354.  
  355.                 Changed the name of WV_WINNT to WV_NO_WINNT to
  356.                 properly reflect the meaning of the TRUE state of
  357.                 the flag.
  358.  
  359.    1.7    95-02-07    JMH    Changed the 'wfwg_....' string pointers to 'far' to
  360.                 overcome an apparent bug in the Borland C++ 4.5
  361.                 compiler, which gets something wrong in the output
  362.                 from the wsprintf() statement noted. However, it
  363.                 does not take much to put it right, as almost any
  364.                 reference to a string corrects the problem. The
  365.                 point of change and the point where it went wrong
  366.                 are noted by comments starting ++++. Fortunately
  367.                 the Microsoft Visual C++ 1.51 compiler does not
  368.                 mind if they are declared far or not.
  369.  
  370.                 In the 16-bit version when not running under
  371.                 Windows NT add the code to extract a version string
  372.                 (but *NOT* a part of the version resource) from the
  373.                 WINVER.EXE file. Even for versions of WINVER.EXE
  374.                 for Windows NT 3.1 and 3.5 (though they are not used
  375.                 here) this always appears to be at offset 0x0207 in
  376.                 the WINVER.EXE file, though this is only determined
  377.                 by inspection! The program does a quick confidence
  378.                 test, just in case!! This *appears* to be what the
  379.                 16-bit version of MS' CONFIGSP program from the MSDN
  380.                 CD does, but I have not tried to trace this through
  381.                 to confirm it! However, on Windows 3.11 this reports
  382.                 the version as 3.10, so while it may seem to be
  383.                 good way of working out whether this is    a Workgroups
  384.                 installation it is *NO* good for the version. Who is
  385.                 the twit in MS who decides these things? There is no
  386.                 need to use this code when Windows NT is running
  387.                 (though that could be said about getting the file
  388.                 version from USER.EXE as well) as the program generic
  389.                 thunks through    to get the true    underlying version.
  390.  
  391.                 Now that I have Service Pack 1 for Windows NT 3.5 I
  392.                 can see that the szCSDVersion string was being
  393.                 handled properly for both ASCII and UNICODE.
  394.  
  395.                 For 32-bit version leave the code to determine the
  396.                 File Version behind but conditionally compile it
  397.                 out of the build (but with a correction to report
  398.                 the USER32.DLL file and not USER.EXE as before).
  399.  
  400.                 Corrected a minor, and extermely unlikely, bug in
  401.                 the 16-bit code if the GetSystemDirectory were to
  402.                 fail on the second call after the first call had
  403.                 succeeded.
  404.  
  405.                 In the 16-bit version moved USER.EXE file version
  406.                 detection so that it is not executed when Windows
  407.                 NT is the host.
  408.  
  409.                 Added a pointer to the OSVERSIONINFO definition
  410.                 and a function type for GetVersionEx in the 32-bit
  411.                 code.
  412.  
  413.                 Made the UNICODE definition only apply to 32-bit
  414.                 programs.
  415.  
  416.                 In the 16-bit output moved the DOS version up to
  417.                 just after the version reported by GetVersion().
  418.  
  419.                 Output the Service Pack [aka CSD=Corrective
  420.                 Service Diskette] number in the 32-bit version
  421.                 of the program for the basic GetVersion interface.
  422.                 This is the nearest that Windows NT 3.1 has to the
  423.                 szCSDVersion string returned on Windows NT 3.5 in
  424.                 the GetVersionEx() data structure. This should
  425.                 only be needed on Windows NT 3.1, because the
  426.                 extended interface does the job on NT 3.5, and
  427.                 while this item does exist on NT 3.5 it is a
  428.                 string when we are looking for the number used
  429.                 on NT 3.1, so we allow for it being either type,
  430.                 and for its absence, when the basic interface is
  431.                 tested. Note that this entry in the registry is
  432.                 not *quite* where GetVersionEx() gets its string
  433.                 from, but this string in the registry is updated
  434.                 to reflect the GetVersionEx() string when the
  435.                 system boots up.
  436.  
  437.                 In the DPMI code that gets the version of Windows
  438.                 that a DOS application sees clear the register
  439.                 set structure to zeroes, just in case.
  440.  
  441.                 Changed 'temp' name to 'win32s_ver' for clarity.
  442.  
  443.                 Moved Windows for Workgroups check in the 16-bit
  444.                 version so that it is only executed when 16-bit
  445.                 Windows is the host as it always indicates that
  446.                 the Windows for Workgroups network is running,
  447.                 even on a stand-alone system with no network
  448.                 whatsoever, when Windows NT is the host OS.
  449.  
  450.                 Corrected the code fragment in this comment, about
  451.                 detecting Win32s/Chicago/NT, etc. as there was one
  452.                 too few 0's in each constant.
  453.  
  454.                 Succumbed to the tempptation, and added even
  455.                 more over the top code to get the type of Windows
  456.                 NT host for a 16-bit program, using a Generic
  457.                 Thunk to the underlying 32-bit functions that
  458.                 access the Registry, requiring a slight change to
  459.                 the 32-bit code. Note that for this we use the
  460.                 CallProc32W function, rather than CallProcEx32W,
  461.                 because the former works on both Windows NT 3.1,
  462.                 while only Windows NT 3.5 supports the latter.
  463.                 Because CallProc32W is a PASCAL function it is
  464.                 not easy to define an appropriate prototype, so
  465.                 there are actually three different versions of
  466.                 it defined here, each taking a different number
  467.                 of parameters, all of which are mapped by the
  468.                 DEF file to the single CallProc32W function!
  469.  
  470.                 Added a function prototype for the Win32sInfo
  471.                 function that get the Win32s version.
  472.  
  473.                 Changed 16-bit version to use STRICT compliance,
  474.                 which required two module handles to be changed
  475.                 from HANDLE to HINSTNACE.
  476.  
  477.                 Slightly changed access to the Windows NT
  478.                 registry from the 16-bit version, and used it to
  479.                 add further over the top code to add the service
  480.                 pack data to the 16-bit version when Windows NT
  481.                 is the host.
  482.  
  483.                 Split the change comments so that each change
  484.                 has its own paragraph.
  485.  
  486.                 Changed WORKSTATION_... (KEY and VAL) to the more
  487.                 appropriate PRODUCTTYPE_... (KEY and VAL).
  488.  
  489.                 Added a #define for the length of the string
  490.                 returned from the registry for the type of the
  491.                 Windows NT host.
  492.  
  493.    1.8    95-03-15    JMH    Cut definitions for OSVERSIONINFO as they are not
  494.                 needed.
  495.  
  496.                 Added code to the 32-bit version to parse the
  497.                 version    from the Win32s.INI file if the GetVersionEx
  498.                 function is not available. This is what happens on
  499.                 version 1.10 and before, though I have never seen
  500.                 version 1.0, and even Microsoft do not seem to know
  501.                 where a copy was distributed. To get this to compile
  502.                 for UNICODE (which is silly anyway!) required some
  503.                 fiddling about with the header files and translated
  504.                 function names, and the UNICODE version does not
  505.                 compile on Borland C++ 4.5, but only the Win32s bit
  506.                 which is silly as UNICODE anyway!
  507.  
  508.                 Added code to the 16-bit version to detect whether
  509.                 Win32s is actually running. Clearly this makes no
  510.                 sense in the 32-bit version! Presumably a program
  511.                 that requires Win32s should only do an installation
  512.                 when Win32s is not actually running, though if the
  513.                 correct version of Win32s is already present the
  514.                 Win32s bit of the installation can, of course, be
  515.                 skipped.
  516.  
  517.                 Added numeric CSD Version to 16-bit and 32-bit code
  518.                 when Windows NT is the host. This value does not exist
  519.                 on Windows NT 3.1, where the CSD Version under the
  520.                 CurrentVersion key is numeric. This should allow
  521.                 an application that depends on a particular service
  522.                 pack level to make sure that the system is already
  523.                 configured appropriately.
  524.  
  525.                 minor rearrangement of code to always use reg32_access
  526.                 function for access to registry. a different form of
  527.                 the function is provided for each of 16-bit and 32-bit
  528.                 programs.
  529.  
  530.    1.9    95-04-18    JMH    Changed 32-bit Visual C++ 2 project settings for
  531.                    compatibility with the Visual C++ 2.1 subscription
  532.                    release by adding 3.10 to the /subsystem:windows
  533.                    flag so that Windows NT 3.1 can run the program.
  534.  
  535.                 Added the Windows 95 definition if the header files
  536.                 have not already defined it, and removed conditional
  537.                 compilation tests for it.
  538.  
  539.                 Used only bottom WORD of the GetVersionEx result for
  540.                 the build number, as on Windows 95 (I've got a preview
  541.                 copy at last - on 10th April 1995!) this appears to
  542.                 put the version number into the top word, as on build
  543.                 347 it contains 0x0400015B ( 4.0.347 ?? ).
  544.  
  545.                 Also, on Windows 95 the Generic Thunk interface works
  546.                 (see Article 32 in the Windows 95 Programmer's Guide
  547.                 from the Windows 95 SDK, though the GENTHUNK.TXT file
  548.                 on the same CD still states that only Windows NT will
  549.                 support Generic Thunks), so much to my surprise    a
  550.                 16-bit program on Windows 95 can get at the underlying
  551.                 32-bit GetVersionEx and GetVersion functions. However,
  552.                 under Windows 95 the build number is not present in the
  553.                 GetVersion response, so modified the 16-bit output
  554.                 preperation when the Generic Thunk works, so that under
  555.                 Windows 95 the stuff that is exclusive to Windows NT is
  556.                 not produced.
  557.  
  558.                 Changed the 16-bit program slightly so that it refers
  559.                 to WOW (Windows On Windows) only when running on
  560.                 Windows NT, otherwise just refer to Win16.
  561.                 
  562.                 Added the DOS version number to the 16-bit program
  563.                 running    under Windows 95.
  564.  
  565.    Further notes:
  566.  
  567.    The results from the 16-bit version
  568.    ===================================
  569.  
  570.     Windows                  Windows  Windows    WFWG   Windows  Windows  Windows
  571.     Version                    3.10     3.11     3.11    NT 3.1   NT 3.5     95
  572.  
  573.     File Version               3.10     3.11     3.11     3.10     3.10
  574.     GetVersion()               3.10     3.10     3.10     3.10     3.10     3.95
  575.     DOS               3.10     3.10     3.11
  576.     Build Id                   3.10     3.11     3.11
  577.     WINVER.EXE string          3.10     3.10     3.11
  578.     Gen Thunk to GetVersion                               3.10     3.50     4.00
  579.     Gen Thunk to GetVersionEx                             3.10     3.50     4.00
  580.  
  581.     It can be seen that (at the time of writing!) the most reliable method
  582.     for getting the correct version number of the host OS as binary data
  583.     (as opposed to the string provided by the Build ID) is to first check
  584.     for the availability of Generic Thunk support and then use the File
  585.     Version information from USER.EXE on the 16-bit versions of Windows,
  586.     as recommended in Q113892, and the Thunk layer where available, where
  587.     the GetVersion is simpler as it applies to all versions of Windows NT,
  588.     whereas the GetVersionEx thunk is only available on Windows NT 3.5,
  589.     so you have to be prepared to drop back to the GetVersion thunk if that
  590.     fails.
  591.     
  592.     For the installation of Win32s programs should always use the 16-bit
  593.     function from a 16-bit program, as Win32s may not yet be installed.
  594.     The information returned appears to be a reliable indication of the
  595.     version of Win32s, and is consistent with the information returned
  596.     by the 32-bit family GerVersionEx function available in later versions.
  597.  
  598.    Windows 95 (aka Chicago)
  599.    ========================
  600.    
  601.        This information represents the Preview version, build 347. Note,
  602.        that this is built with the commercially released tools and does
  603.     not need anything from the Win95 SDK.
  604.  
  605.     In the 16-bit version of the program the Windows 95 check can use
  606.     the Generic Thunk interface. The documentation on the Windows NT SDK
  607.     indcated that the Generic Thunk interface would always be unique to
  608.     Windows NT (and that same document on the Windows 95 SDK CD still
  609.     indicates this). However, the Programmer's Guide on the Windows 95
  610.     SDK CD reports that the Generic Thunk interface is supported by
  611.     Windows 95, so the 16-bit program can use that interface to access
  612.     the 32-bit GetVersionEx or GetVersion functions. Note that if the
  613.     GetVersion interface is used the program can distinguish Windows NT
  614.     from Windows 95 by using the WF_WINNT bit in the value returned by
  615.     GetWinFlags.
  616.  
  617.     In the 32-bit version the GetVersionEx function is available from
  618.     the Windows 95 host, so there is no problem there.
  619.  
  620.     In both cases there is one minor problem with Windows 95, that the
  621.     build identity field returned in the GetVersionEx structure contains
  622.     (at least in the Preview build 347) 'rubbish' in the top word that
  623.     looks like the version number (04.00). In the registry there is a
  624.     DWORD version number that contains the same 'rubbish', so presumably
  625.     that is copied straight into the build field without removing the
  626.     version    number part. Because of this the program uses only the low
  627.     WORD of the field when displaying the build number. Maybe this will
  628.     change for the final version.
  629.  
  630.    Detecting Windows for Workgroups
  631.    ================================
  632.  
  633.     As of Version 1.6 the program does its best to detect whether it is
  634.     running under Windows for Workgroups. The program can easily detect
  635.     Windows for Workgroups on a networked system when the network is
  636.     running, as in Q114470, but this test is unreliable.
  637.     
  638.     The problem is that Windows for Workgroups does not require a network,
  639.     and in such a mode there appears to be no obvious difference from a
  640.     basic (i.e. not Workgroup) Windows installation. The program looks for
  641.     the presence of WFWNET.DRV in the WINDOWS\SYSTEM directory as a further
  642.     indication of Windows for Workgroups, but this in itself is still a
  643.     very unreliable check. In the end we report what we find:
  644.  
  645.     Workgroups found and running (because net detect says so)
  646.     
  647.     Workgroups not found (net detect says no and no WFWNET.DRV found)
  648.     
  649.     Workgroups found but not running (net detect says no but WFWNET.DRV found)
  650.     
  651.     Note that the wording "Not found" is carefully chosen! It is intended
  652.     to indicate that just because it was "not found" does not mean this is
  653.     not a Windows for Workgroups system.
  654.  
  655.     But if anybody reading this knows a more reliable test for Windows for
  656.     Workgroups ... other than the WINVER string, that is ...
  657.  
  658.     In version 1.7 a check on a string from the WINVER.EXE file is included,
  659.     which contains the word "Workgroups" in the WFWG 3.11 version, but this
  660.     seems somewhat dubious, and I have no idea what is in the Windows for
  661.     Workgroups 3.10 string.
  662.  
  663.     Again, if anybody knows a better Workgroups check ...
  664.  
  665. */
  666.  
  667.  
  668. #if    defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ )
  669.  
  670. /* #define        UNICODE        /* uncomment this or define UNICODE on the command
  671.                        line to create a UNICODE version of the 32-bit
  672.                        program, though it's not of any practical use! */
  673.  
  674. #endif
  675.  
  676.  
  677. #if    defined( _UNICODE ) && !defined( UNICODE )
  678. #define    UNICODE                /* just in case the alternate _UNICODE define is used */
  679. #endif
  680.  
  681. #if    defined( UNICODE ) && !defined( _UNICODE )
  682. #define    _UNICODE            /* and the other way round! */
  683. #endif
  684.  
  685.  
  686. #define        STRICT
  687.  
  688. #include    <windows.h>
  689. #include    <stdlib.h>        /* for _MAX_PATH */
  690. #include    <string.h>        /* for strncpmi() and strstr() and _fmemset */
  691.  
  692. #if    defined( _MSC_VER )
  693. /* Borland C++ 4.5 does not have tchar.h (yet?),
  694.    so UNICODE version does not compile! */
  695. #include    <tchar.h>
  696. #endif
  697.  
  698. #if    defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ )
  699.  
  700. /* to get the Borland version to compile when not
  701.    using UNICODE define the translated name */
  702. #if    !defined( _tcstol )
  703. #define        _tcstol        strtol
  704. #endif
  705.  
  706. #endif    /* of Borland translation */
  707.  
  708.  
  709. #include    "win_ver.h"
  710.  
  711.  
  712. /* #define    TEST_BASIC_IF         /* remove comment starter at beginning of line
  713.                        or define on command line to test basic 32-bit
  714.                        I/F's rather than the extended I/F's */
  715.  
  716. /* #define    INCLUDE_USER32_DLL    /* remove comment starter at beginning of line
  717.                        or define on command line to include the version
  718.                        of USER32.DLL in the 32-bit version output */
  719.  
  720.  
  721. #if    !defined( TEXT )        /* have to do this for 16-bit compiles */
  722. #define        TCHAR        char
  723. #define        PTCHAR        char*
  724. #define        TEXT( str )    str
  725. #endif
  726.  
  727.  
  728. #define        PROG_NAME    TEXT( "Windows Version Program : V" )            \
  729.                     TEXT( PROG_MAJOR_VERS_STRING ) TEXT( "." )    \
  730.                         TEXT( PROG_MINOR_VERS_STRING )
  731.  
  732.  
  733.  
  734. /* some of the things are defined here because they are not
  735.    available in any of the header files that ship with some
  736.    of the standard C/C++ development environments currently
  737.    available, as presented here it will build with the retail
  738.    versions of Microsoft VC++ 1.5 or 1.51 or 1.52 (from the
  739.    VC++ 2.x packages) for 16-bit and VC++ 1.10 (aka 1.0)
  740.    32-bit edition or VC++ 2.0 or VC++ 2.1, and the Borland
  741.    C++ 4.02 or C++ 4.5 packages for both 16-bit and 32-bit. */
  742.  
  743.  
  744. #define        WF_WINNT    0x4000        /* flag for WOW in 16-bit Windows, 1 == WOW == NT */
  745. #define        WV_NO_WINNT    0x80000000    /* flag for not NT in 32-bit GetVersion(), 1 == not NT */
  746.  
  747. #define        W32SYS        "W32SYS.DLL"    /* name of Win32s DLL */
  748. #define        WIN32SINF    "GETWIN32SINFO"    /* name of GetWin32sInfo function in W32SYS.DLL */
  749.  
  750. #define        WIN32S_INI    "\\SYSTEM\\WIN32S.INI"
  751.                         /* name of Win32s.INI file in the Windows directory */
  752. #define        WIN32S_SECT    "Win32s"    /* section in Win32s.INI file */
  753. #define        WIN32S_KEY    "Version"    /* key in Win32s.INI file for version data */
  754.  
  755. #define        KRNL386        "KRNL386.EXE"    /* name of Krnl386 EXE */
  756. #define        LDLIB32W    "LOADLIBRARYEX32W"
  757.                         /* name of LoadLibraryEx32W function in KRNL386.EXE */
  758. #define        CALLPROCEX    "_CALLPROCEX32W"/* name of CallProcEx32W function in Kernel32.DLL.
  759.                            note that this does *not* exist on Windows NT 3.1,
  760.                            only on Windows NT 3.5 and later. */
  761.  
  762. #define        USER_EXE    "USER.EXE"    /* name of USER.EXE file */
  763. #define        BUILD_ID    516        /* number of Build string in USER.EXE */
  764.  
  765. #define        USER32_DLL    "USER32.DLL"    /* name of USER32.DLL file on Windows NT */
  766.  
  767. #define        KERNEL32    "KERNEL32.DLL"    /* name of module that holds GetVersion(Ex) functions */
  768. #define        GETVERN        "GetVersion"    /* name of GerVersion function in Kernel32.DLL */
  769.  
  770.  
  771. #define        FILEINFO_KEY    "\\"        /* name of file info root block for getting file
  772.                            version information */
  773.  
  774.  
  775. #define        VER_UNKNOWN    0xFFFF        /* value when can't get version from DOS */
  776.  
  777.  
  778. /* data for Windows for Workgroups network detection */
  779. #define        WFWG_NOT_FOUND    0        /* value when WFWG not found */
  780. #define        WFWG_FOUND    1        /* value when WFWG found but not running */
  781. #define        WFWG_RUNNING    2        /* value when WFWG found and running */
  782.  
  783.  
  784. /* data for Workgroups detection from WINVER.EXE */
  785. #define        WINVER_EXE    "WINVER.EXE"    /* name of WINVER.EXE file */
  786. #define        WINVER_OFFSET    0x0207        /* offset of windows version string in that file */
  787. #define        WINVER_LENGTH    64        /* number of chars to read */
  788.  
  789.  
  790. /* data for Service Pack detection on Windows NT 3.1 */
  791. #define        REG_CSD_KEY    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
  792.                         /* name of the registry entry for the CSD version */
  793. #define        REG_CSD_DATA    "CSDVersion"    /* name of CSD version data - only needed for NT 3.1 */
  794. #define        CSD_TO_SP( xx )    ( ( xx ) >> 8 )    /* to convert CSD Version number to the Service Pack
  795.                            number. I have seen no documentation for this, so
  796.                            maybe something like HIBYTE( LOWORD( xx ) ) should
  797.                            be used, but I have not traced through the WINVER
  798.                            from Windows NT 3.1 to see how it is worked out. */
  799.  
  800.  
  801. /* data for the extended get version information */
  802. #if    !defined( UNICODE )
  803.                         /* using ASCII version of extended interface */
  804. # define    GETVERNEX    "GetVersionExA"    /* name of GetVersionEx function in Kernel32.DLL,
  805.                            note that this is in W32sCOMB.DLL in Win32s,
  806.                            but asking for it in the KERNEL32.DLL module
  807.                            nevertheless still works properly. */
  808.  
  809. #else    /* UNICODE */
  810.                         /* using UNICODE version of extended interface */
  811. # define    GETVERNEX    "GetVersionExW"    /* only sensible in Windows NT */
  812.  
  813. #endif
  814.  
  815. #define        CSD_STR_SIZE    128        /* MS do not define a macro for this */
  816.  
  817. #if    !defined( TEST_BASIC_IF )          /* declarations only relevent for new interface */
  818.  
  819. #if    !defined( VER_PLATFORM_WIN32_NT )    /* if already defined don't do again. this is
  820.                            defined in the headers for MS Visual C++ 2
  821.                            and Borland C++ 4.5 but not in the earlier
  822.                            versions, and not for 16-bit compilations. */
  823.  
  824. typedef    struct    _OSVERSIONINFO    {        /* Extended Vern Info from GetVersionEx() */
  825.     DWORD    dwOSVersionInfoSize;        /* set to size of structure */
  826.     DWORD    dwMajorVersion;            /* OS major version */
  827.     DWORD    dwMinorVersion;            /* OS minor version */
  828.     DWORD    dwBuildNumber;            /* OS build number */
  829.     DWORD    dwPlatformId;            /* OS platform type */
  830.     TCHAR    szCSDVersion[ CSD_STR_SIZE ];    /* description string */
  831. } OSVERSIONINFO, *LPOSVERSIONINFO;
  832.  
  833. /* the values returned in the PlatformID field of the Extended Version
  834.    information to indicate which version of the 32-bit platform is in use. */
  835. #define    VER_PLATFORM_WIN32s        0    /* Win32s on Windows 3.1x */
  836. #define    VER_PLATFORM_WIN32_WINDOWS    1    /* Win32 on Windows 95. */
  837. #define    VER_PLATFORM_WIN32_NT        2    /* Windows NT */
  838.  
  839. #endif    /* of extended version info already defined */
  840.  
  841. #if    !defined( VER_PLATFORM_WIN32_WINDOWS )    /* Windows 95 value for platform ID not
  842.                            defined in the header files VC++ 2.0
  843.                            or 2.1 or BC++ 4.5, so define it now */
  844. #define    VER_PLATFORM_WIN32_WINDOWS    1
  845. #endif
  846.  
  847. #endif    /* of testing full interface */
  848.  
  849.  
  850. #if    !defined( WIN32 ) && !defined( _WIN32 ) && !defined( __WIN32__ )
  851.  
  852. /* prototypes for functions that permit the program to
  853.    thunk from this 16-bit world to a 32-bit module.
  854.    the DEF file identifies these as in the Kernel,
  855.    and it does not matter if they are not, as they
  856.    will only be called if the earlier checks OK it */
  857. DWORD    FAR    PASCAL    LoadLibraryEx32W( LPCSTR, DWORD, DWORD );
  858. BOOL    FAR    PASCAL    FreeLibrary32W( DWORD );
  859. DWORD    FAR    PASCAL    GetProcAddress32W( DWORD, LPCSTR );
  860. DWORD    FAR    PASCAL    CallProc32W__0( DWORD, DWORD, DWORD );        /* NT 3.1, et seq */
  861.  
  862. #if    !defined( TEST_BASIC_IF )
  863.  
  864. DWORD    FAR    CDECL    CallProcEx32W( DWORD, DWORD, DWORD, ... );    /* NT 3.5, et seq */
  865.  
  866. #endif    /* of testing only basic interface */
  867.  
  868. #endif    /* of 16-bit Windows NT and Windows 95 generic thunk function prototypes */
  869.  
  870.  
  871. #if    __BORLANDC__ >= 0x0452
  872. /* on Borland C++ 4.x exclude exception handling code */
  873. void    _ExceptInit( void )
  874. {
  875.     ;
  876. }
  877. #endif
  878.  
  879.  
  880. #ifdef    __BORLANDC__
  881. #pragma    argsused
  882. #endif
  883.  
  884. /* the function that drives the whole thing */
  885. int PASCAL WinMain( HINSTANCE    hInstance,
  886.             HINSTANCE    hPrevInstance,
  887.             LPSTR    lpszCmdLine,
  888.             int        nCmdShow )
  889. {
  890.     DWORD    win_vers;
  891.  
  892.     DWORD    maj_vers;
  893.     DWORD    min_vers;
  894.  
  895.     TCHAR    file_ver[ 128 ] = TEXT( "" );    /* file version string created from USER.??? */
  896.  
  897.     TCHAR    buff[ 1024 ];            /* results buffer */
  898.     int    chrs;                /* characters in results buffer so far */
  899.  
  900.     extern    PTCHAR    nt_type( void );    /* function that works out type of Windows NT */
  901.     extern    PTCHAR    nt_csd_num( void );    /* function to get numeric CSD number as string */
  902.     extern    BOOL    reg32_access( PTCHAR, PTCHAR, LPDWORD, LPBYTE, LPDWORD );
  903.                         /* function to read value from the registry */
  904.  
  905. #if    defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ )
  906.  
  907.  
  908.     /* ====================================
  909.        program is being compiled for 32-bit
  910.        ==================================== */
  911.  
  912.  
  913. #define    WIN_BITS    "32"
  914.  
  915.     /* by comparison with the 16-bit code the 32-bit code is relatively
  916.        straightforward! There are only two functions GetVersion() and
  917.        GetVersionEx(). The latter is available on version 3.5 of Windows NT
  918.        but not on version 3.1 of Windows NT, and on version 1.15 build 103
  919.        of Win32s, but not on version 1.10 build 88 of Win32s. Note that
  920.        when we find Win32s and report the underlying Windows version we do
  921.        *not* take any steps to determine the correct version number!!! */
  922.  
  923. #if    !defined( TEST_BASIC_IF )        /* normally included in build - define for testing */
  924.  
  925.     typedef    BOOL    ( WINAPI *GVEX_PTR )( LPOSVERSIONINFO );
  926.                         /* type of ptr to GetVersionEx function */
  927.  
  928.     HINSTANCE    hKernel32;        /* handle to 32-bit KERNEL32.DLL */
  929.     GVEX_PTR    Get_Version_Ex;        /* function ptr to GetVersionEx */
  930.     OSVERSIONINFO    os_vn_inf = { sizeof( OSVERSIONINFO ) };
  931.                         /* initialized Extended Version information */
  932.  
  933. #endif
  934.  
  935. #if    defined( INCLUDE_USER32_DLL )        /* chop this out of the build for version 1.7
  936.                            but leave the code behind */
  937.  
  938. #include    <winver.h>            /* not included by windows.h - different
  939.                            name for 16-bit and 32-bit windows */
  940.  
  941.     TCHAR    user_name[ _MAX_PATH ];        /* constructed name of USER32.DLL file */
  942.  
  943.     /* first try for platform independent stuff (though this will
  944.        silently fail on Win32s) this is for file version from the
  945.        USER32.DLL file. here errors are checked but not reported.
  946.        this is just to demonstrate the consistency, because
  947.        unlike 16-bit Windows the 32-bit GetVersion() function
  948.        always [so far on NT 3.1 and NT 3.5!] tells the truth.
  949.        UNICODE versions of the functions uses wide characters. */
  950.     if( GetSystemDirectory( user_name, _MAX_PATH ) != 0 )
  951.     {
  952.         DWORD            dummy;        /* ignored on 32-bit */
  953.         DWORD            inf_size;    /* how big is version info buffer */
  954.         PBYTE            ver_buff;    /* ptr to mem allocated for ver info buff */
  955.         VS_FIXEDFILEINFO    *fixed_info;    /* ptr to fixed info in ver info buff */
  956.         UINT            fixed_size;    /* number of bytes read for ver info */
  957.  
  958.         /* add name of user32.dll to system directory. */
  959.         lstrcat( user_name, TEXT( "\\" ) TEXT( USER32_DLL ) );
  960.         if( ( inf_size = GetFileVersionInfoSize( user_name, &dummy ) ) != 0 )
  961.         {
  962.             /* how big is version info buffer required */
  963.             ver_buff = GlobalAlloc( GPTR, inf_size );
  964.             if( ver_buff != NULL )
  965.             {
  966.                 /* and get the information */
  967.                 if( GetFileVersionInfo( user_name, 0, inf_size, ver_buff ) != FALSE )
  968.                 {
  969.                     /* get ptr to fixed version information in version info buffer */
  970.                     if( VerQueryValue( ver_buff, TEXT( FILEINFO_KEY ),
  971.                             (PVOID *)&fixed_info, &fixed_size ) != 0 )
  972.                     {
  973.                         /* okay - add file version to build information */
  974.                         wsprintf( file_ver, TEXT( "\n" ) TEXT( USER32_DLL ) TEXT( " File Version %lu.%lu" ),
  975.                                 HIWORD(    fixed_info->dwFileVersionMS ),
  976.                                 LOWORD(    fixed_info->dwFileVersionMS ) );
  977.                     }
  978.                 }
  979.                 /* free memory for version buffer */
  980.                 GlobalFree( ver_buff );
  981.             }
  982.         }
  983.     }
  984.  
  985. #endif                    /* of code chopped out of build for version 1.7 */
  986.  
  987. #if    !defined( TEST_BASIC_IF )    /* code normally included in build - define for testing */
  988.  
  989.     /* now try for extended version information - the function is
  990.        run-time linked so that we can use simple information if
  991.        the extended information is not available on this version
  992.        of the OS. Here normal 32-bit functions are used. the name
  993.        given to GetProcAddress *NEVER* uses wide characters, but
  994.        the name given to GetModuleHandle does - go figure. */
  995.     if( ( hKernel32 = GetModuleHandle( TEXT( KERNEL32 ) ) ) != NULL &&
  996.         ( Get_Version_Ex = (GVEX_PTR)GetProcAddress( hKernel32, GETVERNEX ) ) != NULL )
  997.     {
  998.         /* got Extended Version function ptr so use it */
  999.  
  1000.         PTCHAR    platform_type;
  1001.         TCHAR    unknown_str[ 32 ];
  1002.  
  1003.         /* and get data */
  1004.         Get_Version_Ex( &os_vn_inf );
  1005.  
  1006.         /* set up name of 32-bit platform */
  1007.         switch( os_vn_inf.dwPlatformId )
  1008.         {
  1009.         case VER_PLATFORM_WIN32_NT:
  1010.             platform_type = TEXT( "Windows NT" );
  1011.             break;
  1012.  
  1013.         case VER_PLATFORM_WIN32s:
  1014.             platform_type = TEXT( "Win32s" );
  1015.             break;
  1016.  
  1017.         case VER_PLATFORM_WIN32_WINDOWS:
  1018.             platform_type = TEXT( "Windows 95" );
  1019.             break;
  1020.  
  1021.         default:
  1022.             /* cater for unknown platform types */
  1023.             wsprintf( unknown_str, TEXT( "Unknown type %lu" ), os_vn_inf.dwPlatformId );
  1024.             platform_type = unknown_str;
  1025.         }
  1026.         /* here use only low word of build identity for Windows 95
  1027.            compatiblity, as on build 347 it contains 0x0400015B */
  1028.         chrs = wsprintf( buff, TEXT( "%s Version %u.%02u Build %u\n" )
  1029.                        TEXT( "%s" ),
  1030.                         platform_type,
  1031.                         os_vn_inf.dwMajorVersion,
  1032.                         os_vn_inf.dwMinorVersion,
  1033.                         LOWORD( os_vn_inf.dwBuildNumber ),
  1034.                         os_vn_inf.szCSDVersion
  1035.                                     );
  1036.         switch( os_vn_inf.dwPlatformId )
  1037.         {
  1038.         case VER_PLATFORM_WIN32s:
  1039.             /* on Win32s append version of underlying 16-bit Windows */
  1040.             win_vers = GetVersion();
  1041.             chrs += wsprintf( buff + chrs,  TEXT( "\n" )
  1042.                             TEXT( "running on\n" )
  1043.                             TEXT( "Windows Version %u.%02u" ),
  1044.                         LOBYTE( LOWORD( win_vers ) ),
  1045.                         HIBYTE( LOWORD( win_vers ) ) );
  1046.             break;
  1047.  
  1048.         case VER_PLATFORM_WIN32_NT:
  1049.             /* on Windows NT report if workstation or some kind of
  1050.                server and add the numeric Service Pack information */
  1051.             chrs += wsprintf( buff + chrs, TEXT( "\n" )
  1052.                                TEXT( "Windows NT %s" )
  1053.                                TEXT( "%s" ),
  1054.                         nt_type(), nt_csd_num() );
  1055.             break;
  1056.         }
  1057.  
  1058.         /* end of extended version code */
  1059.  
  1060.     }
  1061.     else
  1062.     {
  1063.  
  1064.         /* we could not get extended version information so go with basic */
  1065.  
  1066. #endif
  1067.  
  1068.         /* GetVersion() always exists so it is compile-time linked */
  1069.         win_vers = GetVersion();
  1070.  
  1071.         /* save version information */
  1072.         maj_vers = LOBYTE( LOWORD( win_vers ) );
  1073.         min_vers = HIBYTE( LOWORD( win_vers ) );
  1074.  
  1075.         if( ( win_vers & WV_NO_WINNT ) == 0 )
  1076.         {
  1077.             /* this is real Windows NT */
  1078.             union
  1079.             {
  1080.                 DWORD    csd_no;                /* CSD as number in NT 3.1 */
  1081.                 TCHAR    csd_str[ CSD_STR_SIZE + 1 ];    /* CSD as string in NT 3.5 */
  1082.             } csd_data;                    /* CSD as number or string */
  1083.             DWORD    csd_type;                /* CSD value type */
  1084.              DWORD    csd_size = sizeof( csd_data );        /* CSD size in bytes -
  1085.                                         even for UNICODE */
  1086.  
  1087.             /* try and get the CSD Version (Service Pack) value from the Registry */
  1088.             if( reg32_access( TEXT( REG_CSD_KEY ), TEXT( REG_CSD_DATA ),
  1089.                         &csd_type, (LPBYTE)&csd_data, &csd_size ) )
  1090.             {
  1091.                 /* display depends on the data type from the Registry */
  1092.                 switch( csd_type )
  1093.                 {
  1094.                 case REG_DWORD:
  1095.                     /* this is Windows NT with numeric CSD version -
  1096.                        this is the code used on Windows NT 3.1 by the
  1097.                        basic interface - form a service pack string */
  1098.                     wsprintf( csd_data.csd_str, TEXT( "Service Pack %lu (CSD Version 0x%lX)\n" ),
  1099.                                 CSD_TO_SP( csd_data.csd_no ), csd_data.csd_no );
  1100.                     break;
  1101.                     
  1102.                 case REG_SZ:
  1103.                     /* this should only be used when testing the basic
  1104.                        interface on Windows NT 3.5 - add line terminator */
  1105.                     lstrcat( csd_data.csd_str, TEXT( "\n" ) );
  1106.                     break;
  1107.  
  1108.                 default:
  1109.                     /* this should never happen! */
  1110.                     lstrcpy( csd_data.csd_str, TEXT( "??\n" ) );
  1111.                     /* drop through and out */
  1112.                 }
  1113.                 /* form full Windows NT version string */
  1114.                 chrs = wsprintf( buff, TEXT( "Windows NT Version %u.%02u Build %u\n" )
  1115.                                TEXT( "%s" )
  1116.                                TEXT( "Windows NT %s" )
  1117.                                TEXT( "%s" ),
  1118.                                 maj_vers, min_vers, HIWORD( win_vers ) & 0x7FFF,
  1119.                                 csd_data.csd_str,
  1120.                                 nt_type(), nt_csd_num() );
  1121.             }
  1122.         }
  1123.         else
  1124.         {
  1125.             /* yes - I know this bit is idiotic as UNICODE!
  1126.                and it does cause some grief. */
  1127.             if( maj_vers < 4 )
  1128.             {
  1129.                 /* this is Win32s running on 16-bit Windows 3.1x, the
  1130.                    program is running in 32-bit mode and the extended
  1131.                    information is not available but although we are
  1132.                    getting desperate before we bottle out we try for
  1133.                    information from the Win32s.INI file */
  1134.                 TCHAR    file_name[ _MAX_PATH ];    /* space for full INI file name */
  1135.                 TCHAR    ver_buff[ 32 ];        /* space for string from INI file */
  1136.                 PTCHAR    ver_ptr = ver_buff;    /* ptr to string from INI file */
  1137.  
  1138.                 LONG    win32s_maj;        /* Win32s major version */
  1139.                 LONG    win32s_min;        /* Win32s minor version */
  1140.                 LONG    win32s_bld;        /* Win32s build number */
  1141.  
  1142.                 /* the default string is empty so we can recognize the
  1143.                    case where it is returned - the documentation says
  1144.                    that if we do not supply a full path name then the
  1145.                    GetPrivateProfileString function should look in the
  1146.                    Windows directory, but it appears not to work here,
  1147.                    so we make the full name ourselves. the string is
  1148.                    parsed as we go along. the various parts of the
  1149.                    version number are handled as longs. note that we
  1150.                    use strtol (portably _tcstol) rather than atoi
  1151.                    (portably _ttoi) as it serves three purposes at once,
  1152.                    (1) it gets the number (2) it steps the pointer to
  1153.                    the next character of the string, (3) it allows us
  1154.                    to check [following from (2)] that exactly all the
  1155.                    characters expected were read as part of the number.
  1156.                    all three numbers must be followed by a full stop. */
  1157.                 if( 0 != GetWindowsDirectory( file_name, _MAX_PATH ) &&
  1158.                     0 != GetPrivateProfileString( TEXT( WIN32S_SECT ), TEXT( WIN32S_KEY ),
  1159.                             TEXT( "" ), ver_ptr, sizeof( ver_buff ) / sizeof( TCHAR ),
  1160.                             lstrcat( file_name, TEXT( WIN32S_INI ) ) ) &&
  1161.                     ( win32s_maj = _tcstol(   ver_ptr, &ver_ptr, 10 ), TEXT( '.' ) == *ver_ptr ) &&
  1162.                     ( win32s_min = _tcstol( ++ver_ptr, &ver_ptr, 10 ), TEXT( '.' ) == *ver_ptr ) &&
  1163.                     ( win32s_bld = _tcstol( ++ver_ptr, &ver_ptr, 10 ), TEXT( '.' ) == *ver_ptr ) )
  1164.                 {
  1165.                     /* we cannot tell if this is the debug or retail version,
  1166.                        but we warn the user the data has come from the INI file!
  1167.                        note that the version number is formatted as %ld.%ld rather
  1168.                        than %ld.%02ld because the version 1.10 INI file contains
  1169.                        version 1.1 and we must not report the identity as 1.01. */
  1170.                     chrs = wsprintf( buff, TEXT( "Win32s Version %ld.%ld Build %ld\n" )
  1171.                                    TEXT( "(with data from WIN32S.INI file)\n" )
  1172.                                    TEXT( "running on\n" )
  1173.                                    TEXT( "Windows Version %u.%02u" ),
  1174.                             win32s_maj, win32s_min, win32s_bld,
  1175.                             maj_vers, min_vers );
  1176.                 }
  1177.                 else
  1178.                 {
  1179.                     /* the version number from the Win32s.INI file is no
  1180.                        good so we cannot easily determine the version of
  1181.                        Win32s as that info would have to come from a 16-bit
  1182.                        DLL - good innit. the program *could* work out the
  1183.                        version, but it would require a 16-bit DLL to thunk
  1184.                        to, and that just is not worth the effort. Bottle
  1185.                        out and tell the user to run the 16-bit version. */
  1186.                     chrs = wsprintf( buff, TEXT( "      Win32s running on Version %u.%02u\n" )
  1187.                                 TEXT( "\t      of Windows\n" )
  1188.                                 TEXT( "\n" )
  1189.                                 TEXT( "      Run 16-bit version for information\n" )
  1190.                                 TEXT( "                 on Version of Win32s\n" )
  1191.                                 TEXT( "and for additional 16-bit build information" ),
  1192.                             maj_vers, min_vers );
  1193.                 }
  1194.             }
  1195.             else
  1196.             {
  1197.                 /* this is Windows 95 - aka Chicago - how did we ever get here as
  1198.                    GetVersionEx (or GetVersion!) should have already done its thing. */
  1199.                 chrs = wsprintf( buff, TEXT( "??? Windows 95 Version %u.%02u" ),
  1200.                                 maj_vers, min_vers );
  1201.             }
  1202.         }
  1203.  
  1204. #if    !defined( TEST_BASIC_IF )
  1205.  
  1206.     }
  1207.  
  1208. #endif
  1209.     /* however we got here add possibly empty file version string */
  1210.     wsprintf( buff + chrs, TEXT( "%s" ), file_ver );
  1211.  
  1212.  
  1213.     /* ==========================
  1214.        end of 32-bit Windows code
  1215.        ========================== */
  1216.  
  1217.  
  1218. #else
  1219.  
  1220.  
  1221.     /* ============================
  1222.        start of 16-bit Windows code
  1223.        ============================ */
  1224.  
  1225.  
  1226. #if    defined( UNICODE )
  1227. #error    UNICODE for a 16-bit program ???
  1228. #endif
  1229.  
  1230.  
  1231. #include    <ver.h>            /* not included by windows.h - different
  1232.                        name for 16-bit and 32-bit windows */
  1233.  
  1234. #define    WIN_BITS    "16"
  1235.  
  1236.     /* compared with the 32-bit code this is horrible, though ironically
  1237.        it is tidier when Windows NT is the host. It tries for a host
  1238.        32-bit OS. If that fails it proceeds with 16-bit detection,
  1239.        looking for Win32s as well as checking for version numbers known
  1240.        to DOS and the USER.EXE module, the file version from USER.EXE,
  1241.        and the Windows string from the WINVER.EXE file. */
  1242.  
  1243. #if    !defined( REG_DWORD )        /* we have to define these for 16-bit version */
  1244. #define    REG_SZ        1
  1245. #define    REG_DWORD    4
  1246. #endif
  1247.  
  1248.     DWORD        win_flags;
  1249.  
  1250.     HINSTANCE    hKrnl386;        /* instance handle for Krnl386.EXE */
  1251.  
  1252.     /* first do the bits that are host independent - simple GetVersion */
  1253.     win_vers = GetVersion();
  1254.  
  1255.     /* save basic version number */
  1256.     maj_vers = LOBYTE( LOWORD( win_vers ) );
  1257.     min_vers = HIBYTE( LOWORD( win_vers ) );
  1258.  
  1259.     /* OK - done system independent stuff - now system dependent */
  1260.  
  1261.     win_flags = GetWinFlags();
  1262.     
  1263.     /* detect whether the underlying OS can support the functions
  1264.        that would allow a 16-bit program to thunk to that underlying
  1265.        32-bit OS. we don't need the function address, as this is
  1266.        only to check that the function we need is present, so don't
  1267.        bother to save it. the WF_WINNT check could be used, but by
  1268.        ignoring that and doing it this way we are not dependant on
  1269.        any particular flags to tell us which version of the OS we are
  1270.        on. If the LoadLibraryEx32W function is present we use it,
  1271.        [even though that almost certainly means only on Windows NT].
  1272.        Well, on 10th April 1995 the previous statement in [ ] was
  1273.        proved wrong when I finally got my hands on a Preview copy of
  1274.        Windows 95, which *does* support the Generic Thunk stuff, so
  1275.        not using the WF_WINNT flag proved to be the correct choice
  1276.        way back in May 1995! Sheesh! note that neither the value from
  1277.        the 16-bit GetVersion nor from the 16-bit GetWinFlags function
  1278.        appears to indicate if this is running on Windows 95. */
  1279.        
  1280.     if( /* win_flags & WF_WINNT - *NO* - the following works on Windows 95
  1281.            where that flag is not set - testing this longer way is better */
  1282.         ( hKrnl386 = GetModuleHandle( KRNL386 ) ) != NULL &&
  1283.           GetProcAddress( hKrnl386, LDLIB32W ) != NULL )
  1284.     {
  1285.         /* Program is running on Windows NT, or on some other system
  1286.            (Windows 95? - I thought maybe not - until I found out to the
  1287.            contrary on 10th April 1995) that supports LoadLibraryEx32W
  1288.            so use it to get version data from the underlying 32-bit OS */
  1289.  
  1290.         DWORD        hKernel32;    /* handle to 32-bit DLL while thunking */
  1291.         DWORD        Get_Version;    /* ptr to 32-bit function that gets version. */
  1292.  
  1293. #if    !defined( TEST_BASIC_IF )
  1294.  
  1295.         OSVERSIONINFO    os_vn_inf = { sizeof( OSVERSIONINFO ) };
  1296.                         /* initialized extended version information */
  1297.         DWORD        Get_Version_Ex;    /* ptr to Extended Version function */
  1298.  
  1299.         /* first try for extended version information - we go through
  1300.            this so that we can use simple information if the extended
  1301.            information is not available on this version of the OS, as
  1302.            Windows NT 3.1 does not provide Extended Version information.
  1303.  
  1304.            Here the program uses the thunk interface to call from the
  1305.            16-bit version of the program 'through' to the 32-bit world.
  1306.            the KRNL386 check is because the CallProcEx32W function did
  1307.            not appear till Windows NT 3.5, so we can't use the extended
  1308.            functions on Windows NT 3.1, and we don't want an unresolved
  1309.            Dynalink message to stop the program, but we don't need to
  1310.            save the address of the function. So, do checks and get data.
  1311.  
  1312.            We assume that if the CallProcEx32W() function does not exist
  1313.            then nor does the GetVersionEx() function.
  1314.  
  1315.            The CallProcEx32W function passes 1 parameter that requires
  1316.            address translation to the GetVersionEx() function.
  1317.  
  1318.            if any check fails, or we fail to get data, we go on to try
  1319.            for basic data.
  1320.  
  1321.            Note, that it is *VITAL* that hKernel32 is obtained *FIRST*
  1322.            so that is is *ALWAYS* done (the rest may not be!), as it
  1323.            is assumed to be set up when getting the basic version. */
  1324.         if( ( hKernel32 = LoadLibraryEx32W( KERNEL32, NULL, 0 ) ) != NULL &&
  1325.             ( Get_Version_Ex = GetProcAddress32W( hKernel32, GETVERNEX ) ) != NULL &&
  1326.               GetProcAddress( hKrnl386, CALLPROCEX ) != NULL &&
  1327.               CallProcEx32W( 1, 1, Get_Version_Ex, (OSVERSIONINFO FAR *)&os_vn_inf ) == TRUE )
  1328.         {
  1329.             char    *platform_type;
  1330.             char    unknown_str[ 32 ];
  1331.  
  1332.             /* free library when done. */
  1333.             FreeLibrary32W( hKernel32 );
  1334.  
  1335.             /* set up name of 32-bit platform though
  1336.                unlikely to be other than Windows NT */
  1337.             switch( os_vn_inf.dwPlatformId )
  1338.             {
  1339.             case VER_PLATFORM_WIN32_NT:
  1340.                 platform_type = "Windows NT";
  1341.                 break;
  1342.  
  1343.             case VER_PLATFORM_WIN32s:
  1344.                 /* this will never happen, as Win32s
  1345.                    does not support Generic Thunks */
  1346.                 platform_type = "Win32s";
  1347.                 break;
  1348.  
  1349.             case VER_PLATFORM_WIN32_WINDOWS:
  1350.                 platform_type = "Windows 95";
  1351.                 break;
  1352.  
  1353.             default:
  1354.                 /* cater for unknown platform types */
  1355.                 wsprintf( unknown_str, "Unknown type %lu", os_vn_inf.dwPlatformId );
  1356.                 platform_type = unknown_str;
  1357.             }
  1358.             /* prepare output buffer and give user information */
  1359.             chrs = wsprintf( buff, "%s reports OS Version %u.%2u\n"
  1360.                     "running on\n"
  1361.                     "%s Version %u.%02u Build %u\n"
  1362.                     "%s\n",
  1363.                     (LPCSTR)( win_flags & WF_WINNT ? "WOW (Win16)" : "Win16" ),
  1364.                     LOBYTE( win_vers ), HIBYTE( win_vers ),
  1365.                     (LPCSTR)platform_type,
  1366.                     (WORD)os_vn_inf.dwMajorVersion,
  1367.                     (WORD)os_vn_inf.dwMinorVersion,
  1368.                     (WORD)os_vn_inf.dwBuildNumber,
  1369.                     (LPCSTR)os_vn_inf.szCSDVersion );
  1370.             switch( os_vn_inf.dwPlatformId )
  1371.             {
  1372.             case VER_PLATFORM_WIN32_NT:
  1373.                 /* if Windows NT is the host add the type and CSD version */
  1374.                 wsprintf( buff + chrs, "Windows NT %s"
  1375.                         "%s",
  1376.                         (LPCSTR)nt_type(), (LPCSTR)nt_csd_num() );
  1377.                 break;
  1378.                 
  1379.             case VER_PLATFORM_WIN32s:
  1380.                 /* if Wind32s add the DOS version number - should never happen */
  1381.                 
  1382.             case VER_PLATFORM_WIN32_WINDOWS:
  1383.                 /* if Windows 95 add the DOS version number */
  1384.                 wsprintf( buff + chrs, "with DOS version %d.%02d",
  1385.                         HIBYTE( HIWORD( win_vers ) ),
  1386.                         LOBYTE( HIWORD( win_vers ) ) );
  1387.                 break;
  1388.             }
  1389.         }
  1390.         else
  1391.         {
  1392.  
  1393. #else
  1394.  
  1395.             /* if testing basic interfaces get library handle */
  1396.             hKernel32 = LoadLibraryEx32W( KERNEL32, NULL, 0 );
  1397.  
  1398. #endif
  1399.  
  1400.             /* failed to get extended information - try for basic.
  1401.                under normal circumstances (i.e. when not testing)
  1402.                this should only run if Windows NT 3.1 is the host. */
  1403.  
  1404.             if( hKernel32 == NULL ||
  1405.                 ( Get_Version = GetProcAddress32W( hKernel32, GETVERN ) ) == NULL )
  1406.             {
  1407.                 /* somehow we failed to get a handle to KERNEL32.DLL, or the
  1408.                    address of the GetVersion function - this should never happen!!! */
  1409.                 wsprintf( buff, "Error\n"
  1410.                         "\n"
  1411.                         "No %s" KERNEL32 " module\n"
  1412.                         "\n"
  1413.                         "???? - How can this be - ????",
  1414.                             hKernel32 != NULL ? GETVERN " function in" : "" );
  1415.                 if( hKernel32 != NULL )
  1416.                 {
  1417.                     FreeLibrary32W( hKernel32 );
  1418.                 }
  1419.             }
  1420.             else
  1421.             {
  1422.                 DWORD    nt_vers;      /* underlying OS version and build */
  1423.  
  1424.                 /* get OS version number reported by the 32-bit OS.
  1425.                    no parameters, so no translation. note that here the
  1426.                    CallProc32W function is used, as the CallProcEx32W
  1427.                    function does not exist on Windows NT 3.1, and we
  1428.                    want this program to work on all Windows NT versions. */
  1429.                 nt_vers = CallProc32W__0( Get_Version, 0, 0 );
  1430.                 /*free the library when done */
  1431.                 FreeLibrary32W( hKernel32 );
  1432.  
  1433.                 /* prepare output buffer and give user information */
  1434.                 chrs = wsprintf( buff,  "%s reports OS Version %u.%2u\n"
  1435.                             "running on\n"
  1436.                             "Windows %s Version %u.%02u",
  1437.                                 (LPCSTR)( win_flags & WF_WINNT ?
  1438.                                     "WOW (Win16)" : "Win16" ),
  1439.                                 (WORD)maj_vers, (WORD)min_vers,
  1440.                                 (LPCSTR)( win_flags & WF_WINNT ?
  1441.                                         "NT" : "95" ),
  1442.                                 LOBYTE( LOWORD( nt_vers ) ),
  1443.                                 HIBYTE( LOWORD( nt_vers ) ) );
  1444.                 if( win_flags & WF_WINNT )
  1445.                 {
  1446.                     /* if Windows NT is the host add the build number
  1447.                        (which is not present in the GetVersion response
  1448.                        on Windows 95) and the type and CSD version */
  1449.  
  1450.                     /* data for access to registry for Service Pack level */
  1451.                     union
  1452.                     {
  1453.                         DWORD    csd_no;                /* CSD as number in NT 3.1 */
  1454.                         char    csd_str[ CSD_STR_SIZE + 1 ];    /* CSD as string in NT 3.5 */
  1455.                     } csd_data;                    /* CSD as number or string */
  1456.                     DWORD    csd_type;                /* CSD value type */
  1457.                      DWORD    csd_size = sizeof( csd_data );        /* CSD size in bytes */
  1458.  
  1459.                     /* get service pack info from the Windows NT registry */
  1460.                     if( reg32_access( REG_CSD_KEY, REG_CSD_DATA,
  1461.                                 &csd_type, (LPBYTE)&csd_data, &csd_size ) == TRUE )
  1462.                     {
  1463.                         /* display depends on the data type from the Registry */
  1464.                         switch( csd_type )
  1465.                         {
  1466.                         case REG_DWORD:
  1467.                             /* this is Windows NT with numeric CSD version -
  1468.                                this is the code used on Windows NT 3.1 by the
  1469.                                basic interface - form a service pack string */
  1470.                             wsprintf( csd_data.csd_str, "Service Pack %lu (CSD Version 0x%lX)\n",
  1471.                                         CSD_TO_SP( csd_data.csd_no ) , csd_data.csd_no );
  1472.                             break;
  1473.                         
  1474.                         case REG_SZ:
  1475.                             /* this should only be used when testing the basic
  1476.                                interface on Windows NT 3.5 - add line terminator */
  1477.                             lstrcat( csd_data.csd_str, "\n" );
  1478.                             break;
  1479.  
  1480.                         default:
  1481.                             /* this should never happen! */
  1482.                             lstrcpy( csd_data.csd_str, "??\n" );
  1483.                             /* drop through and out */
  1484.                         }
  1485.                     }
  1486.                     wsprintf( buff + chrs,
  1487.                             " Build %u\n"
  1488.                             "%s"
  1489.                             "Windows NT %s"
  1490.                             "%s",
  1491.                                 HIWORD( nt_vers ) & 0x7FFF,
  1492.                                 (LPCSTR)csd_data.csd_str,
  1493.                                 (LPCSTR)nt_type(),
  1494.                                 (LPCSTR)nt_csd_num() );
  1495.                 }
  1496.                 else
  1497.                 {
  1498.                     /* on Windows 95 (but never on Win32s!!)
  1499.                        add the DOS version number */
  1500.                     wsprintf( buff + chrs,  "\n"
  1501.                                 "with DOS version %d.%02d",
  1502.                             HIBYTE( HIWORD( win_vers ) ),
  1503.                             LOBYTE( HIWORD( win_vers ) ) );
  1504.                 }
  1505.             }
  1506.  
  1507. #if    !defined( TEST_BASIC_IF )
  1508.  
  1509.         }
  1510.  
  1511. #endif
  1512.         /* end of code for Windows NT as host */
  1513.     }
  1514.     else
  1515.     {
  1516.         /* Program is running on real 16-bit Windows 3.1x - here the program
  1517.            can find out which version, if any, of Win32s is available, and
  1518.            which particular build of Windows 3.1x this really is */
  1519.            
  1520.         char        file_name[ _MAX_PATH ];        /* constructed name of a file */
  1521.  
  1522.         /* space for WINVER.EXE data */
  1523.         char        winver_buff[ WINVER_LENGTH + 1 ] = "";
  1524.                                 /* string from WINVER.EXE file,
  1525.                                    also used for result string */
  1526.  
  1527.         /* data for Win32s */
  1528.         typedef    struct
  1529.         {
  1530.             BYTE    bMajor;            /* Win32s major version number */
  1531.             BYTE    bMinor;            /* Win32s minor version number */
  1532.             WORD    wBuildNumber;        /* Win32s build number */
  1533.             BOOL    fDebug;            /* Win32s retail/debug flag */
  1534.         } WIN32SINFO, far *LPWIN32SINFO;
  1535.  
  1536.         typedef    int    ( WINAPI *GW32S_PTR )( LPWIN32SINFO );
  1537.                             /* type of ptr to GetWin32sInfo function */
  1538.  
  1539.         HINSTANCE    h_win32s;           /* handle to Win32s library */
  1540.         GW32S_PTR    Get_Win32s_Info;    /* address of Win32s version function */
  1541.         WIN32SINFO    win32s_info;        /* the Win32s version information */
  1542.         char        win32s_ver[ 128 ];    /* filled with Win32s version - if any */
  1543.  
  1544.         /* data for USER.EXE build string */
  1545.         HINSTANCE    h_userexe;        /* handle to USER.EXE library */
  1546.         char        bld_temp[ 64 ] = "";    /* space for build string */
  1547.         char        bld_str[ 64 ] = "";    /* space for build result */
  1548.  
  1549.         /* data for DOS build string */
  1550.         char        dos_bld_id[ 64 ] = "";    /* for DOS build information */
  1551.  
  1552.         /* ++++ these are only declared 'far' to keep the
  1553.            code working when the Borland C++ 4.5 compiler is used */
  1554.         char    far    *wfwg_string;
  1555.         char    far    *wfwg_spacer;
  1556.  
  1557.         WORD        win_for_wkg_check( void );    /* our function to check if this
  1558.                                    is Windows for Workgroups */
  1559.  
  1560.         WORD        get_dos_win_ver( void );    /* our function to ask DOS what
  1561.                                    version of Windows it thinks */
  1562.  
  1563.         BOOL        win32s_running( void );        /* our function to detect if
  1564.                                    Win32s is actually running */
  1565.  
  1566.         /* get file version information from USER.EXE file -
  1567.            the only way of getting the true version number! */
  1568.         if( GetSystemDirectory( file_name, _MAX_PATH ) != 0 )
  1569.         {
  1570.             /* this is slightly simplified since V1.6 since now that it
  1571.                is not excecuted under Windows NT we do not have to worry
  1572.                about the possible alternate directory for the file */
  1573.             DWORD            inf_handle;    /* handle to version information in USER.EXE */
  1574.             DWORD            inf_size;    /* how big is version info buffer */
  1575.             LPBYTE            ver_buff;    /* ptr to mem allocated for ver info buff */
  1576.             VS_FIXEDFILEINFO    FAR    *fixed_info;    /* ptr to fixed info in ver info buff */
  1577.             UINT            fixed_size;    /* number of bytes read for ver info */
  1578.  
  1579.             /* add file name to directory */
  1580.             lstrcat( file_name, "\\" USER_EXE );
  1581.             /* see if there is a file with constructed name that contains version info */
  1582.             if( ( inf_size = GetFileVersionInfoSize( file_name, &inf_handle ) ) != 0 )
  1583.             {
  1584.                 HGLOBAL    h_mem;
  1585.  
  1586.                 /* we have an appropriate file name and the size
  1587.                    of version info buffer - allocate space for it */
  1588.                 if( NULL != ( ver_buff = (LPBYTE)GlobalLock( h_mem = GlobalAlloc( GPTR, inf_size ) ) ) )
  1589.                 {
  1590.                     /* get version information */
  1591.                     if( GetFileVersionInfo( file_name, inf_handle, inf_size, ver_buff ) != 0 )
  1592.                     {
  1593.                         /* get ptr to version info in buffer */
  1594.                         if( VerQueryValue( ver_buff, FILEINFO_KEY,
  1595.                                 (LPVOID FAR*)&fixed_info, &fixed_size ) != 0 )
  1596.                         {
  1597.                             wsprintf( file_ver, USER_EXE " File Version %u.%u\n",
  1598.                                     HIWORD(    fixed_info->dwFileVersionMS ),
  1599.                                     LOWORD( fixed_info->dwFileVersionMS ) );
  1600.                         }
  1601.                     }
  1602.                     GlobalUnlock( h_mem );
  1603.                     GlobalFree( h_mem );
  1604.                 }
  1605.             }
  1606.         }
  1607.         
  1608.         /* set up WFWG information */
  1609.         switch( win_for_wkg_check() )
  1610.         {
  1611.         case    WFWG_NOT_FOUND:
  1612.             wfwg_string    = "Not Found";
  1613.             wfwg_spacer    = "   ";
  1614.             break;
  1615.  
  1616.         case    WFWG_FOUND:
  1617.             wfwg_string    = "Found\n          but Network not Running";
  1618.             wfwg_spacer    = "       ";
  1619.             break;
  1620.  
  1621.         case    WFWG_RUNNING:
  1622.             wfwg_string    = "Found\n            and Network Running";
  1623.             wfwg_spacer    = "       ";
  1624.             break;
  1625.  
  1626.         default:
  1627.             wfwg_string    =
  1628.             wfwg_spacer    = "";
  1629.         }
  1630.  
  1631.         /* get Windows version string from the WINVER.EXE file
  1632.            which should be in the main Windows directory */
  1633.         if( GetWindowsDirectory( file_name, _MAX_PATH ) != 0 )
  1634.         {
  1635.             HFILE    h_winver;
  1636.  
  1637.             /* add the file name to the Windows directory name */
  1638.             lstrcat( file_name, "\\" WINVER_EXE );
  1639.             /* see if we can open the file */
  1640.             if( ( h_winver = _lopen( file_name, READ ) ) != HFILE_ERROR )
  1641.             {
  1642.                 /* seek to start of string and read it in */
  1643.                 if( _llseek( h_winver, WINVER_OFFSET, SEEK_SET ) != HFILE_ERROR &&
  1644.                     _lread( h_winver, winver_buff, WINVER_LENGTH ) == WINVER_LENGTH )
  1645.                 {
  1646.                     /* make sure string is terminated */
  1647.                     winver_buff[ WINVER_LENGTH ] = '\0';
  1648.                     /* check that the string looks sensible - i.e. it starts with
  1649.                        "Windows" - using the standard C function for the compare
  1650.                        as the Windows function does not allow the definition of the
  1651.                        length, and see if the string contains "Workgroup" within it */
  1652.                     if( strncmp( winver_buff, "Windows", sizeof( "Windows" ) - 1 ) == 0 &&
  1653.                         strstr( winver_buff, "Workgroup" ) != NULL )
  1654.                     {
  1655.                         /* yup - string looks OK and Workgroup found in string */
  1656.                         lstrcpy( winver_buff, WINVER_EXE " indicates Workgroups\n" );
  1657.                     }
  1658.                     else
  1659.                     {
  1660.                         /* nope - blank out string */
  1661.                         winver_buff[ 0 ] = '\0';
  1662.                     }
  1663.                 }
  1664.                 /* tidy up */
  1665.                 _lclose( h_winver );
  1666.             }
  1667.         }
  1668.  
  1669.         if( ( ( maj_vers << 8 ) + min_vers ) >= 0x030A )
  1670.         {
  1671.             /* reported version number is 3.10 or greater so
  1672.                double check because Windows for Workgroups 3.11
  1673.                and Windows 3.11 both report the version as 3.10 */
  1674.             WORD    dos_win_vers;
  1675.  
  1676.             dos_win_vers = get_dos_win_ver();
  1677.             if( dos_win_vers != VER_UNKNOWN )
  1678.             {
  1679.                 /* setup DOS information */
  1680.                 wsprintf( dos_bld_id, "       DOS reports Windows %u.%u\n",
  1681.                         HIBYTE( dos_win_vers ),
  1682.                         LOBYTE( dos_win_vers ) );
  1683.             }
  1684.             /* don't disable No File Found error box - we should *always* find USER.EXE! */
  1685.             h_userexe = LoadLibrary( USER_EXE );
  1686.             if( h_userexe > HINSTANCE_ERROR )
  1687.             {
  1688.                 if( LoadString( h_userexe, BUILD_ID, bld_temp, sizeof( bld_temp ) ) != 0 )
  1689.                 {
  1690.                     wsprintf( bld_str, "USER.EXE build id Windows %s\n", (LPCSTR)bld_temp );
  1691.                 }
  1692.                 FreeLibrary( h_userexe );
  1693.             }
  1694.         }
  1695.  
  1696.         /* disable "File Not Found" error box for
  1697.            the case where Win32s is not installed */
  1698.         SetErrorMode( SEM_NOOPENFILEERRORBOX );
  1699.  
  1700.         /* now try and load Win32s library to find out
  1701.            what (if any) version of Win32s is available */
  1702.         h_win32s = LoadLibrary( W32SYS );
  1703.         if( h_win32s > HINSTANCE_ERROR )
  1704.         {
  1705.             /* Win32s library loaded so Win32s does exist, find out if it is
  1706.                running. note that though we have loaded the W32SYS library this
  1707.                does *not* cause Win32s to run so we do get a correct result,
  1708.                a 32-bit Win32s application has to be actually running. When
  1709.                all Win32s programs have closed the test will return false.*/
  1710.             BOOL    win32s_state = win32s_running();
  1711.  
  1712.             /* get address of Win32s info function */
  1713.             Get_Win32s_Info = (GW32S_PTR)GetProcAddress( h_win32s, WIN32SINF );
  1714.             if( Get_Win32s_Info )
  1715.             {
  1716.                 int    win32_stat;
  1717.  
  1718.                 /* Win32s Version 1.1 or later installed. there is no
  1719.                    need for the slightly more complex
  1720.                     (*Get_Win32s_Info)( ... )
  1721.                    form of call used in the Win32s Programmer's Reference
  1722.                    as modern compilers know to use a function ptr to call
  1723.                    a function when the construct is appropriate. */
  1724.                 if( ( win32_stat = Get_Win32s_Info( &win32s_info ) ) == 0 )
  1725.                 {
  1726.                     /* Win32s OK - report information */
  1727.                     wsprintf( win32s_ver, "\n Supporting Version %u.%u Build %u\n"
  1728.                                   "        of Win32s - %s version\n"
  1729.                                   "             Win32s %s running",
  1730.                                 win32s_info.bMajor,
  1731.                                 win32s_info.bMinor,
  1732.                                 win32s_info.wBuildNumber,
  1733.                                 (LPCSTR)( win32s_info.fDebug ? "Debug" : "Retail" ),
  1734.                                 (LPCSTR)( win32s_state ? "is" : "not" ) );
  1735.                 }
  1736.                 else
  1737.                 {
  1738.                     /* Win32s failure */
  1739.                     static    char    *fmt = "\n         Win32s VXD not %s";
  1740.  
  1741.                     switch( win32_stat )
  1742.                     {
  1743.                     case 1:
  1744.                         wsprintf( win32s_ver, fmt, (LPCSTR)"present" );
  1745.                         break;
  1746.  
  1747.                     case 2:
  1748.                         wsprintf( win32s_ver, fmt, (LPCSTR)"loaded\n      because paging not enabled" );
  1749.                         break;
  1750.  
  1751.                     case 3:
  1752.                         wsprintf( win32s_ver, fmt, (LPCSTR)"loaded\n    because running in Standard Mode" );
  1753.                         break;
  1754.  
  1755.                     default:
  1756.                         wsprintf( win32s_ver, "\n      Win32s fail reason %d", win32_stat );
  1757.                     }
  1758.                 }
  1759.             }
  1760.             else
  1761.             {
  1762.                 /* if no function then Win32s is version 1.0 */
  1763.                 wsprintf( win32s_ver, "\nSupporting Version 1.0\n"
  1764.                         "\tof Win32s"
  1765.                         "             Win32s %s running",
  1766.                     (LPCSTR)( win32s_state ? "is" : "not" ) );
  1767.             }
  1768.             FreeLibrary( h_win32s );
  1769.         }
  1770.         else
  1771.         {
  1772.             /* no Win32s library */
  1773.             wsprintf( win32s_ver, "          Win32s not available" );
  1774.         }
  1775.         /* ++++ this compiles badly on BC++ 4.5 unless
  1776.            the wfwg_... strings are declared far. */
  1777.         wsprintf( buff, "   Windows reports Version %u.%02u\n"
  1778.                 "%s               in %s mode\n"
  1779.                 "           on Version %u.%02u of DOS\n"
  1780.                 "%sWin for Workgroups %s\n"
  1781.                 "%s"            /* WINVER.EXE Workgroups */
  1782.                 "%s\n"            /* Win32s string */
  1783.                 "\n"
  1784.                 "%s"            /* USER.EXE build id */
  1785.                 "%s"            /* USER.EXE file version */
  1786.                 "%s",            /* DOS build */
  1787.                 LOBYTE( LOWORD( win_vers ) ),
  1788.                 HIBYTE( LOWORD( win_vers ) ),
  1789.                 (LPCSTR)( ( win_flags & ( WF_ENHANCED | WF_STANDARD ) ) ?
  1790.                         "" : "    " ),
  1791.                 (LPCSTR)( win_flags & WF_ENHANCED ? "Enhanced" :
  1792.                       win_flags & WF_STANDARD ? "Standard" : "Real" ),
  1793.                 HIBYTE( HIWORD( win_vers ) ),
  1794.                 LOBYTE( HIWORD( win_vers ) ),
  1795.                 (LPCSTR)wfwg_spacer, (LPCSTR)wfwg_string,
  1796.                 (LPCSTR)winver_buff,
  1797.                 (LPCSTR)win32s_ver,
  1798.                 (LPCSTR)bld_str,
  1799.                 (LPCSTR)file_ver,
  1800.                 (LPCSTR)dos_bld_id );
  1801.     }
  1802.  
  1803.  
  1804.     /* ==========================
  1805.        end of 16-bit Windows code
  1806.        ========================== */
  1807.  
  1808.  
  1809. #endif
  1810.  
  1811.     MessageBox( NULL, buff, PROG_NAME TEXT( " : " ) TEXT( WIN_BITS ) TEXT( "-bit" ),
  1812.                 MB_OK | MB_ICONINFORMATION );
  1813.     return( FALSE );
  1814.  
  1815. }
  1816.  
  1817.  
  1818.  
  1819.  
  1820. #if    defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ )
  1821.  
  1822.  
  1823. /* =========================================
  1824.    support function only used in 32-bit mode
  1825.    ========================================= */
  1826.  
  1827.  
  1828. BOOL    reg32_access( PTCHAR key, PTCHAR val,
  1829.             LPDWORD data_type_ptr, LPBYTE data_ptr, LPDWORD data_size_ptr )
  1830. {
  1831.     /* this function reads a single value held under the
  1832.        HKEY_LOCAL_MACHINE key in the Windows NT registry */
  1833.     HKEY    reg_key;
  1834.     BOOL    ret_val = FALSE;
  1835.  
  1836.     if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, key, 0,    KEY_READ, ®_key ) )
  1837.     {
  1838.         if( ERROR_SUCCESS == RegQueryValueEx( reg_key, val, 0,
  1839.                         data_type_ptr, data_ptr, data_size_ptr ) )
  1840.             {
  1841.             /* all OK */
  1842.             ret_val = TRUE;
  1843.         }
  1844.         /* tidy up if we got key */
  1845.         RegCloseKey( reg_key );
  1846.     }
  1847.     return( ret_val );
  1848. }
  1849.  
  1850.  
  1851. #else
  1852.  
  1853.  
  1854. /* ==========================================
  1855.    support functions only used in 16-bit mode
  1856.    ========================================== */
  1857.  
  1858.  
  1859. #define        MPX_INT        0x2f        /* DOS Multiplex Interrupt */
  1860. #define        MPX_WIN_VER    0x160A        /* Get Windows Version */
  1861.  
  1862. #define        DPMI_INT    0x31        /* DPMI Interrupt */
  1863. #define        DPMI_REAL_INT    0x0300        /* DPMI call real-mode int */
  1864.  
  1865. #include    <string.h>
  1866.  
  1867. /* this uses the DPMI interface to call real-mode INT 0x2F
  1868.    to get Windows version as seen by a DOS program. If the
  1869.    program directly uses INT 0x2F it gets a protected mode
  1870.    interupt that does not support the required interface.
  1871.  
  1872.    Paul A LeBlanc of Borland's TeamB on CIS's BCPPWIN forum
  1873.    suggested the technuique of using a DPMI real-mode interrupt
  1874.    for getting at the Windows version reported to a DOS program. */
  1875. WORD    get_dos_win_ver( void )
  1876. {
  1877.     /* DPMI Function 0x0300 (real-mode interrupt) register set */
  1878.     struct    real_regs
  1879.     {
  1880.         DWORD    reg_edi;
  1881.         DWORD    reg_esi;
  1882.         DWORD    reg_ebp;
  1883.         DWORD    reg_reserved;
  1884.         DWORD    reg_ebx;
  1885.         DWORD    reg_edx;
  1886.         DWORD    reg_ecx;
  1887.         DWORD    reg_eax;
  1888.         WORD    reg_status;
  1889.         WORD    reg_es;
  1890.         WORD    reg_ds;
  1891.         WORD    reg_fs;
  1892.         WORD    reg_gs;
  1893.         WORD    reg_ip;
  1894.         WORD    reg_cs;
  1895.         WORD    reg_sp;
  1896.         WORD    reg_ss;
  1897.     } real_regs;
  1898.     static    struct    real_regs far *real_reg_ptr;
  1899.  
  1900.     /* just make sure program is running in protected mode */
  1901.     if( ( GetWinFlags() & WF_PMODE ) == 0 )
  1902.     {
  1903.         /* real mode - this should never happen! */
  1904.         return( VER_UNKNOWN );
  1905.     }
  1906.  
  1907.     /* program is already running in protected mode,
  1908.        so it is okay to make DPMI (INT 0x31) calls */
  1909.     real_reg_ptr = &real_regs;        /* init ptr to register set */
  1910.     _fmemset( real_reg_ptr, 0, sizeof( real_regs ) );
  1911.                         /* clear register set */
  1912.  
  1913.     real_regs.reg_eax = MPX_WIN_VER;    /* MPX fn to get WIN version */
  1914.  
  1915.     _asm    {
  1916.         les    di, real_reg_ptr    /* ptr to register set */
  1917.         mov    bx, MPX_INT        /* INT 0x2f - flags zero */
  1918.         mov    cx, 0            /* 0 words to real-mode stack */
  1919.         mov    ax, DPMI_REAL_INT    /* DPMI call real-mode int */
  1920.         int    DPMI_INT        /* go to it */
  1921.         jc    dpmi_bad        /* carry set on error */
  1922.         }
  1923.  
  1924.     return( LOWORD( real_regs.reg_ebx ) );    /* return word with WIN vers */
  1925.  
  1926. dpmi_bad:
  1927.     return( VER_UNKNOWN );              /* version reported to DOS not known */
  1928. }
  1929.  
  1930.  
  1931. /* the next function tried to detect whether this is a
  1932.    Windows for Workgroups installation */
  1933.  
  1934. /* things unique to the next function. These are included here
  1935.    so that special Win for Workgroups SDK stuff is not needed.
  1936.    the function prototype and the WNNC_... defines are also in
  1937.    the Workgroups SDK WINNET.H file */
  1938.  
  1939. #include    <stdlib.h>
  1940. #include    <io.h>
  1941.  
  1942. #if    !defined( WNNC_NET_TYPE )        /* if already defined don't do again */
  1943.  
  1944. /* the DEF file identifies this as in the User module */
  1945. WORD    WINAPI    WNetGetCaps( WORD );
  1946.  
  1947. #define    WNNC_NET_TYPE            0x0002
  1948. #define    WNNC_NET_MultiNet        0x8000
  1949. #define    WNNC_SUBNET_WinWorkgroups    0x0004
  1950.  
  1951. #endif
  1952.  
  1953. #define    ACC_EXIST            0    /* value for 'file exists' in access() */
  1954.  
  1955. WORD    win_for_wkg_check( void )
  1956. {
  1957.     WORD    net_flags;
  1958.     char    wfwnet_name[ _MAX_PATH ];
  1959.  
  1960.     /* check for network type */
  1961.     net_flags = WNetGetCaps( WNNC_NET_TYPE );
  1962.     /* check for mutiple-network bit */
  1963.     if( net_flags & WNNC_NET_MultiNet )
  1964.     {
  1965.         /* check low byte for WFWG bit */
  1966.         if( ( LOBYTE( net_flags ) ) & WNNC_SUBNET_WinWorkgroups )
  1967.         {
  1968.             /* net detect report Windows for Workgroups */
  1969.             return( WFWG_RUNNING );
  1970.         }
  1971.     }
  1972.     /* network not running - but this does not mean this is not
  1973.        Windows for Workgroups. construct name and check it exists */
  1974.     if( GetSystemDirectory( wfwnet_name, sizeof( wfwnet_name ) ) )
  1975.     {
  1976.         lstrcat( wfwnet_name, "\\wfwnet.drv" );
  1977.         if( access( wfwnet_name, ACC_EXIST ) == 0 )
  1978.         {
  1979.             /* if file found this system is Windows for Workgroups */
  1980.             return( WFWG_FOUND );
  1981.         }
  1982.         /* if file not found this is not Windows for Workgroups (probably!) */
  1983.     }
  1984.     return( WFWG_NOT_FOUND );
  1985. }
  1986.  
  1987.  
  1988. /* the next function detects if Win32s is actully running,
  1989.    as opposed to being installed but not running */
  1990.  
  1991. #include    <toolhelp.h>
  1992.  
  1993. #define    WIN32S_MODULE    "WIN32S16"    /* name of Win32s module loaded when
  1994.                        Win32s is actually running */
  1995.  
  1996. BOOL    win32s_running( void )
  1997. {
  1998.     MODULEENTRY    module_entry = { sizeof( MODULEENTRY ) };
  1999.  
  2000.     /* if we can find module data then Win32s is running.
  2001.        no need to dynamically link to the ModuleFindName
  2002.        function as TOOLHELP.DLL should be installed on a
  2003.        Windows 3.1 (or later) system supporting Win32s. */
  2004.     if( ModuleFindName( &module_entry, WIN32S_MODULE ) != NULL )
  2005.     {
  2006.         return( TRUE );
  2007.     }
  2008.     /* if we get here no Win32s application is running */
  2009.     return( FALSE );
  2010. }
  2011.  
  2012.  
  2013. /* the next function accesses the Windows NT Registry from a 16-bit program */
  2014.  
  2015. /* handle to Registry key pre-defined in 32-bit environment but not in 16-bit */
  2016. #define        HKEY_LOCAL_MACHINE    ( (DWORD)0x80000002UL )
  2017.  
  2018. /* and error return value - defined here to make sure we get correct 32-bit value */
  2019. #define        ERROR_SUCCESS           0
  2020.  
  2021.  
  2022. /* more variations of the CallProc32W function.
  2023.    these are all mapped to CallProc32W by the DEF file */
  2024. DWORD    FAR    PASCAL    CallProc32W__1( DWORD,
  2025.                     DWORD, DWORD, DWORD );        /* 1 param */
  2026. DWORD    FAR    PASCAL    CallProc32W__5( DWORD, DWORD, DWORD, DWORD, DWORD,
  2027.                     DWORD, DWORD, DWORD );        /* 5 params */
  2028. DWORD    FAR    PASCAL    CallProc32W__6( DWORD, DWORD, DWORD, DWORD, DWORD, DWORD,
  2029.                     DWORD, DWORD, DWORD );        /* 6 params */
  2030.  
  2031. /* to define the number of params to the function
  2032.    called - the last parameter to CallProc32W */
  2033. #define    NUM_PARAMS( xx )    ( (DWORD)( xx ) )
  2034. /* a macro to set an address translation bit for the
  2035.    CallProc32W function so that we can use the normal param
  2036.    number even though the bits are used 'in reverse'. the
  2037.    bits form the penultimate parameter to CallProc32W */
  2038. #define    ADDR( parno, numpars )    ( (DWORD)1 << ( numpars - parno ) )
  2039. /* used as penultimate param if no parameters to function
  2040.    called by CallProc32W require address translation */
  2041. #define    NO_ADDR            ( (DWORD)0 )
  2042.  
  2043.  
  2044. #define    ADVAPI32    "ADVAPI32.DLL"        /* module with 32-bit Reg... functions */
  2045.  
  2046. #define    REGOPENKEYEX    "RegOpenKeyExA"        /* name of 32-bit ASCII RegOpenKeyEx() */
  2047. #define    REGQUERYVALUEEX    "RegQueryValueExA"    /* name of 32-bit ASCII RegQueryValueEx() */
  2048. #define    REGCLOSEKEY    "RegCloseKey"        /* name of 32-bit RegCloseKey */
  2049.  
  2050. #define    REG_QUERY_VALUE    0x0001            /* manifest constant for ReqOpenKeyEx */
  2051.  
  2052.  
  2053. BOOL    reg32_access( char *key, char *val, LPDWORD data_type, LPBYTE data, LPDWORD data_size )
  2054. {
  2055.     /* this function reads a single value held under the
  2056.        HKEY_LOCAL_MACHINE key in the Windows NT registry */
  2057.     DWORD    hAdvAPI32;            /* handle to 32-bit ADVAPI32 module */
  2058.     DWORD    Reg_Open_Key_Ex;        /* address in that of 32-bit RegOpenKeyEx function */
  2059.     DWORD    Reg_Query_Value_Ex;        /* and address of 32-bit RegQueryValueEx function */
  2060.     DWORD    Reg_Close_Key;            /* and address of 32-bit RegCloseKey function */
  2061.  
  2062.     DWORD    hKey;                /* 32-bit key */
  2063.  
  2064.     BOOL    ret_val = FALSE;        /* default return value */
  2065.  
  2066.     if( ( hAdvAPI32 = LoadLibraryEx32W( ADVAPI32, NULL, 0 ) ) != NULL )
  2067.     {
  2068.         /* got 32-bit module handle OK - get 32-bit function addresses */
  2069.         if( ( Reg_Open_Key_Ex = GetProcAddress32W( hAdvAPI32, REGOPENKEYEX ) ) != NULL &&
  2070.             ( Reg_Query_Value_Ex = GetProcAddress32W( hAdvAPI32, REGQUERYVALUEEX ) ) != NULL &&
  2071.             ( Reg_Close_Key = GetProcAddress32W( hAdvAPI32, REGCLOSEKEY ) ) != NULL &&
  2072.             /* got 32-bit function addresses - access registry */
  2073.               ERROR_SUCCESS == CallProc32W__5(        /* open key */
  2074.                 HKEY_LOCAL_MACHINE, (DWORD)(LPSTR)key, 0,
  2075.                 REG_QUERY_VALUE, (DWORD)(LPDWORD)&hKey,
  2076.                     Reg_Open_Key_Ex,
  2077.                         ADDR( 2, 5  ) | ADDR( 5, 5 ),
  2078.                             NUM_PARAMS( 5 ) ) &&
  2079.               ERROR_SUCCESS == CallProc32W__6(        /* query value */
  2080.                 hKey, (DWORD)(LPSTR)val, 0,
  2081.                 (DWORD)data_type, (DWORD)data, (DWORD)data_size,
  2082.                     Reg_Query_Value_Ex,
  2083.                         ADDR( 2, 6 ) | ADDR( 4, 6 ) | ADDR( 5, 6 ) | ADDR( 6, 6 ),
  2084.                             NUM_PARAMS( 6 ) ) &&
  2085.               ERROR_SUCCESS == CallProc32W__1(        /* close key */
  2086.                 hKey,
  2087.                     Reg_Close_Key,
  2088.                         NO_ADDR,
  2089.                             NUM_PARAMS( 1 ) ) )
  2090.         {
  2091.             /* access OK */
  2092.             ret_val = TRUE;
  2093.         }
  2094.         /* tidy up whether or not we got value */
  2095.         FreeLibrary32W( hAdvAPI32 );
  2096.     }
  2097.     return( ret_val );
  2098. }
  2099.  
  2100. #endif
  2101.  
  2102.  
  2103. /* ======================================================
  2104.    support functions used in 16-bit mode and 32-bit modes
  2105.    ====================================================== */
  2106.  
  2107.  
  2108. /* note that these functions use TCHAR to compile properly for UNICODE or not */
  2109.  
  2110.  
  2111. #define    PRODUCTTYPE_KEY    "System\\CurrentControlSet\\Control\\ProductOptions"
  2112.                     /* Registry key under HKEY_LOCAL_MACHINE that tells
  2113.                        us whether host OS is NT Workstaion or NT Server */
  2114. #define    PRODUCTTYPE_VAL    "ProductType"    /* data item under that key with useful info. */
  2115. #define    PRODUCTTYPE_LEN    16        /* space to allow for product type string */
  2116.  
  2117. #define    NT_WORKSTATION    "WinNT"        /* value in Windows NT Registry that indicates
  2118.                        if this is Workstation, other entries (either
  2119.                        SERVERNT or LANMANNT) indicate server. */
  2120. #define    NT_SERVER    "ServerNT"    /* value ... for NT Server */
  2121. #define    NT_LANMAN    "LanManNT"    /* value ... for Lan Manager NT */
  2122.  
  2123. #define    PRODUCTTYPE_CSD    "CSDVersion"    /* value in the Registry under the ProductOptions
  2124.                        key for numeric CSD Version. exists on Windows
  2125.                        NT 3.5 when a service pack is loaded but not on
  2126.                        Windows NT 3.1 where the CSD Version under the
  2127.                        CurrentVersion key is numeric. */
  2128.  
  2129. /* determine Windows NT type from string */
  2130. PTCHAR    winnt_type( PTCHAR val_buff )
  2131. {
  2132.     /* start compares with value for Workstation */
  2133.     if( lstrcmpi( val_buff, TEXT( NT_WORKSTATION ) ) == 0 )
  2134.     {
  2135.         /* yes - workstation */
  2136.         return( TEXT( "Workstation" ) );
  2137.     }
  2138.     /* no - must be a server of some kind */
  2139.     if( lstrcmpi( val_buff, TEXT( NT_SERVER ) ) == 0 )
  2140.     {
  2141.         /* this is NT Server */
  2142.         return( TEXT( "Server" ) );
  2143.     }
  2144.     /* not NT server - try LAN Manager */
  2145.     if( lstrcmpi( val_buff, TEXT( NT_LANMAN ) ) == 0 )
  2146.     {
  2147.         /* this is LAN Manager on NT */
  2148.         return( TEXT( "Lan Manager" ) );
  2149.     }
  2150.     /* oops - don't recognize the type */
  2151.     return( TEXT( "( Unknown Type [Server?] )" ) );
  2152. }
  2153.  
  2154. /* the next two functions use reg32_access to be 16-bit/32-bit portable */
  2155.  
  2156. /* determines whether this is Windows NT Workstation or
  2157.    Server, works on Windows NT 3.1 and Windows NT 3.5. */
  2158. PTCHAR    nt_type( void )
  2159. {
  2160.     TCHAR    val_buff[ PRODUCTTYPE_LEN ];
  2161.     DWORD    val_buff_size = sizeof( val_buff );       /* even with UNICODE size is *ALWAYS* bytes */
  2162.     DWORD    val_type;
  2163.     PTCHAR    retstr = TEXT( "( Type Not Known )" );    /* default return string */
  2164.  
  2165.     /* open the master key for the product type information - read value and check type */
  2166.     if( reg32_access( TEXT( PRODUCTTYPE_KEY ), TEXT( PRODUCTTYPE_VAL ),
  2167.                         &val_type, (LPBYTE)val_buff, &val_buff_size ) &&
  2168.         REG_SZ == val_type )
  2169.     {
  2170.         /* get type of Windows NT */
  2171.         retstr = winnt_type( val_buff );
  2172.     }
  2173.     return( retstr );
  2174. }
  2175.  
  2176. /* function gets numeric service pack identity */
  2177. PTCHAR    nt_csd_num( void )
  2178. {
  2179.     DWORD        csd_val;
  2180.     DWORD        csd_val_size = sizeof( csd_val );
  2181.     DWORD        csd_type;
  2182.     static    TCHAR    retstr[ 64 ] = TEXT( "" );        /* default return string */
  2183.  
  2184.     /* open the master key for the product type information - read value and check type */
  2185.     if( reg32_access( TEXT( PRODUCTTYPE_KEY ), TEXT( PRODUCTTYPE_CSD ),
  2186.                         &csd_type, (LPBYTE)&csd_val, &csd_val_size ) &&
  2187.         REG_DWORD == csd_type )
  2188.     {
  2189.         /* get type of Windows NT */
  2190.         wsprintf( retstr, TEXT( "\nService Pack %lu (CSD Version 0x%lX)" ), CSD_TO_SP( csd_val ), csd_val );
  2191.     }
  2192.     return( retstr );
  2193. }
  2194.