;--------------------------------------------------------------------------
; MyCom.dll     ActiveX simple server to demonstrate basic concepts
;               written & © copyright April 14, 2000 by Ernest Murphy
;
;               updated July 12 for new interface definitions
;               contact the author at ernie@surfree.com
;
;               may be reused for any educational or
;               non-commercial application without further licence
;--------------------------------------------------------------------------
.386
.model flat, stdcall
option casemap:none ; case sensitive
.NOLIST  ; keep the list file small, don't add the std libs to it.
;--------------------------------------------------------------------------

; please make sure your paths are the same has mine.

include     \masm32\include\windows.inc
include     \masm32\include\user32.inc
include     \masm32\include\kernel32.inc
include     \masm32\include\advapi32.inc
include     \masm32\include\oleaut32.inc
include     \masm32\include\ole32.inc

include     \masm32\COM\include\oaidl.inc


includelib  \masm32\lib\user32.lib
includelib  \masm32\lib\kernel32.lib
includelib  \masm32\lib\advapi32.lib
includelib  \masm32\lib\oleaut32.lib
includelib  \masm32\lib\ole32.lib

;--------------------------------------------------------------------------
.LISTALL ; NOW let's list everything

; build with these settings: \masm32\bin\ml /c /Fl /Sn /coff %1.asm
;  /Fl /Sn will create a small listing without all the include files
;  (note placement of .NOLIST and .LISTALL bracketing the include area)

; IMyCom Interface
;------------------------------------------------------------------------------------
; public IUnknown

sIID_IMyCom       TEXTEQU   <{0A21A8C41H, 01266H, 011D4H,     \ 
                             {0A3H, 024H, 000H, 040H, 0F6H, 0D4H, 087H, 0D9H}}>
sLIBID_MyComLib   TEXTEQU   <{0A21A8C43H, 01266H, 011D4H,     \ 
                             {0A3H, 024H, 000H, 040H, 0F6H, 0D4H, 087H, 0D9H}}>
sCLSID_MyCom      TEXTEQU   <{0A21A8C42H, 01266H, 011D4H,     \ 
                             {0A3H, 024H, 000H, 040H, 0F6H, 0D4H, 087H, 0D9H}}>

_vtIMyCom MACRO CastName:REQ
    ; IUnknown methods 
    _vtIUnknown CastName
    ; IMyCom methods
    &CastName&_GetValue     comethod2   ?
    &CastName&_SetValue     comethod2   ?
    &CastName&_RaiseValue   comethod2   ?
ENDM

IMyCom                   STRUCT
    _vtIMyCom IMyCom
IMyCom                   ENDS

;--------------------------------------------------------------------------
; .dll exports
DllRegisterServer       PROTO 
DllCanUnloadNow         PROTO
DllGetClassObject       PROTO :DWORD, :DWORD, :DWORD
DllUnregisterServer     PROTO 

; our interface implimentations
QueryInterface_CF       PROTO :DWORD, :DWORD, :DWORD
AddRef_CF               PROTO :DWORD
Release_CF              PROTO :DWORD
QueryInterface_MC       PROTO :DWORD, :DWORD, :DWORD
AddRef_MC               PROTO :DWORD
Release_MC              PROTO :DWORD

CreateInstance          PROTO :DWORD, :DWORD, :DWORD, :DWORD
GetValue                PROTO :DWORD, :DWORD
SetValue                PROTO :DWORD, :DWORD
RaiseValue              PROTO :DWORD, :DWORD

;internal helper functions
CreateMyComObject       PROTO 
GuardedDeleteKey        PROTO :DWORD, :DWORD

;------------------------------------------------------------------------
; declare the ClassFactory object structure
ClassFactoryObject  STRUCT   ; this_
    ; interface object
    lpVtbl          DWORD       0
    ; object data
    nRefCount       DWORD       1  
ClassFactoryObject  ENDS

; declare the MyCom object structure
MyComObject         STRUCT   ; this_
    ; interface object
    lpVtbl          DWORD       0
    ; object data
    nRefCount       DWORD       1  
    nValue          DWORD       0  
MyComObject         ENDS

;-----------------------------------------------------------------------------
.data

; here are the actual vtables for the interfaces we need to support.
vtIClassFactory   IClassFactory     <   QueryInterface_CF,      \
                                        AddRef_CF,              \
                                        Release_CF,             \
                                        CreateInstance,         \
                                        LockServer       >

