# "Programming without Win32lib !! .

Previous lessons in this tutorial have frequently called Win32 API functions which are not yet a part of Win32lib. This tutorial goes to an extreme, by presenting a program which does not use Win32lib at all !

It may seem out of place, but I wanted to include a pair of programs in these 'tuts', which appeared almost identical, so I could illustrate how to 'clone' a simple program without using Win32lib.

The Win32lib example, speed-win32.exw, which I won't detail here, is very similar to the example in Lesson 4, only adding an LText() control in the main window.

Since I'm also a bit of a benchmarking 'freak', more out of curiosity than anything else, I've tried to include a timing routine which shows how long it takes for each of these programs to start up. The results are displayed in the LText(). If the results occasionally look suspicious, it's because I'm only using the lowest four bytes of an 8 byte counter, which may have 'rolled' over during the timing interval.

So here's Euman's code, which is not really that much different than the Euphoria example Window.exw, only adding a few more controls in it's WndProc().

-- speed-api32.exw

    -- Wolf's Tutorials Example, BUT only API is used!
    -- Please DO NOT Edit this Demo!
    -- This Demo does exactly the same thing as Wolf's
    -- Wolf wanted a speed test for some reason...
    -- here tis' enjoy!
    -- H.W Overman  - aka: Euman > euman@bellsouth.net

    without type_check

    include misc.e
    include machine.e
    include dll.e

    -- counter code variables.
    object jk, output
    atom arg, t1, t2, speed, diff

    output = 0

    -- 8 bytes for counter return value !,
    -- but I'm only using 4.
    arg=allocate(8)

    -- since the two functions required to time this demo are
    -- in kernel32, we have to link to them now.
    constant kernel32=open_dll("kernel32.dll"),
    ctr  = define_c_func( kernel32,"QueryPerformanceCounter",{C_INT},C_INT),
    freq = define_c_func( kernel32,"QueryPerformanceFrequency",{C_INT},C_INT)

    -- get counter's frequency
    jk=c_func(freq,{arg})

    -- if the 'freq' function returns zero,
    -- this CPU has no counter ?

    if jk = 0 then
      printf(1,"%s\n",{"...OOPS,NO COUNTER FOUND !"})
    else
      -- get our 'frequency' divisor
      speed=peek4u(arg)
      jk=c_func(ctr,{arg})
      -- get 'start' counter value
      t1=peek4u(arg)
    end if

    -- the next group of constants is our WNDCLASS structure definition.
    -- These are actually used as pointers into an allocated block
    -- of memory.

    constant cbSize = 0,
        style  = 4,
        lpfnWndProc = 8,
        cbClsExtra = 12,
        cbWndExtra = 16,
        hInstance  = 20,
        hIcon      = 24,
        hCursor    = 28,
        hbrBackground = 32,
        lpszMenuName  = 36,
        lpszClassName = 40,
        hIconSm = 44,
        SIZE_OF_WNDCLASS = 48

    constant SIZE_OF_MESSAGE = 40

    constant SW_SHOWNORMAL = 1

    -- the Windows messages we will look for in our WndProc() callback function.
    constant WM_CREATE = #01,
        WM_COMMAND = #111,
        WM_DESTROY= #02,
        WM_SETFONT = #30,
        WM_DRAWITEM = 43,
        WM_ACTIVATE = 6

    constant MF_STRING = 0,
        MF_POPUP = #10

    constant SS_OWNERDRAW = #D,
        ES_LEFT = 0

    constant DEFAULT_GUI_FONT = 17

    constant TRANSPARENT = 1

    constant COLOR_BTNFACE = 15

    -- refers to the Windows 'stock' application icon.
    constant IDI_APPLICATION = 32512

    function or_all(sequence s)
    -- or together all elements of a sequence
        atom result

        result = 0
        for i = 1 to length(s) do
            result = or_bits(result, s[i])
        end for
        return result
    end function

    constant MB_OK = 0,
        MB_APPLMODAL = 0

    -- our Window 'style' parameters.
    constant WS_OVERLAPPED = #00000000,
        WS_CAPTION     = #00C00000,
        WS_SYSMENU     = #00080000,
        WS_THICKFRAME  = #00040000,
        WS_MINIMIZEBOX = #00020000,
        WS_MAXIMIZEBOX = #00010000,
        WS_CHILD       = #40000000,
        WS_VISIBLE     = #10000000

    constant IDC_ARROW = 32512,  -- the 'stock' mouse pointer.
        CW_USEDEFAULT = #80000000,

    -- the 'generic' window style
        WS_OVERLAPPEDWINDOW = or_all({WS_OVERLAPPED,WS_CAPTION,
          WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX})

    -- The following integer variables will be used to 'hold'
    -- all the 'links' to the API functions and procedures
    -- that this program uses.

    integer
    LoadIcon, LoadCursor, GetStockObject, RegisterClassEx,
    xCreateWindow, ShowWindow, UpdateWindow, DestroyWindow, GetMessage,
    TranslateMessage, DispatchMessage, PostQuitMessage, DefWindowProc,
    CreateMenu, CreatePopupMenu, AppendMenu, xMessageBox, xSendMessage,
    SetFocus, SetWindowText, xTextOut, xSetBkMode, xlstrlen

    -- this is used to report errors
    procedure not_found(sequence name)
        puts(1, "Couldn't find " & name & '\n')
        sleep(2)
        abort(1)
    end procedure

    function link_c_func(atom dll, sequence name, sequence args, atom result)
    -- dynamically link a C routine as a Euphoria function
        integer handle

        handle = define_c_func(dll, name, args, result)
        if handle = -1 then
            not_found(name)
        else
            return handle
        end if
    end function

    function link_c_proc(atom dll, sequence name, sequence args)
    -- dynamically link a C routine as a Euphoria procedure
    -- ignoring any return value.
        integer handle

        handle = define_c_proc(dll, name, args)
        if handle = -1 then
            not_found(name)
        else
            return handle
        end if
    end function

    procedure link_dll_routines()
    -- get handles to the dll's,
    -- and the dll routines, that we need
      atom user32, gdi32

      user32 = open_dll("user32.dll")
      if user32 = NULL then
          not_found("user32.dll")
      end if
      gdi32 = open_dll("gdi32.dll")
      if gdi32 = NULL then
          not_found("gdi32.dll")
      end if

      LoadIcon = link_c_func(user32, "LoadIconA", {C_POINTER, C_INT}, C_INT)
      LoadCursor = link_c_func(user32, "LoadCursorA", {C_POINTER, C_INT}, C_INT)
      GetStockObject = link_c_func(gdi32, "GetStockObject", {C_INT}, C_INT)
      RegisterClassEx = link_c_func(user32, "RegisterClassExA", {C_POINTER}, C_INT)
      xCreateWindow = link_c_func(user32, "CreateWindowExA",
       {C_INT, C_INT,C_INT,C_INT,C_INT,C_INT,C_INT,C_INT,C_INT,C_INT,C_INT,C_INT},C_INT)
      ShowWindow = link_c_proc(user32, "ShowWindow", {C_INT, C_INT})
      UpdateWindow = link_c_proc(user32, "UpdateWindow", {C_INT})
      DestroyWindow = link_c_func(user32, "DestroyWindow", {C_INT}, C_INT)
      GetMessage = link_c_func(user32, "GetMessageA", {C_INT, C_INT, C_INT, C_INT}, C_INT)
      TranslateMessage = link_c_proc(user32, "TranslateMessage", {C_INT})
      DispatchMessage = link_c_proc(user32, "DispatchMessageA", {C_INT})
      PostQuitMessage = link_c_proc(user32, "PostQuitMessage", {C_INT})
      DefWindowProc = link_c_func(user32, "DefWindowProcA",
       {C_INT, C_INT, C_INT, C_INT}, C_INT)

      -- Menu Handling
      CreateMenu = link_c_func(user32, "CreateMenu", {}, C_LONG)
      CreatePopupMenu = link_c_func(user32, "CreatePopupMenu", {}, C_LONG)
      AppendMenu = link_c_func(user32, "AppendMenuA", {C_LONG, C_LONG, C_LONG, C_POINTER}, C_LONG)

      xMessageBox = link_c_func(user32, "MessageBoxA",
       {C_LONG, C_POINTER, C_POINTER, C_LONG}, C_LONG)
      xSendMessage = link_c_func(user32, "SendMessageA", {C_LONG, C_LONG, C_INT, C_LONG}, C_LONG)
      SetFocus=link_c_proc(user32,"SetFocus",{C_POINTER})
      SetWindowText=link_c_func(user32,"SetWindowTextA",{C_POINTER,C_POINTER},C_INT)

      xTextOut = link_c_proc(gdi32, "TextOutA", {C_POINTER, C_INT, C_INT, C_POINTER, C_INT})
      xSetBkMode = link_c_func(gdi32, "SetBkMode", {C_POINTER, C_INT}, C_INT)
      xlstrlen = link_c_func(kernel32,"lstrlen",{C_POINTER},C_INT)

    end procedure

    link_dll_routines()  -- run the above procedure

    function LOWORD(atom long)
        return remainder(long,65536)
    end function

    object junk

    -- we call the next function from our WinMain() once,
    -- to create the main window, then twice from our WndProc(),
    -- to create our button and text box controls.
    function CreateWindow(atom dwExStyle, object ClassName, object WindowName,
                         atom dwStyle, atom x, atom y, atom Width, atom Height,
                         atom Parent, atom Mnu, atom Instance, integer void)

    atom classname, windowname, id

      if atom(ClassName) then
      classname = ClassName
      else
      classname = allocate_string(ClassName)
      end if

      if atom(WindowName) then
      windowname = WindowName
      else
      windowname = allocate_string(WindowName)
      end if

      id = c_func(xCreateWindow,{dwExStyle,classname,windowname,dwStyle,
                  x,y,Width,Height,Parent,Mnu,Instance,void})

      free(classname)
      free(windowname)

      return id
    end function

    -- sends text out to the TextBox device context
    procedure TextOut(atom hdc, integer x,integer y, atom pStr, integer size)
        c_proc(xTextOut,{hdc,x,y,pStr,size})
    end procedure

    -- returns a string from memory
    function peek_zstring(atom lpzString)
        return peek({lpzString,c_func(xlstrlen,{lpzString})})
    end function

    atom hMenu, pMenu, IDM_EXIT

    IDM_EXIT = 100

    function createMenu()
    -- create a popup menu ( which *IS* a dropdown menu  ;-)  )
        pMenu = c_func(CreatePopupMenu, {})

    -- add an item to the dropdown
        junk = c_func(AppendMenu, {pMenu, MF_STRING,
                  IDM_EXIT, allocate_string("&Now!")})

    -- create our main menu
        hMenu = c_func(CreateMenu, {})

    -- add an item to the main menu, *and* pass the handle of our dropdown
        junk = c_func(AppendMenu, {hMenu, MF_STRING + MF_POPUP,
                  pMenu, allocate_string("&Exit")})

        return hMenu

    end function

    atom AboutBox, TextBox, IDM_ABOUTBOX, txtbuff, title, stdc

    IDM_ABOUTBOX = 101

    integer cmd

    global function WndProc(atom hwnd, atom iMsg, atom wParam, atom lParam)
    -- callback routine to handle this window's messages

      if iMsg = WM_CREATE then
        -- the main window has been created, so create the AboutBox,
        -- the TextBox, set their fonts, and set the focus.
        AboutBox = CreateWindow(0,"button","About",
            or_all({WS_CHILD,WS_VISIBLE}),10, 10, 50, 25, hwnd, IDM_ABOUTBOX, 0, 0)

        TextBox = CreateWindow(0,"STATIC","",
            or_all({WS_CHILD,WS_VISIBLE,ES_LEFT,SS_OWNERDRAW}),5,50,180,40,hwnd,0,0,0)

        junk = c_func(xSendMessage,{AboutBox,
                 WM_SETFONT, c_func(GetStockObject,{DEFAULT_GUI_FONT}), 0})
        junk = c_func(xSendMessage,{TextBox,
                 WM_SETFONT, c_func(GetStockObject,{DEFAULT_GUI_FONT}), 0})

        c_proc(SetFocus,{AboutBox})

        return 0

      elsif iMsg=WM_DRAWITEM then -- tricky code !

        stdc = peek4s(lParam + 24)
        junk = c_func(xSetBkMode, {stdc, TRANSPARENT})
        if output != 0 then
          c_proc(xTextOut,{stdc,0,0,output,length(peek_zstring(output))})
        end if

        return 0

    -- the following sets the focus to the button whenever
    -- the main window receives focus.
      elsif iMsg=WM_ACTIVATE then

        if LOWORD(wParam) > 0 then
          c_proc(SetFocus,{AboutBox})
    --    else
    --      Loosing Focus
        end if

        return 0

      elsif iMsg = WM_COMMAND then

        cmd = LOWORD(wParam)

        if cmd = IDM_EXIT then

          junk  = c_func(DestroyWindow, {hwnd})

        elsif cmd = IDM_ABOUTBOX then
          -- this MessageBox is a 'stock' control that's
          -- created every time we click on the button,
          -- and automatically destroyed by Windows
          -- when it's 'Okay' button is clicked.
          txtbuff = allocate_string("...it's GREAT !")
          title = allocate_string("* Euphoria *")

          junk = c_func(xMessageBox, {hwnd, txtbuff, title,
                  or_all({MB_APPLMODAL, MB_OK})})

          free(txtbuff)
          free(title)

        end if

        return 0

      elsif iMsg = WM_DESTROY then

        c_proc(PostQuitMessage, {0})

        return 0

      end if

      -- any messages not handled by our WndProc routine, are returned
      -- to Windows for it's default message handler routines...
      return c_func(DefWindowProc, {hwnd, iMsg, wParam, lParam})
    end function

    atom my_title
    my_title = allocate_string("..using API")

    procedure WinMain()
    -- the main routine,
    -- which creates our main window,
    -- sets up the callback to our WndProc() routine, and
    -- starts the message loop for our whole program.

        atom szAppName
        atom hwnd
        atom msg
        atom wndclass
        atom WndProcAddress
        atom class
        integer id
        atom icon_handle

        wndclass = allocate(SIZE_OF_WNDCLASS)
        msg = allocate(SIZE_OF_MESSAGE)
        szAppName = allocate_string("EumansWin")

        id = routine_id("WndProc")
        if id = -1 then
            puts(1, "routine_id failed!\n")
            sleep(2)
            abort(1)
        end if
        WndProcAddress = call_back(id) -- get 32-bit address for callback

        -- fill in the WNDCLASS structure
        poke4(wndclass + cbSize, SIZE_OF_WNDCLASS)
        poke4(wndclass + style, 0)  --or_bits(CS_HREDRAW, CS_VREDRAW))
        poke4(wndclass + lpfnWndProc, WndProcAddress)
        poke4(wndclass + cbClsExtra, 0)
        poke4(wndclass + cbWndExtra, 0)
        poke4(wndclass + hInstance, 0) --hInstance

        icon_handle=c_func(LoadIcon, {NULL, IDI_APPLICATION})
        poke4(wndclass+hIcon,icon_handle)
        poke4(wndclass+hIconSm,icon_handle)
        poke4(wndclass+hCursor,c_func(LoadCursor,{NULL,IDC_ARROW}))

        poke4(wndclass+hbrBackground,COLOR_BTNFACE+1)
        poke4(wndclass + lpszMenuName, NULL)
        poke4(wndclass + lpszClassName, szAppName)

        class = c_func(RegisterClassEx, {wndclass})
        if class = 0 then
        puts(1, "Couldn't register class\n")
        sleep(2)
        abort(1)
        end if

        -- create menu *before* creating main window
        hMenu = createMenu()

        -- create main window
        hwnd = CreateWindow(
        0,                     -- extended style
        szAppName,             -- window class name
        my_title,              -- window caption
        WS_OVERLAPPEDWINDOW,   -- window style
        CW_USEDEFAULT,         -- initial x position
        CW_USEDEFAULT,         -- initial y position
        200,                   -- initial x size
        150,                   -- initial y size
        NULL,                  -- parent window handle
        hMenu,                 -- window menu handle
        0,                     -- hInstance // program instance handle
        NULL)                  -- creation parameters

        if hwnd = 0 then
            puts(1, "Couldn't CreateWindow\n")
            sleep(2)
            abort(1)
        end if

        -- show, and repaint the main window
        c_proc(ShowWindow,{hwnd,SW_SHOWNORMAL})
        c_proc(UpdateWindow,{hwnd})

      -- calculate, and display elapsed time
      jk=c_func(ctr,{arg})
      t2=peek4u(arg)
      diff=t2-t1
      output=allocate_string(sprintf(
                             "program time = %.8f sec.",{(diff/speed)}))
      jk=c_func(SetWindowText,{TextBox,output})

        -- the message loop !
        while c_func(GetMessage, {msg, NULL, 0, 0}) do
          c_proc(TranslateMessage, {msg})
          c_proc(DispatchMessage, {msg})
        end while

    end procedure

    WinMain()

    --------

