Win32Inc ASMCtrl Sample

About

This is an OCX control written for Win32Inc. To make the control visible, COMView is used to create an instance of the class. It should also be mentioned that helper macros contained in OLECNTRL.INC are used, which hide a good deal of the complexity of a full-featured OCX control.

Screenshot

ASMCtrl showed inside COMView

Source Code

ASMCtrl.ASM

;--- AsmCtrl: An OCX control in ASM

;--- naming conventions:
;--- - the typelib/module is called AsmCtrl
;--- - the coclass/CLSID is called AsmClass, with interface IAsmClass
;--- that should be distinguished, because a typelib may define
;--- more than 1 coclass/CLSID.

;--- So in this file define infos on the typelib level
;--- needed for DllRegisterServer, DllUnregisterServer + DllGetClassObject.
;--- Describe each coclass in its own source file (C.asm)

;--- this sample can be used as a template. To create a control do:
;--- 1. adjust all infos in the .IDL file (AsmCtrl.idl)
;--- 2. generate include file(s) with COMView (AsmCtrl.inc)
;--- 3. adjust AsmCtrl.asm (this file) if necessary (name of coclass i.e.)
;--- 4. supply CLSID specific definitions in .INC file (CAsmClass.inc)
;--- 5. supply CLSID specific data/procs  in .ASM file (CAsmClass.asm)

    .386
    .model flat, stdcall
    option casemap:none
    option proc:private

    .nolist
    .nocref
WIN32_LEAN_AND_MEAN equ 1
INC_OLE2            equ 1
    include windows.inc
    include olectl.inc

    include macros.inc
    include disphlp.inc
    include olecntrl.inc
    include debugout.inc
    .list
    .cref

    includelib  kernel32.lib
    includelib  advapi32.lib
    includelib  user32.lib
    includelib  gdi32.lib
    includelib  oleaut32.lib
    includelib  ole32.lib
    includelib  uuid.lib

    include AsmCtrl.inc     ;COMView generated: AsmCtrl interfaces
    include CAsmClass.inc

;--------------------------------------------------------------------------

externdef g_DllRefCount:DWORD

    .const

LIBID_AsmCtrl   sTLBID_AsmCtrl

;--- the DEBUGPREFIX is a string to distinguish our output
;--- from others on the debug terminal. look debugout.inc for details

ifdef _DEBUG
externdef DEBUGPREFIX:LPSTR
DEBUGPREFIX LPSTR CStr("AsmCtrl:")
endif

;--- the object table: defines coclasses installed by this module
;--- used by DllGetClassObject, DllRegisterServer + DllUnregisterServer

;--- ObjectEntry = {REFGUID pClsId;
;---            REFGUID pLibId; SWORD wVerMajor; SWORD wVerMinor;
;---            LPREGSTRUCT pRegKeys;
;---            LPCONSTRUCTOR constructor}

BEGIN_OBJECT_MAP ObjectMap
    ObjectEntry {\
        offset CLSID_CAsmClass,\
        offset LIBID_AsmCtrl, _MajorVer_AsmCtrl, _MinorVer_AsmCtrl,\
        offset RegKeys_CAsmClass,\
        Create@CAsmClass}
;-------------------------------------------
;--- include further ObjectEntry {} here ---
;-------------------------------------------
END_OBJECT_MAP

;--------------------------------------------------------------------------

;--- define standard COM functions
;--- one instance for all coclasses in this module

    DEFINE_COMHELPER
    DEFINE_CLASSFACTORY
    DEFINE_GETCLASSOBJECT offset ObjectMap
    DEFINE_REGISTERSERVER offset ObjectMap
    DEFINE_UNREGISTERSERVER offset ObjectMap
    DEFINE_CANUNLOADNOW
    DEFINE_DLLMAIN      ;DllMain std (saves hInstance in g_hInstance)


end DllMain

 
 

CASMClass.ASM

;*** methods of CAsmClass coclass

;--- How to add new properties/methods to custom Interface IAsmClass
;--- 1. AsmCtrl.IDL: add the properties/methods to IAsmClass
;--- 2. add the properties/methods to vtable CAsmClassVtbl in this file
;--- 3. add new variables - if required - to CAsmClass.inc
;--- 4. add implementation of new functions in this file.
;---    properties require a get/set function pair.
;--- 5. to access new variables with m_ prefix, use the MEMBER macro
;---    as it is done in this file (search for "member")

    .586
    .model flat, stdcall
    option casemap:none ; case sensitive
    option proc:private

    .nolist
    .nocref
WIN32_LEAN_AND_MEAN equ 1
INC_OLE2            equ 1
    include windows.inc
    include olectl.inc
    
    include macros.inc
    include disphlp.inc
    include olecntrl.inc
    include debugout.inc

    include AsmCtrl.inc     ;type information (COMView generated)
    include AsmCtrlc.inc    ;dispatch helper (COMView generated)
    .list
    .cref

    include CAsmClass.inc

Destroy@CAsmClass           proto :ptr CAsmClass
AboutBox                    PROTO :ptr CAsmClass
aboutBoxProc                PROTO :HWND, :DWORD, :WPARAM, :LPARAM

;--- exit procs: called from the macros in olecntrl.inc
;--- to ensure these exits are called place the prototypes here

OnInitNew@CAsmClass         PROTO :ptr CAsmClass
OnInPlaceActivate@CAsmClass PROTO :ptr CAsmClass, :HWND, :ptr RECT
OnDraw@CAsmClass            PROTO :ptr CAsmClass, :ptr DVTARGETDEVICE, :HDC, :HDC, :ptr RECT, :ptr RECT
GetPages@CAsmClass          PROTO :ptr CAsmClass, :ptr CAUUID
OnDoVerb@CAsmClass          PROTO :ptr CAsmClass, :SDWORD, :ptr MSG, :LPOLECLIENTSITE, :HWND, :ptr RECT
OnInvoke@CAsmClass          PROTO :ptr CAsmClass, :DWORD, :DWORD, :ptr DISPPARAM, :ptr VARIANT, :ptr EXCEPINFO, :ptr DWORD