vtIMyCom          IMyCom            <   QueryInterface_MC,      \
                                        AddRef_MC,              \
                                        Release_MC,             \
                                        GetValue,               \
                                        SetValue,               \
                                        RaiseValue       >

; we only need a single class factory object, so let's just allow
;  for it in our .data area and not bother instancing it every time.

MyCFObject        ClassFactoryObject<   OFFSET vtIClassFactory, \
                                        0                >


; define the interface's IID

IID_IMyCom          GUID    sIID_IMyCom
CLSID_MyCom         GUID    sCLSID_MyCom
TYPELIB_MyCom       GUID    sLIBID_MyComLib
IID_IUnknown        GUID    sIID_IUnknown
IID_IClassFactory   GUID    sIID_IClassFactory

g_hDllMain          DWORD   0       ; hInstance for the dll (process)

; strings needed to register the component
szSampleDesc        BYTE    "CMyCom simple client thing", 0
szInprocServer32    BYTE    "InprocServer32", 0
szSampleProgID      BYTE    "CMyCom", 0
szProgID            BYTE    "CMyCom", 0
szThreadModel       BYTE    "ThreadingModel", 0
szThreadType        BYTE    "Both",0
szCLSID             BYTE    "CLSID"              ,0

;--------------------------------------------------------------------------
.code

;--------------------------------------------------------------------------
DllMain PROC hModule:HANDLE, dwReason:DWORD, lpReserved:DWORD
    .IF dwReason == DLL_PROCESS_ATTACH
        mov eax, hModule
        mov g_hDllMain, eax
        mov eax, TRUE
    .ELSEIF
        mov eax, FALSE
    .ENDIF
    ret
DllMain Endp

;--------------------------------------------------------------------------
DllCanUnloadNow PROC
    .IF (MyCFObject.nRefCount == 0)
        mov eax, TRUE
    .ELSEIF
        mov eax, FALSE
    .ENDIF
    ret
DllCanUnloadNow Endp

;--------------------------------------------------------------------------
DllGetClassObject PROC pCLSID:DWORD, pIID:DWORD, ppv:DWORD
    LOCAL   hr        :DWORD
    LOCAL   pFactory  :DWORD
    ; compare the clsid given us to the one we can build
    ; we can ONLY build a Class Factory object to build a IID_IMyCom object
    ; all others get rejected
    invoke  IsEqualGUID, pCLSID, addr CLSID_MyCom
    .IF (eax == TRUE)
        ; get a pointer to our static object
        ; (static = no constructor or alloc methods needed)
        ; (member values are set at compile time)
        mov eax, OFFSET MyCFObject
        ; use it to get the requested pointer
        invoke QueryInterface_CF, eax, pIID, ppv
        ; use the QI hresult to determine the sucess of this procedure
        mov hr, eax
        ; and release our helper pointer
        invoke Release_CF, OFFSET MyCFObject
    .ELSE
        mov hr, CLASS_E_CLASSNOTAVAILABLE
    .ENDIF  
    mov eax, hr         ; pass back the hresult
    ret
DllGetClassObject Endp