Folks familiar with Win32lib code might argue that the results of this speed test are unfair, because the timer results are calculated before the program goes into it's message loop. I did, however, modify a copy of win32lib.ew, and placed the ( timer ) code at the top of win32lib's WndProc() routine. In 'bound' examples of both win32lib programs, there was very little difference.

speed-api32.exe was still over 12 times faster starting up, on average, than speed-win32.exe.

Another difference, of course, is that the 'bound' version of speed-api32.exe is over 60 kilobytes smaller than the win32lib version.

Doing a simple 'startup' benchmark like this doesn't always give an accurate picture. In another test, I simply added some code like the example below to both programs, just to call an API function an additional 100 times.

Using 'bound' versions of both, speed-api32.exe was only 6 times faster.

    for i = 1 to 100 do  -- for win32 example
    setText(SpText,"          ")
    end for
    --------
    for i = 1 to 100 do  -- for api32 example
    str = allocate_string("          ")
    jk = c_func(SetWindowText,{TextBox, str})
    free(str)
    end for

Doing a comparison without binding would really be unfair, because EU would have to load win32lib.ew from disk or cache during the timing interval.

( using Euphoria version 2.3, which already 'strips' most unused constants/routines etc., and win32lib version 0.57.0 )

RE: -- tricky code !
Euman's used some rather unusual code to give the text box the same background color as our main window.

When the TextBox is created, he's using the SS_OWNERDRAW style.
Upon creation of this control, Windows sends a WM_DRAWITEM message to our WndProc().
lParam of this message contains a pointer to a DRAWITEMSTRUCT structure, in which offset 24 contains the hDC of the TextBox.
He then sets the background mode of this DC to transparent, so the main window's background shows 'thru'.

..end of lesson.

CONTENTS