IDD_ABOUT       EQU 1000    ;resource ID

?INVOKELOG      equ 1       ;log all IDispatch::Invoke being called (debug only)

    .const

CLSID_CAsmClass     sCLSID_AsmClass
IID_IAsmClass       sIID_IAsmClass
IID__AsmClassEvent  sIID__AsmClassEvent

;--- add new interface IIDs here if required

;IID_IDummy         IID <1,2,3,<4,5,6,7,8,9,0Ah,0Bh>>

;-----------------------------------------------------------------
;--- define the IIDs for outgoing interfaces (must match ?NUMCP)
;--- one outgoing interface as here will do in most cases
;-----------------------------------------------------------------

BEGIN_CONNECTION_POINT_MAP CAsmClass
    CONNECTION_POINT_ENTRY IID__AsmClassEvent
END_CONNECTION_POINT_MAP

;-----------------------------------------------------------------
;--- IOleObject
;-----------------------------------------------------------------

szWndClassName  BYTE "CAsmClassClass",0     ; window class name

    .data

g_bRegistered   BYTE FALSE      ; flag if our window class is registered


;--------------------------------------------------------------------------
;--- IUnknown interface: the table of supp. interfaces must be defined
;--------------------------------------------------------------------------

    .const

    DEFINE_KNOWN_INTERFACES CAsmClass, IAsmClass

;--- to add new interfaces to the list of interfaces known by QueryInterface
;--- add it to DEFINE_KNOWN_INTERFACES like this:

;   DEFINE_KNOWN_INTERFACES CAsmClass, IAsmClass, IDummy

;-----------------------------------------------------------------
;--- ICategorizeProperties: Array of DISPIDs belonging to specific Category
;-----------------------------------------------------------------

;--- this interface is for VB support to group properties a bit nicer

PropArray1 dd 0,1,2, DISPID_FORECOLOR, DISPID_BACKCOLOR, DISPID_ABOUTBOX
NUMPROPARRAY1 equ ($ - offset PropArray1) / sizeof DWORD

;--- category table (ID, Name, ptr to DispID-Array, length of Array)

Categories label dword
    Category <1, CStr("AsmClass"), offset PropArray1, NUMPROPARRAY1>
NUMCATEGORIES equ ($ - offset Categories) / sizeof Category


;-----------------------------------------------------------------
;--- IPersistStreamInit, IPersistPropertyBag: table of persistant properties
;-----------------------------------------------------------------

;--- here bags can be defined which will be saved/loaded automatically

BagTab label BagEntry
    BagEntry {VT_I4, CAsmClass.dwValue,     CStrW(L("Value"))   }
    BagEntry {VT_I4, CAsmClass.dwBackColor, CStrW(L("BackColor"))}
    BagEntry {VT_I4, CAsmClass.dwForeColor, CStrW(L("ForeColor"))}
    BagEntry {VT_I2, CAsmClass.wClientEdge, CStrW(L("ClientEdge"))}
    BagEntry {VT_I4, CAsmClass.himetricExtent.cx_,CStrW(L("Width"))}
    BagEntry {VT_I4, CAsmClass.himetricExtent.cy,CStrW(L("Height"))}
NUMBAGS equ ($ - offset BagTab) / sizeof BagEntry

;--- define max size for IPersistStreamInit: so no exit proc needed

?SAVESIZE equ (sizeof CAsmClass.dwValue + sizeof CAsmClass.dwBackColor +\
    sizeof CAsmClass.dwForeColor + sizeof CAsmClass.wClientEdge +\
    sizeof CAsmClass.himetricExtent)


;--------------------------------------------------------------------------
;--- IDispatch interface
;--------------------------------------------------------------------------

;--- the IDispatch/IAsmClass vtable. This vtable is our responsibility
;--- it is strongly recommended being located at offset 0 in the 
;--- CAsmClass structure - thats first entry in BEGIN_COM_MAP table!
;--- Otherwise we could not use the "standard" IUnknown procs
;--- and would have to adjust "this" in all of the IAsmClass methods

CAsmClassVtbl label IAsmClassVtbl
    IDispatchVtbl {QueryInterface@CAsmClass, AddRef@CAsmClass, Release@CAsmClass,\
        GetTypeInfoCount_, GetTypeInfo_, GetIDsOfNames_, Invoke_}
    dd put_Value, get_Value
    dd Raise
    dd put_ForeColor, get_ForeColor
    dd put_BackColor, get_BackColor
    dd put_ClientEdge, get_ClientEdge
    dd AboutBox
    dd put_Value2, get_Value2
;---- add new methods/properties here
;   dd put_PropertyX, get_PropertyX


;--------------------------------------------------------------------------
;--- define table of registry entries for this CLSID
;--- REGSTRUCT  = {LPSTR lpszSubKey; LPSTR lpszValueName; LPSTR lpszData}
;--- -1 in lpszSubKey -> key in HKEY_CLASSES_ROOT, in which next
;--- entries will be written. %s is szCLSID in such entries.
;--- else: -1 in lpszData: replaced by szCLSID, -2 in lpszData: replaced by szTLBID
;--- %s is module path in such entries
;--------------------------------------------------------------------------

IDI_ICON        equ 0       ;index of icon resource for DefaultIcon
IDB_BITMAP      equ 299
MISCSTATUS      equ OLEMISC_SETCLIENTSITEFIRST + OLEMISC_ACTIVATEWHENVISIBLE + \
                    OLEMISC_INSIDEOUT + OLEMISC_RECOMPOSEONRESIZE