;--------------------------------------------------------------------------
DllRegisterServer PROC
    LOCAL   hKey    :DWORD
    LOCAL   hKey2   :DWORD
    LOCAL   hKey3   :DWORD
    LOCAL   sBuf    [MAX_PATH]:BYTE
    LOCAL   wsBuf   [MAX_PATH]:WORD
    LOCAL   psBuf   :DWORD
    LOCAL   pwsBuf  :DWORD
    LOCAL   pti     :DWORD
    
    ; assign the pointers to the local text buffers
    lea eax, sBuf
    mov psBuf, eax
    lea eax, wsBuf
    mov pwsBuf, eax
    ; Create HKEY_CLASSES_ROOT\progid\CLSID
    invoke RegCreateKey, HKEY_CLASSES_ROOT, ADDR szSampleProgID, ADDR hKey
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    invoke lstrlen, ADDR szSampleDesc    
    invoke RegSetValue,hKey, NULL, REG_SZ, ADDR szSampleDesc, eax
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    ; Create HKEY_CLASSES_ROOT\%szProgID%\CLSID 
    invoke RegCreateKey, hKey, ADDR szCLSID, ADDR hKey2
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    ; convert the CLSID_MyCom number to a string    
    invoke StringFromGUID2, ADDR CLSID_MyCom, pwsBuf, MAX_PATH
    invoke WideCharToMultiByte, CP_ACP, 0, pwsBuf, -1, 
                                psBuf, MAX_PATH, NULL, NULL
    invoke lstrlen, psBuf
    invoke RegSetValue, hKey2, NULL, REG_SZ, psBuf, eax
    .IF (eax != ERROR_SUCCESS)
        jmp return
    .ENDIF

    invoke RegCloseKey, hKey
    invoke RegCloseKey, hKey2
    ; Create HKEY_CLASSES_ROOT\CLSID
    invoke RegCreateKey, HKEY_CLASSES_ROOT, ADDR szCLSID, ADDR hKey
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    ; Create HKEY_CLASSES_ROOT\CLSID\%GUID%
    invoke RegCreateKey, hKey, psBuf, ADDR hKey2
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    ; put in sample description value into CLSID\GUID key
    invoke lstrlen, ADDR szSampleDesc    
    invoke RegSetValue, hKey2, NULL, REG_SZ, ADDR szSampleDesc, eax
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    ; get our path ..
    invoke GetModuleFileName,g_hDllMain, psBuf, MAX_PATH
    .IF (eax == 0) 
        mov eax, 1
        jmp return
    .ENDIF
    ; Create HKEY_CLASSES_ROOT\CLSID\%GUID%\InprocServer32
    invoke RegCreateKey, hKey2, ADDR szInprocServer32, ADDR hKey3
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    ; set key value to the path obtained above
    invoke RegSetValue, hKey3, NULL, REG_SZ, psBuf, MAX_PATH
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    ; set the threading type value
    invoke lstrlen, ADDR szThreadType
    invoke RegSetValueEx, hKey3, ADDR szThreadModel, 0, REG_SZ, ADDR szThreadType, eax
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    invoke RegCloseKey, hKey3
    ; Create HKEY_CLASSES_ROOT\CLSID\%GUID%\%szProgID% 
    invoke RegCreateKey, hKey2, ADDR szProgID, ADDR hKey3
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    invoke lstrlen, ADDR szSampleProgID
    invoke RegSetValue, hKey3, NULL, REG_SZ, ADDR szSampleProgID, eax
    .IF (eax != ERROR_SUCCESS) 
        jmp return
    .ENDIF
    invoke RegCloseKey, hKey3
    .IF (hKey != 0)
        invoke RegCloseKey, hKey
    .ENDIF    
    .IF (hKey2 != 0)
        invoke RegCloseKey, hKey2
    .ENDIF    
    .IF (hKey3 != 0)
        invoke RegCloseKey, hKey3
    .ENDIF    
    ; sBuf still has our dll pathname, lets use it to
    ; get the class type library
    Invoke MultiByteToWideChar, CP_ACP, 0, psBuf, -1, pwsBuf, MAX_PATH
    .IF !eax
        mov eax, S_FALSE
        jmp return
    .ENDIF
    lea eax, pti
    Invoke LoadTypeLib, pwsBuf, eax
    .IF (eax != ERROR_SUCCESS)
        mov eax, S_FALSE
        jmp return
    .ENDIF
    ; and register the type lib
    Invoke RegisterTypeLib, pti, pwsBuf, NULL
    .IF (eax != ERROR_SUCCESS)
        mov eax, S_FALSE
        jmp return
    .ELSE
        ; release the type info pointer we got
        ; NOTE: the 3 following lines could be substuited with
        ;       the coinvoke macro like so:
        ;  coinvoke pti, IUnknown, Release
        mov eax, pti
        mov eax, [eax]
        invoke (IUnknown PTR [eax]).IUnknown_Release, pti
    .ENDIF
    xor eax, eax    ;    mov eax, S_OK
return:
    ret
DllRegisterServer ENDP

; -------------------------------------------------------------------------
DllUnregisterServer PROC
    LOCAL   hSubkey:DWORD
    LOCAL   hSubkey2:DWORD
    LOCAL   sBuf    [MAX_PATH]:BYTE
    LOCAL   psBuf   :DWORD
    LOCAL   wsBuf   [MAX_PATH]:WORD
    LOCAL   pwsBuf  :DWORD

    lea eax, sBuf
    mov psBuf, eax
    lea eax, wsBuf
    mov pwsBuf, eax
    invoke StringFromGUID2, ADDR CLSID_MyCom, pwsBuf, MAX_PATH
    invoke WideCharToMultiByte, CP_ACP, 0, pwsBuf, -1, 
                                psBuf, MAX_PATH, NULL, NULL
    invoke GuardedDeleteKey, HKEY_CLASSES_ROOT, ADDR szSampleProgID
    .IF (eax != ERROR_SUCCESS)        
        jmp return
    .ENDIF
    invoke RegOpenKey, HKEY_CLASSES_ROOT, ADDR szCLSID, ADDR hSubkey
    invoke GuardedDeleteKey, hSubkey, psBuf
    .IF (eax != ERROR_SUCCESS)        
        jmp return
    .ENDIF
    invoke RegCloseKey, hSubkey
    xor eax, eax    ; mov eax, S_OK
return:    
    ret
DllUnregisterServer ENDP

;--------------------------------------------------------------------------
GuardedDeleteKey PROC hKey:DWORD, lpszSubKey:DWORD
    LOCAL   szSubKeyName    [MAX_PATH+1]:TCHAR
    LOCAL   hSubkey         :DWORD  

    ; check to see if the key to be deleted still has subkey, 
    ; if not delete it, otherwise delete the subkey
    invoke RegOpenKey, hKey, lpszSubKey, ADDR hSubkey
    .IF (eax != ERROR_SUCCESS) 
        mov eax, REGDB_E_INVALIDVALUE
        jmp return
    .ENDIF
KillNextSubkey:    
    ; check for a subkey
    invoke RegEnumKey, hSubkey, 0, ADDR szSubKeyName, MAX_PATH+1
    .IF (eax != ERROR_NO_MORE_ITEMS) ; IF SubKeys exist...
        ; delete the subkey thru recusion
        invoke GuardedDeleteKey, hSubkey, ADDR szSubKeyName
        jmp KillNextSubkey
    .ELSE
    .ENDIF
    ; no more Subkeys, delete the specfied hey.
    invoke RegCloseKey, hSubkey
    invoke RegDeleteKey, hKey,  lpszSubKey
    .IF (eax == ERROR_SUCCESS) 
        xor eax, eax  ; mov eax, S_OK
    .ENDIF
return:
    ret
GuardedDeleteKey ENDP

;--------------------------------------------------------------------------
QueryInterface_CF PROC this_:DWORD, pRIID:DWORD,  ppv:DWORD
; QueryInterface for the ClassFactory Interface
    LOCAL Match:DWORD
    ; check against the two interfaces we support
    invoke  IsEqualGUID, pRIID, addr IID_IUnknown
    mov Match, eax
    invoke  IsEqualGUID, pRIID, addr IID_IClassFactory
    or eax, Match    
    .IF (eax == TRUE)           ; then asking for either IUnknown or IClassFactory
                                ; both of which we are & support
        mov eax, this_          
        mov edx, ppv
        mov [edx], eax          ; so we point to ourselves
        invoke AddRef_CF, eax   ; inc the ref count for the new pointer *we* created     
        xor eax, eax            ; and signal all is well (same as 'ret S_OK')
        jmp return
    .ENDIF      
NoInterface:
    mov [ppv], NULL             ; good practice, always NULL bad pointers 
                                ; in case client doesn't check the SCODE in hResult
    mov eax, E_NOINTERFACE      ; and signal interface is not supported here
return: 
    ret
QueryInterface_CF endp

;-----------------------------------------------------------------------------
AddRef_CF proc this_:DWORD
    inc MyCFObject.nRefCount
    mov eax, MyCFObject.nRefCount
    ret         ; note we return the object count
AddRef_CF endp

;-----------------------------------------------------------------------------
Release_CF proc this_:DWORD
    ; Remember, we don't have a 'real' constuctor for this
    ; object. Neither do we have a real destructor.
    dec MyCFObject.nRefCount  
    mov eax, MyCFObject.nRefCount
    ret         ; note we return the object count
Release_CF endp

;-----------------------------------------------------------------------------
CreateInstance PROC this_:DWORD, pUnknownOuter:DWORD, iid:DWORD, ppv:DWORD
    LOCAL   pMyObject:DWORD
    LOCAL   hr:DWORD
    .IF pUnknownOuter != NULL
        ; we don't support aggregation (it's too aggravating)
        mov eax, CLASS_E_NOAGGREGATION
    .ELSE
        invoke CreateMyComObject
        mov pMyObject, eax
        .IF eax == NULL                
            ; Create failed
            mov eax, E_OUTOFMEMORY
        .ELSE
            ; we have a valid pointer & new object
            ; record we sucessfully made a new object
            inc MyCFObject.nRefCount
            ; see if we can get the requested interface
            invoke QueryInterface_MC, pMyObject, iid, ppv
            mov hr, eax
            invoke Release_MC,  pMyObject
        .ENDIF
    .ENDIF
    mov eax, hr
    ret