DEFAULTICON     textequ @CatStr(!",%IDI_ICON,!")
TOOLBOXBITMAP   textequ @CatStr(!",%IDB_BITMAP,!")
MISCSTATUS$     textequ @CatStr(!",%MISCSTATUS,!")
ProgId          textequ <"AsmClass">
Description     textequ <"OCX control in pure ASM">

?COMPCAT    = 1 ;1= define "Implemented Categories" registry keys
sGUID_SavelyScriptable      textequ <"{7DD95801-9882-11CF-9FA9-00AA006C42C4}">
sGUID_SavelyInitializable   textequ <"{7DD95802-9882-11CF-9FA9-00AA006C42C4}">

RegKeys_CAsmClass label REGSTRUCT
    REGSTRUCT <-1, 0, CStr("CLSID\%s")>
    REGSTRUCT <0, 0, CStr(Description)>
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT )>
    REGSTRUCT )>
    REGSTRUCT )>
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT )>
if ?COMPCAT
    REGSTRUCT 
    REGSTRUCT ), 0, 0>
    REGSTRUCT ), 0, 0>
endif
    REGSTRUCT <-1, 0, CStr(ProgId)>
    REGSTRUCT <0, 0, CStr(Description)>
    REGSTRUCT 
    REGSTRUCT )>
    REGSTRUCT <-1, 0, CStr()>
    REGSTRUCT <0, 0, CStr(Description)>
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT <-1, 0, 0>

;-----------------------------------------------------------------

    .code

__this  textequ 
_this   textequ <[__this].CAsmClass>

;--------------------------------------------------------------------------
;--- provide short names for our members so "dwValue"
;--- can be accessed with "m_dwValue" instead of "[ebx].CAsmClass.dwValue"
;--------------------------------------------------------------------------

    MEMBER dwValue, dwBackColor, dwForeColor, wClientEdge, szText, dwValue2
;--- add new variables here    
;   MEMBER dwDummy

;-----------------------------------------------------------------
;--- the following macros include most of the code needed 
;-----------------------------------------------------------------
;--- DEFINE_DISPATCH: needs IID of IAsmClass as parameter
;--- DEFINE_STD_COM_METHODS: include code for all other interfaces
;-----------------------------------------------------------------

    DEFINE_DISPATCH CAsmClass, IID_IAsmClass
    DEFINE_STD_COM_METHODS CAsmClass

    DEFINE_FIREEVENTHELPER

;--------------------------------------------------------------------------
;--------------- constructor CAsmClass
;--------------------------------------------------------------------------

Create@CAsmClass    proc public uses esi __this pClass: ptr ObjectEntry, pUnkOuter:LPUNKNOWN
    
    invoke LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT,sizeof CAsmClass
    .if (eax == NULL)
        ret
    .endif
    mov __this,eax

;--------------------------------------------------------------------------
;--- the IAsmClass/IDispatch interface is in our responsibility
;--- so set the vtable ptr now
;--------------------------------------------------------------------------

    mov m__IDispatch, OFFSET CAsmClassVtbl

;--- for other new interfaces, initialize vtable ptr here    
    
;   mov m__IDummy, OFFSET CDummyVtbl_CAsmClass    

;--------------------------------------------------------------------------
;--- include the standard COM constructor code
;--------------------------------------------------------------------------

    mov esi, pClass
    assume esi:ptr ObjectEntry
    STD_COM_CONSTRUCTOR CAsmClass, [esi].pLibId, [esi].dwVerMajor, [esi].dwVerMinor
    assume esi:nothing

;--------------------------------------------------------------------------
;--- init member variables
;--------------------------------------------------------------------------

    mov eax, 0808080H
    mov m_dwForeColor, eax
    mov eax, 0000000H
    mov m_dwBackColor, eax

;--- add other member initialization here

;   mov m_dwDummy, 1

;--------------------------------------------------------------------------
;--- a LPUNKNOWN must be returned since we will support aggregation
;--------------------------------------------------------------------------
if ?AGGREGATION
    lea eax, m__IUnknown
    ret
else
    return __this
endif

Create@CAsmClass    endp

;--------------------------------------------------------------------------
;--------------- destructor
;--------------------------------------------------------------------------

Destroy@CAsmClass PROC public uses __this this_:ptr CAsmClass

    DebugOut "Destroy@CAsmClass enter"

    mov __this,this_

;--- add other deinit code here (releasing interfaces, closing files,...)

;--------------------------------------------------------------------------
;--- include the standard COM destructor code
;--------------------------------------------------------------------------

    STD_COM_DESTRUCTOR CAsmClass

    invoke LocalFree, __this

    DebugOut "Destroy@CAsmClass exit"
    ret

Destroy@CAsmClass ENDP


;--------------------------------------------------------------------------
;--- now come some exits. These procs are called from inside olecntrl.inc.
;--------------------------------------------------------------------------
;------------------------------------------------------------------------
;--- IPersistStreamInit::InitNew
;------------------------------------------------------------------------

OnInitNew@CAsmClass  PROC this_:ptr CAsmClass

;--- initialize object (currently nothing to do)

    return S_OK

OnInitNew@CAsmClass  endp

;------------------------------------------------------------------------
;--- IViewObject::Draw
;------------------------------------------------------------------------

;--- this exit routine draws content (with or without window)

OnDraw@CAsmClass proc this_:ptr CAsmClass, ptd:ptr DVTARGETDEVICE,
         hicTargetDev:HDC, hdcDraw:HDC, lprcBounds:ptr RECT, lprcWBounds:ptr RECT

local   szText[256]:byte
local   szId[16]:byte
LOCAL   tRect:RECT

    mov eax, m_dwBackColor
    invoke CreateSolidBrush, eax
    push eax
    invoke FillRect, hdcDraw, lprcBounds, eax
    pop eax
    invoke DeleteObject, eax
    invoke SetTextColor, hdcDraw, m_dwForeColor
    push eax
    invoke SetBkColor, hdcDraw, m_dwBackColor
    push eax
    push ebx
    mov eax,0
    cpuid
    mov dword ptr szId+0,ebx
    pop ebx
    mov dword ptr szId+4,edx
    mov dword ptr szId+8,ecx
    mov szId+12,0
    mov ecx,eax
    invoke wsprintf,addr szText, CStr(<"Max CPUID value=%u",13,10,"%s",13,10,"%s">),ecx,addr szId,addr m_szText
    invoke CopyRect,addr tRect, lprcBounds
    invoke DrawText, hdcDraw, ADDR szText, -1, addr tRect, DT_CALCRECT
    mov ecx,lprcBounds
;------------------------------------ center text manually
    mov edx,[ecx].RECT.bottom
    sub edx,[ecx].RECT.top
    .if (edx >= eax)
        sub edx,eax
        shr edx,1
        add tRect.top,edx
        add edx,eax
        add tRect.bottom,edx
    .endif

    mov eax, tRect.right
    sub eax, tRect.left

    mov edx,[ecx].RECT.right
    sub edx,[ecx].RECT.left
    .if (edx >= eax)
        sub edx,eax
        shr edx,1
        add tRect.left,edx
        add edx,eax
        add tRect.right,edx
    .endif

    invoke DrawText, hdcDraw, ADDR szText, -1, addr tRect, 0

    pop eax
    invoke SetBkColor, hdcDraw, eax
    pop eax
    invoke SetTextColor, hdcDraw, eax
    return S_OK

OnDraw@CAsmClass endp

;------------------------------------------------------------------------
;--- IOleObject
;------------------------------------------------------------------------

;--- this exit is called from inside IOleObject:DoVerb
;--- we are being in-place-activated, create window

OnInPlaceActivate@CAsmClass  PROC uses __this this_:ptr CAsmClass, hwndParent:HWND,lprcRect:ptr RECT

    local wc:WNDCLASS
    local dwXPos:dword
    local dwYPos:dword
    local dwCX:dword
    local dwCY:dword

    mov __this, this_

;---------------------------- register window class it not done yet

    .if (!g_bRegistered)
        mov wc.style, 0
        mov wc.lpfnWndProc, OFFSET wndproc
        mov wc.cbClsExtra,NULL
        mov wc.cbWndExtra,4
        push  g_hInstance
        pop wc.hInstance
        mov wc.hbrBackground,0
        mov wc.lpszMenuName,NULL                    ;OFFSET MenuName
        mov wc.lpszClassName,offset szWndClassName
        mov wc.hIcon,NULL
        mov eax, 32512              ;IDC_ARROW
        invoke LoadCursor,NULL,eax
        mov wc.hCursor,eax
        invoke RegisterClass, addr wc
        mov g_bRegistered,TRUE
    .endif

    mov eax,lprcRect
    mov edx,[eax].RECT.left
    mov ecx,[eax].RECT.right
    sub ecx,edx
    mov dwCX,ecx
    mov dwXPos,edx
    mov edx,[eax].RECT.top
    mov ecx,[eax].RECT.bottom
    sub ecx,edx
    mov dwCY,ecx
    mov dwYPos,edx

;------------------ better do not rely on infos from
;------------------ IOleObject:SetExtend. So save dimensions here
    mov eax,dwCX        
    mov m_pixelExtent.cx_,eax
    mov eax,dwCY
    mov m_pixelExtent.cy,eax

    .if (m_wClientEdge)
        mov ecx,WS_EX_CLIENTEDGE
    .else
        mov ecx,0
    .endif
    invoke CreateWindowEx, ecx, offset szWndClassName,\
        CStr(""), WS_CHILD or WS_VISIBLE, dwXPos,\
        dwYPos, dwCX, dwCY, hwndParent, 0, g_hInstance, __this
    
    mov m_hWnd,eax

    return S_OK

OnInPlaceActivate@CAsmClass ENDP