CreateInstance ENDP

;-----------------------------------------------------------------------------
LockServer PROC pif:DWORD, bLockServer:DWORD
    ; minor cheat here, we reuse the object count to also
    ; count locks, because BOTH must be zero to unload the dll
    ; (the only way to delete our static ClassFactory object)
    .IF bLockServer == TRUE
        inc MyCFObject.nRefCount
    .ELSE
        dec MyCFObject.nRefCount
    .ENDIF
    mov eax, S_OK
    ret    
LockServer ENDP

;-----------------------------------------------------------------------------
CreateMyComObject PROC
    LOCAL   pNewObject:DWORD
    ; make a new MyCom object
    invoke  CoTaskMemAlloc, sizeof MyComObject
    mov pNewObject, eax
    .IF (eax != NULL)
        ; we got our memory
        ; initialize the new object
        mov edx, pNewObject
        mov (MyComObject PTR [edx]).lpVtbl, OFFSET vtIMyCom
        mov (MyComObject PTR [edx]).nRefCount, 1
        mov (MyComObject PTR [edx]).nValue, 0
    .ENDIF
    mov eax, pNewObject         ; restore our memory pointer
    ret
CreateMyComObject endp

;-----------------------------------------------------------------------------
QueryInterface_MC PROC this_:DWORD, pRIID:DWORD,  ppv:DWORD
; QueryInterface for the IMyCom Interface
    LOCAL Match     :DWORD
    LOCAL ppvt      :DWORD
    ; check against the two interfaces we support
    invoke  IsEqualGUID, pRIID, addr IID_IUnknown
    mov Match, eax
    invoke  IsEqualGUID, pRIID, addr IID_IMyCom
    or eax, Match
    .IF (eax == TRUE)           ; then asking for either IUnknown or IMyCom
                                ; both of which we are & support
        mov eax, this_          
        mov edx, ppv
        mov [edx], eax          ; so we point to ourselves
        mov ppvt, eax
        invoke AddRef_MC, ppvt  ; inc the ref count for the new pointer *we* created     
        mov eax, S_OK           ; and signal all is well
        jmp return
    .ENDIF    
    ; no other interfaces supported
NoInterface:
    xor eax, eax                ; eax = NULL
    mov edx, ppv                ; good practice, always NULL bad pointers 
    mov [edx], eax              ; in case client doesn't check the SCODE in hResult
    mov eax, E_NOINTERFACE      ; and signal interface is not supported here
return: 
    ret
QueryInterface_MC endp

;-----------------------------------------------------------------------------
AddRef_MC proc this_:DWORD
    mov eax, this_
    inc (MyComObject ptr [eax]).nRefCount
    mov eax, (MyComObject ptr [eax]).nRefCount
    ret         ; note we return the object count
AddRef_MC endp

;-----------------------------------------------------------------------------
Release_MC proc this_:DWORD
    mov eax, this_
    dec (MyComObject ptr [eax]).nRefCount
    mov eax, (MyComObject ptr [eax]).nRefCount
    .IF (eax == 0)
        ; the reference count has dropped to zero
        ; no one holds reference to the object
        ; so let's delete it
        invoke  CoTaskMemFree, this_
        dec MyCFObject.nRefCount
        xor eax, eax    ; clear eax (count = 0)
    .ENDIF
    ret         ; note we return the object count
Release_MC endp

;--------------------------------------------------------------------------
GetValue proc this_:DWORD, pval:DWORD
    mov eax, this_
    mov eax, (MyComObject ptr [eax]).nValue
    mov edx, pval
    mov [edx], eax
    xor eax, eax        ; return S_OK
    ret
GetValue endp

;-----------------------------------------------------------------------------
SetValue proc this_:DWORD, val:DWORD
    mov eax, this_
    mov edx, val
    mov (MyComObject ptr [eax]).nValue, edx
    xor eax, eax        ; return S_OK
    ret
SetValue endp

;-----------------------------------------------------------------------------
RaiseValue  PROC this_:DWORD, val:DWORD
    mov eax, this_
    mov edx, val
    add (MyComObject ptr [eax]).nValue, edx
    xor eax, eax        ; return S_OK    
    ret
RaiseValue  ENDP

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

End DllMain