;--- another exit from inside IOleObject:DoVerb
;--- called if the verb is NOT a standard verb (that is, it's > 0)


OnDoVerb@CAsmClass proc uses __this this_:ptr CAsmClass, iVerb:SDWORD, lpmsg:ptr MSG,
         pOleClientSite:LPOLECLIENTSITE, hwndParent:HWND, lprcPosRect:ptr RECT

    mov __this, this_

    .if (iVerb == 1)
        invoke AboutBox, __this
        mov eax, S_OK
    .else
        mov eax, OLEOBJ_S_INVALIDVERB
    .endif
    ret

OnDoVerb@CAsmClass endp


;--------------------------------------------------------------------------
;--- wnd proc if we are activated in place
;--------------------------------------------------------------------------

wndproc PROC uses __this hWnd:HWND, uMessage:DWORD, wParam:WPARAM, lParam:LPARAM

local   ps:PAINTSTRUCT
local   rect:RECT

    invoke GetWindowLong,hWnd,0
    mov __this,eax

    mov eax,uMessage
    .IF (eax == WM_CREATE)

        DebugOut "WM_CREATE received"
        mov eax,lParam
        invoke SetWindowLong,hWnd,0,(CREATESTRUCT ptr [eax]).lpCreateParams

    .ELSEIF (eax == WM_PAINT)

        invoke BeginPaint,hWnd,addr ps

;------------------------------------ just call the draw routine

        mov rect.left,0
        mov rect.top,0
        mov eax,m_pixelExtent.cx_
        mov rect.right,eax
        mov eax,m_pixelExtent.cy
        mov rect.bottom,eax
        lea ecx, m__IViewObject2
        invoke OnDraw@CAsmClass, __this, 0, 0, ps.hdc, addr rect, 0

        invoke EndPaint,hWnd,addr ps
        xor eax, eax

    .ELSEIF (eax == WM_ERASEBKGND)

        mov eax,1

    .ELSEIF (eax == WM_LBUTTONDOWN)

        invoke OnUIActivate@CAsmClass, m_pOleClientSite

;------------------------------------ fire an event.
;------------------------------------ m_pConnectionPoint is a variable array
;------------------------------------ defined from connection point map (has one
;------------------------------------ entry only)

        mov eax,lParam
        movzx edx,ax    ;edx = x
        shr eax,16      ;eax = y
        invoke FIREEVENT(m_pConnectionPoint[0], _AsmClassEvent, OnClick), edx, eax

    .ELSEIF (eax == WM_DESTROY)

        DebugOut "WM_DESTROY received"
        xor eax,eax

    .ELSE
        invoke DefWindowProc, hWnd, uMessage, wParam, lParam 
    .ENDIF
    ret
wndproc ENDP

;-------------------------------------------------
;--- ISpecifyPropertyPages support
;--- supply CLSIDs of property pages to show
;-------------------------------------------------

GetPages@CAsmClass proc uses esi edi this_:ptr CAsmClass, pPages:ptr CAUUID
    
    mov edx, pPages
    mov [edx].CAUUID.cElems, 2
    invoke CoTaskMemAlloc, (SIZEOF GUID) * 2
    mov edx, pPages
    mov [edx].CAUUID.pElems, eax
    mov edi, eax

    mov esi, offset CLSID_StockFontPage
    movsd
    movsd
    movsd
    movsd
    mov esi, offset CLSID_StockColorPage
    movsd
    movsd
    movsd
    movsd
    return S_OK

GetPages@CAsmClass endp


;-------------------------------------------------------------
;--- IAsmClass/IDispatch interface: define all specific methods
;-------------------------------------------------------------

;--- OnInvoke is a exit proc for IDispatch::Invoke. Set EAX to -1
;--- if this proc has NOT handled the event and you want further processing

OnInvoke@CAsmClass proc uses __this this_:ptr CAsmClass, dispID:DWORD,
        wFlags:DWORD, pDispParams:ptr DISPPARAM, pVarResult:ptr VARIANT, 
        pExcepInfo:ptr EXCEPINFO, puArgErr:ptr DWORD

    mov __this, this_

    .if (dispID == DISPID_ENABLED)
        mov edx, pVarResult
        mov [edx].VARIANT.vt, VT_BOOL
        .if (m_hWnd)
            mov [edx].VARIANT.boolVal, TRUE
        .else
            mov [edx].VARIANT.boolVal, FALSE
        .endif
        mov eax, S_OK
        jmp done
    .endif
    mov eax, -1
done:
    ret
OnInvoke@CAsmClass endp

put_Value proc uses __this this_:ptr CAsmClass, newVal:DWORD

    mov __this,this_
    DebugOut "put_Value"

    mov eax,newVal
    mov m_dwValue,eax
    mov m_isDirty, TRUE
    return S_OK

put_Value endp

;--------------------------------------------------------------------------

get_Value proc uses __this this_:ptr CAsmClass, pVal:ptr DWORD

    mov __this,this_
    DebugOut "get_Value"

    mov eax,pVal
    .if (eax)
        mov edx,m_dwValue
        mov [eax],edx
        mov eax,S_OK
    .else
        mov eax, E_POINTER
    .endif
    ret
get_Value endp

;--------------------------------------------------------------------------
;--- Raise demonstrates some parameter technics
;--- user defined type (enum) in dwOptions
;--- pointer to variant in pvtText
;--- [out] parameter
;--- optional parameters in vtText2 and iOptValue
;--- default value in vtText2
;--------------------------------------------------------------------------

Raise proc uses __this this_:ptr CAsmClass, dwOptions:DWORD, pvtText:ptr VARIANT, pbstrOut:ptr DWORD, vtText2:VARIANT, iOptValue:DWORD, pUnknown:LPUNKNOWN

local   vtTemp:VARIANT
local   szText2[64]:byte

    mov __this, this_
    invoke VariantInit, addr vtTemp
    invoke VariantChangeType, addr vtTemp, addr vtText2, 0, VT_BSTR
    .if (eax == S_OK)
        invoke WideCharToMultiByte, CP_ACP, 0, vtTemp.bstrVal,-1, addr m_szText, 64,0,0 
        invoke VariantClear, addr vtTemp
    .endif
    invoke VariantInit, addr vtTemp
    invoke VariantChangeType, addr vtTemp, pvtText, 0, VT_BSTR
    mov szText2, 0
    .if (eax == S_OK)
        invoke WideCharToMultiByte, CP_ACP, 0, vtTemp.bstrVal,-1, addr szText2, 64,0,0 
        invoke VariantClear, addr vtTemp
    .endif
    mov ecx, pvtText
    movzx ecx, [ecx].VARIANT.vt
    movzx edx, vtText2.vt
    DebugOut "Raise(dwOptions=%X, vtText1=%u,%s, pbstrOut=%X, vtText2(opt)=%u,%s, iOptValue(opt)=%X, pUnknown(opt)=%X)", dwOptions, ecx, addr szText2, pbstrOut, edx, addr m_szText, iOptValue, pUnknown
    mov eax,dwOptions
    add m_dwValue,eax
    mov ecx, pbstrOut
    .if (ecx)
        push ecx
        invoke SysAllocString, CStrW(L("Test"))
        pop ecx
        mov [ecx], eax
    .endif
    mov m_isDirty, TRUE
    return S_OK

Raise endp

;--------------------------------------------------------------------------

put_ForeColor proc uses __this this_:ptr CAsmClass, NewColor:DWORD

    mov __this, this_
    DebugOut "put_ForeColor"

    mov eax,NewColor
    mov m_dwForeColor,eax
    mov m_isDirty, TRUE
    invoke SendViewChange@CAsmClass, __this
    return S_OK

put_ForeColor  endp

;--------------------------------------------------------------------------

get_ForeColor proc uses __this this_:ptr CAsmClass, pColor:DWORD

    mov __this,this_
    DebugOut "get_ForeColor"

    mov eax,pColor
    .if (eax)
        mov edx, m_dwForeColor
        mov [eax],edx
        mov eax,S_OK
    .else
        mov eax, E_POINTER
    .endif
    ret
get_ForeColor  endp

;--------------------------------------------------------------------------

put_BackColor proc uses __this this_:ptr CAsmClass, NewColor:DWORD

    mov __this,this_
    DebugOut "put_BackColor"

    mov eax,NewColor
    mov m_dwBackColor,eax
    mov m_isDirty, TRUE
    invoke SendViewChange@CAsmClass, __this
    return S_OK

put_BackColor  endp

;--------------------------------------------------------------------------

get_BackColor proc uses __this this_:ptr CAsmClass, pColor:DWORD

    mov __this,this_
    DebugOut "get_BackColor"

    mov eax,pColor
    .if (eax)
        mov edx,m_dwBackColor
        mov [eax],edx
        mov eax,S_OK
    .else
        mov eax, E_POINTER
    .endif
    ret

get_BackColor  endp

;--------------------------------------------------------------------------
;--- the ClientEdge property should change the window style
;--- but to get into effect the window will have to be closed + reopened
;--------------------------------------------------------------------------

put_ClientEdge proc uses __this this_:ptr CAsmClass, fEdge:DWORD

    mov __this,this_
    DebugOut "put_ClientEdge"

    xor ecx,ecx
    movzx eax,word ptr fEdge
    test eax,eax
    setne cl
    mov m_wClientEdge,cx
    mov m_isDirty, TRUE

    .if (m_hWnd)
        movzx eax,m_wClientEdge
        .if (eax)
            mov ecx,WS_EX_CLIENTEDGE
        .else
            mov ecx,0
        .endif
        invoke SetWindowLong,m_hWnd,GWL_EXSTYLE,ecx
    .endif
    invoke SendViewChange@CAsmClass, __this
    return S_OK

put_ClientEdge  endp

;--------------------------------------------------------------------------

get_ClientEdge proc uses __this this_:ptr CAsmClass, pClientEdge:ptr word

    mov __this,this_
    DebugOut "get_ClientEdge"

    mov eax,pClientEdge
    .if (eax)
        mov dx, m_wClientEdge
        mov [eax],dx
        mov eax,S_OK
    .else
        mov eax, E_POINTER
    .endif
    ret
get_ClientEdge  endp

;--------------------------------------------------------------------------

AboutBox  proc uses __this this_:ptr CAsmClass

LOCAL hWnd:HWND
LOCAL pOleWindow:LPOLEWINDOW
local pOleInPlaceSite:LPOLEINPLACESITE

    mov __this, this_
    DebugOut "AboutBox"

;-------------------------------- get a parent hWnd from the container
    mov hWnd, NULL
    .if (m_pOleClientSite)
        invoke vf(m_pOleClientSite, IUnknown, QueryInterface), addr IID_IOleInPlaceSite, ADDR pOleInPlaceSite
        .if SUCCEEDED(eax)
            invoke vf(pOleInPlaceSite, IOleWindow, GetWindow_), ADDR hWnd
            invoke vf(pOleInPlaceSite, IOleWindow, Release)
        .endif                    
    .endif
    invoke DialogBoxParam, g_hInstance, IDD_ABOUT, hWnd, ADDR aboutBoxProc, NULL

    return S_OK

AboutBox endp

;--------------------------------------------------------------------------

aboutBoxProc proc hWnd:HWND, uMessage:DWORD, wParam:WPARAM, lParam:LPARAM

    mov eax,uMessage
    .IF (eax == WM_COMMAND)
        movzx eax,word ptr wParam
        .IF (eax == IDOK)
            invoke EndDialog, hWnd, 0
        .ENDIF
        xor eax,eax
    .ELSEIF (eax == WM_INITDIALOG)
        mov eax,1
    .ELSE
        xor eax,eax
    .ENDIF
    ret
aboutBoxProc endp

;--------------------------------------------------------------------------

put_Value2 proc uses __this this_:ptr CAsmClass, newVal:DWORD

    mov __this,this_
    DebugOut "put_Value2"

    mov eax,newVal
    mov m_dwValue2,eax
    mov m_isDirty, TRUE
    return S_OK

put_Value2 endp

;--------------------------------------------------------------------------

get_Value2 proc uses __this this_:ptr CAsmClass, pVal:ptr DWORD

    mov __this,this_
    DebugOut "get_Value2"

    mov eax,pVal
    .if (eax)
        mov edx,m_dwValue2
        mov [eax],edx
        mov eax,S_OK
    .else
        mov eax, E_POINTER
    .endif
    ret
get_Value2 endp

    end


CASMProp.ASM

;*** methods of CAsmProp coclass

    .586
    .model flat, stdcall
    option casemap:none ; case sensitive
    option proc:private

    .nolist
    .nocref
WIN32_LEAN_AND_MEAN equ 1
INC_OLE2            equ 1
    include windows.inc
    include olectl.inc

    include macros.inc
    include disphlp.inc
    include olecntrl.inc
    include debugout.inc

    include AsmCtrl.inc     ;type information (COMView generated)
    .list
    .cref

    include CAsmClass.inc
    include CAsmProp.inc

IDD_PROPPAGE    equ 1001
IDC_EDIT1       equ 40000

Destroy@CAsmProp proto :ptr CAsmProp

    .const

CLSID_CAsmProp      sCLSID_AsmProp

;--------------------------------------------------------------------------
;--- IUnknown interface: the table of supp. interfaces must be defined
;--------------------------------------------------------------------------

    .const

    DEFINE_KNOWN_INTERFACES CAsmProp, IPropertyPage

;--------------------------------------------------------------------------
;--- define table of registry entries for this CLSID
;--- REGSTRUCT  = {LPSTR lpszSubKey; LPSTR lpszValueName; LPSTR lpszData}
;--- -1 in lpszSubKey -> key in HKEY_CLASSES_ROOT, in which next
;--- entries will be written. %s is szCLSID in such entries.
;--- else: -1 in lpszData: replaced by szCLSID, -2 in lpszData: replaced by szTLBID
;--- %s is module path in such entries
;--------------------------------------------------------------------------

Description     textequ <"OCX control in ASM Property Page">

RegKeys_CAsmProp label REGSTRUCT
    REGSTRUCT <-1, 0, CStr("CLSID\%s")>
    REGSTRUCT <0, 0, CStr(Description)>
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT 
    REGSTRUCT <-1, 0, 0>

;-----------------------------------------------------------------
    .const
    
CAsmPropVtbl    label IPropertyPageVtbl
    IUnknownVtbl {QueryInterface@CAsmProp, AddRef@CAsmProp, Release@CAsmProp}
    dd SetPageSite
    dd Activate
    dd Deactivate
    dd GetPageInfo
    dd SetObjects
    dd Show
    dd Move
    dd IsPageDirty
    dd Apply
    dd Help
    dd TranslateAccelerator_

    .code

__this  textequ 
_this   textequ <[__this].CAsmProp>

    MEMBER pPropertyPageSite, hWnd, pObject, rect, bModal, bIsDirty, bInitUI

    DEFINE_STD_COM_METHODS CAsmProp

;--------------------------------------------------------------------------
;--------------- constructor CAsmProp
;--------------------------------------------------------------------------

Create@CAsmProp proc public uses __this pClass: ptr ObjectEntry, pUnkOuter:LPUNKNOWN
    
    DebugOut "Create@CAsmProp"

    invoke LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT,sizeof CAsmProp
    .if (eax == NULL)
        ret
    .endif
    mov __this,eax
    mov m__IPropertyPage, OFFSET CAsmPropVtbl
    STD_COM_CONSTRUCTOR CAsmProp

if ?AGGREGATION
    lea eax, m__IUnknown
    ret
else
    return __this
endif

Create@CAsmProp endp

;--------------------------------------------------------------------------
;--------------- destructor
;--------------------------------------------------------------------------

Destroy@CAsmProp PROC public uses __this this_:ptr CAsmProp

    DebugOut "Destroy@CAsmProp enter"
    mov __this,this_
    STD_COM_DESTRUCTOR CAsmProp
    .if (m_pPropertyPageSite)
        invoke vf(m_pPropertyPageSite, IUnknown, Release)
    .endif
    .if (m_pObject)
        invoke vf(m_pObject, IUnknown, Release)
    .endif
    invoke LocalFree, __this
    ret

Destroy@CAsmProp ENDP

;--- the window proc of the property sheet 

dlgproc proc uses __this hWnd:HWND, uMessage:DWORD, wParam:WPARAM, lParam:LPARAM

local   dwValue:DWORD

    mov eax,uMessage
    .IF (eax == WM_COMMAND)
        DebugOut "CAsmProp wndproc WM_COMMAND"
        movzx eax, word ptr wParam
        .if (eax == IDC_EDIT1)
            movzx eax, word ptr wParam+2
            .if (eax == EN_CHANGE)
                invoke GetWindowLong, hWnd, DWL_USER
                mov __this, eax
                .if (!m_bInitUI)
                    mov m_bIsDirty, TRUE
                    invoke vf(m_pPropertyPageSite, IPropertyPageSite, OnStatusChange),\
                        PROPPAGESTATUS_DIRTY
                .endif
            .endif
        .endif
        xor eax,eax
    .ELSEIF (eax == WM_INITDIALOG)
        DebugOut "CAsmProp wndproc WM_INITDIALOG"
        invoke SetWindowLong, hWnd, DWL_USER, lParam
        mov __this, lParam
        .if (m_pObject)
            mov m_bInitUI, TRUE
            invoke vf(m_pObject, IAsmClass, get_Value), addr dwValue 
            .if (eax == S_OK)
                invoke SetDlgItemInt, hWnd, IDC_EDIT1, dwValue, FALSE
            .endif
            mov m_bInitUI, FALSE
        .endif
        mov eax,1
    .ELSEIF (eax == WM_CLOSE)
        DebugOut "CAsmProp wndproc WM_CLOSE"
        invoke DestroyWindow, hWnd
    .ELSE
        xor eax,eax
    .ENDIF
    ret
dlgproc endp


SetPageSite proc uses __this this_:ptr CAsmProp, pSite:ptr IPropertyPageSite

    DebugOut "SetPageSite@CAsmProp"
    mov __this, this_
    invoke ComPtrAssign, addr m_pPropertyPageSite, pSite
    mov eax, S_OK
    ret
SetPageSite endp

Activate    proc uses __this this_:ptr CAsmProp, hWnd:HWND, pRect:LPRECT, bModal:BOOL

    DebugOut "Activate@CAsmProp"
    mov __this, this_
    invoke CopyRect, addr m_rect, pRect
    invoke CreateDialogParam, g_hInstance, IDD_PROPPAGE, hWnd, offset dlgproc, __this
    .if (eax)
        mov m_hWnd, eax
        mov eax, S_OK
    .else
        mov eax, E_OUTOFMEMORY
    .endif
    ret
Activate endp

Deactivate  proc uses __this this_:ptr CAsmProp
    DebugOut "Deactivate@CAsmProp"
    mov __this, this_
    .if (m_hWnd)
        invoke DestroyWindow, m_hWnd
        mov m_hWnd, NULL
    .endif
    mov eax, S_OK
    ret
Deactivate endp

GetPageInfo proc uses __this esi this_:ptr CAsmProp, pPPInfo:ptr PROPPAGEINFO
    DebugOut "GetPageInfo@CAsmProp"
    mov __this, this_
    mov esi, pPPInfo
    mov [esi].PROPPAGEINFO.cb, sizeof PROPPAGEINFO
    invoke CoTaskMemAlloc, 32*2
    mov [esi].PROPPAGEINFO.pszTitle, eax
    .if (eax)
        invoke lstrcpyW, eax, CStrW(L("AsmCtrl PropPage"))
    .endif    
    mov [esi].PROPPAGEINFO.size_.cx_, 240
    mov [esi].PROPPAGEINFO.size_.cy, 180
    invoke CoTaskMemAlloc, 32*2
    mov [esi].PROPPAGEINFO.pszDocString, eax
    .if (eax)
        invoke lstrcpyW, eax, CStrW(L("AsmCtrl DocString"))
    .endif    
if 0    
    invoke CoTaskMemAlloc, 32*2
    mov [esi].PROPPAGEINFO.pszHelpFile, eax
    .if (eax)
        invoke lstrcpyW, eax, CStrW(L("AsmCtrl HelpFile"))
    .endif    
else
    mov [esi].PROPPAGEINFO.pszHelpFile, NULL
endif
    mov [esi].PROPPAGEINFO.dwHelpContext, 0
    mov eax, S_OK
    ret
GetPageInfo endp

;--- currently only 1 object supported

SetObjects proc uses __this this_:ptr CAsmProp, cObjects:DWORD, ppUnk:ptr LPUNKNOWN

    DebugOut "SetObjects@CAsmProp"
    mov __this, this_
    mov ecx, cObjects
    .if (ecx)
        mov edx, ppUnk
        mov eax, [edx]
        .if (eax)
            lea ecx, m_pObject
            invoke vf(eax, IUnknown, QueryInterface), offset IID_IAsmClass, ecx
        .endif
    .else
        .if (m_pObject)
            invoke vf(m_pObject, IUnknown, Release)
            mov m_pObject, NULL
        .endif
    .endif
    mov eax, S_OK
    ret
SetObjects endp

Show proc uses __this this_:ptr CAsmProp, nCmdShow:DWORD
    DebugOut "Show@CAsmProp"
    mov __this, this_
    .if (nCmdShow)
        invoke ShowWindow, m_hWnd, SW_SHOW
    .else
        invoke ShowWindow, m_hWnd, SW_HIDE
    .endif
    mov eax, S_OK
    ret
Show endp

Move proc uses __this this_:ptr CAsmProp, pRect:LPRECT
    DebugOut "Move@CAsmProp"
    mov __this, this_
    invoke CopyRect, addr m_rect, pRect
    mov eax, S_OK
    ret
Move endp

IsPageDirty proc uses __this this_:ptr CAsmProp
    DebugOut "IsPageDirty@CAsmProp"
    mov __this, this_
    .if (m_bIsDirty)
        mov eax, S_OK
    .else
        mov eax, S_FALSE
    .endif
    ret
IsPageDirty endp

Apply proc uses __this this_:ptr CAsmProp
    DebugOut "Apply@CAsmProp"
    mov __this, this_
    .if (m_hWnd)
        invoke GetDlgItemInt, m_hWnd, IDC_EDIT1, NULL, FALSE
        invoke vf(m_pObject, IAsmClass, put_Value), eax
    .endif
    mov m_bIsDirty, FALSE
    mov eax, S_OK
    ret
Apply endp

Help proc uses __this this_:ptr CAsmProp, pHelp:LPOLESTR
    DebugOut "Help@CAsmProp"
    mov eax, E_NOTIMPL
    ret
Help endp

TranslateAccelerator_ proc uses __this this_:ptr CAsmProp, pMsg:ptr MSG
    DebugOut "TranslateAccelerator@CAsmProp"
    mov eax, E_NOTIMPL
    ret
TranslateAccelerator_ endp

    end
 

RSRC.RC

#include "resource.h"
#include "windows.h"

IDD_TypeLib             TYPELIB MOVEABLE PURE   "AsmCtrl.tlb"

IDD_CONTROL_BITMAP      BITMAP  MOVEABLE PURE   "control.bmp"

//
// Dialog
//

IDD_ABOUT DIALOG DISCARDABLE  10, 10, 141, 85
STYLE DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_CAPTION
CAPTION "About ASM Control"
FONT 8, "MS Sans Serif"
BEGIN
    PUSHBUTTON      "OK",IDOK,49,62,43,16
    CTEXT           "ASM Control\n\nAn OCX in pure ASM",-1,11,12,115,30
END

IDD_PROPPAGE DIALOG DISCARDABLE  10, 10, 141, 85
STYLE DS_CONTROL | WS_CHILD
CAPTION "AsmCtrl Property Page"
FONT 8, "MS Sans Serif"
BEGIN
    CTEXT "Value",-1,10,13,32,12
    EDITTEXT IDC_EDIT1, 42,12,60,12
END

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1               ICON    DISCARDABLE     "AsmCtrl.ico"