;################################################################

format PE GUI 4.0 DLL
entry start

;################################################################

include '%fasminc%\win32ax.inc'
include '%fasminc%\api\kernel32.inc'
include '%fasminc%\api\user32.inc'
include '%fasminc%\api\advapi32.inc'

;################################################################

MAX_PATH			equ 260
S_OK				equ 0
CP_ACP				equ 0
CLSCTX_INPROC_SERVER		equ 1
COINIT_APARTMENTTHREADED	equ 2
ERROR_NO_MORE_ITEMS		equ 259
REGDB_E_INVALIDVALUE		equ 0x80040153
CLASS_E_CLASSNOTAVAILABLE	equ 0x80040111
CLASS_E_NOAGGREGATION		equ 0x80040110
E_NOINTERFACE			equ 0x80004002
E_OUTOFMEMORY			equ 0x8007000e

;################################################################

section '.data' data readable writeable
align	4

struct	GUID
	Data1	dd ?
	Data2	dw ?
	Data3	dw ?
	Data4	db 8 dup (?)
ends

interface	IClassFactory,\
		QueryInterface,\
		AddRef,\
		Release,\
		CreateInstance,\
		LockServer

interface	IMsgbox,\
		QueryInterface,\
		AddRef,\
		Release,\
		DoMsgbox

; define the interfaces IID
CLSID_Msgbox		GUID <0x02a325cd>,<0x648f>,<0x11d5>,<0xa6,0x5a,0,0xe0,0x29,0x07,0x4d,0x77>
IID_IMsgbox		GUID <0x02a325cc>,<0x648f>,<0x11d5>,<0xa6,0x5a,0,0xe0,0x29,0x07,0x4d,0x77>
IID_IUnknown		GUID <0x00000000>,<0>,<0>,<0xc0,0,0,0,0,0,0,0x46>
IID_IClassFactory	GUID <0x00000001>,<0>,<0>,<0xc0,0,0,0,0,0,0,0x46>

; hInstance for the process
g_hDllMain		dd ?

szMessageByCOM		db 'Message by DoMsgbox method in Msgbox COM',0
szSampleDesc		db 'Cmytest simple client',0
szInprocServer32	db 'InProcServer32',0
szThreadModel		db 'ThreadingModel',0
szThreadType		db 'Apartment',0
szCLSID			db 'CLSID',0

; declare the IClassFactory object structure (static)
struct			oCF
IClassFactory		dd ?							; pointer to IClassFactory vtable
ICFRefCount		dd ?							; reference count
ends

; declare the IMsgbox object structure
struct			oMB
IMsgbox			dd ?							; pointer to IMsgbox vtable
IMBRefCount		dd ?							; reference count
ends

; declare the IClassFactory vtable
IClassFactoryVT		dd QueryInterface_CF
			dd AddRef_CF
			dd Release_CF
			dd CreateInstance
			dd LockServer

; declare the IMsgbox vtable
IMsgboxVT		dd QueryInterface_MC
			dd AddRef_MC
			dd Release_MC
			dd DoMsgbox


; IClassFactory static object
pICF			oCF IClassFactoryVT,0

;################################################################

section '.code' code readable executable
proc		start hinstDLL,fdwReason,lpvReserved
		.if	[fdwReason] = DLL_PROCESS_ATTACH
			mov	eax,[hinstDLL]
			mov	[g_hDllMain],eax
			mov	eax,TRUE
		.elseif
			mov	eax,FALSE
		.endif
		ret
endp

;================================================================

proc		DllRegisterServer
local		hKey:DWORD,\
		hKey2:DWORD,\
		hKey3:DWORD,\
		sBuf[MAX_PATH]:BYTE,\
		wsBuf[MAX_PATH]:WORD,\
		psBuf:DWORD,\
		pwsBuf:DWORD,\
		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	StringFromGUID2,CLSID_Msgbox,[pwsBuf],MAX_PATH

		invoke	WideCharToMultiByte,CP_ACP,0,[pwsBuf],-1,[psBuf],MAX_PATH,0,0

		lea	eax,[hKey]
		invoke	RegOpenKey,HKEY_CLASSES_ROOT,szCLSID,eax
		.if	eax <> 0
			jmp	return1
		.endif

; create HKEY_CLASSES_ROOT\CLSID\%GUID%
		invoke	RegCreateKey,[hKey],[psBuf],[hKey2]
		.if	eax <> 0
			jmp	return1
		.endif

		invoke	lstrlen,[psBuf]
		invoke	RegSetValue,[hKey2],0,REG_SZ,[psBuf],eax
		.if	eax <> 0
			jmp	return1
		.endif

		invoke	GetModuleFileName,[g_hDllMain],[psBuf],MAX_PATH
		.if eax = 0
			mov	eax,1
			jmp	return1
		.endif

; create HKEY_CLASSES_ROOT\CLSID\%GUID%\InprocServer32
		invoke	RegCreateKey,[hKey2],szInprocServer32,[hKey3]
		.if	eax <> 0
			jmp	return1
		.endif

; set key value to the path obtained above
		invoke	RegSetValue,[hKey3],0,REG_SZ,[psBuf],MAX_PATH
		.if	eax <> 0
			jmp	return1
		.endif

; set the threading type value
		invoke	lstrlen,szThreadType
		invoke	RegSetValueEx,[hKey3],szThreadModel,0,REG_SZ,szThreadType,eax
		.if	eax <> 0
			jmp	return1
		.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
		mov	eax,S_OK
return1:	ret
endp

;================================================================

proc		DllUnregisterServer
local		hSubkey:DWORD,\
		hSubkey2:DWORD,\
		sBuf[MAX_PATH]:BYTE,\
		psBuf:DWORD,\
		wsBuf[MAX_PATH]:WORD,\
		pwsBuf:DWORD

		lea	eax,[sBuf]
		mov	[psBuf],eax
		lea	eax,[wsBuf]
		mov	[pwsBuf],eax

		invoke	StringFromGUID2,CLSID_Msgbox,[pwsBuf],MAX_PATH

		invoke	WideCharToMultiByte,CP_ACP,0,[pwsBuf],-1,[psBuf],MAX_PATH,0,0

		lea	eax,[hSubkey]
		invoke	RegOpenKey,HKEY_CLASSES_ROOT,szCLSID,eax

		stdcall	GuardedDeleteKey,[hSubkey],[psBuf]
		.if	eax <> 0
			jmp	return2
		.endif

		invoke	RegCloseKey,[hSubkey]
		mov	eax,S_OK
return2:	ret
endp

;================================================================

proc		GuardedDeleteKey hKey,lpszSubKey
local		szSubKeyName[MAX_PATH+1]:BYTE,\
		hSubkey:DWORD
		invoke	RegOpenKey,[hKey],[lpszSubKey],[hSubkey]
		.if	eax <> 0
			mov	eax,REGDB_E_INVALIDVALUE
			jmp	return3
		.endif
KillNextSubkey:	lea	ecx,[szSubKeyName]					; check for a subkey
		invoke	RegEnumKey,[hSubkey],0,ecx,MAX_PATH+1
		.if	eax <> ERROR_NO_MORE_ITEMS
			lea	ecx,[szSubKeyName]
			stdcall	GuardedDeleteKey,[hSubkey],ecx
			jmp	KillNextSubkey
		.else
		.endif
		invoke	RegCloseKey,[hSubkey]
		invoke	RegDeleteKey,[hKey],[lpszSubKey]
		.if	eax <> 0
			mov	eax,S_OK
		.endif
return3:	ret
endp

;================================================================

proc		DllCanUnloadNow
		.if	[pICF.ICFRefCount] = 0
			mov	eax,TRUE
		.elseif
			mov	eax,FALSE
		.endif
		ret
endp

;================================================================

; pCLSID	= CLSID of the coclass the client requested
; pIID		= IID_IClassFactory
; ppv		= IClassFactory interface pointer
proc		DllGetClassObject pCLSID,pIID,ppv
local		hr:DWORD
		invoke	IsEqualGUID,[pCLSID],CLSID_Msgbox
			.if	eax = TRUE
				mov	eax,pICF				; get requested pointer (QueryInterface_CF,pIID,ppv)
				push	[ppv]
				push	[pIID]
				push	eax
				mov	eax,[eax]
				call	[IClassFactory.QueryInterface + eax]
				mov	[hr],eax				; return pointer

				mov	eax,pICF				; release pointer
				push	eax
				mov	eax,[eax]
				call	[IClassFactory.Release + eax]
			.else
				mov	[hr],CLASS_E_CLASSNOTAVAILABLE
			.endif
		mov	eax,[hr]
		ret
endp

;================================================================

; QueryInterface for the ClassFactory Interface
proc		QueryInterface_CF this,pRIID,ppv
local		wMatch:DWORD
		invoke	IsEqualGUID,[pRIID],IID_IUnknown			; check for supported interfaces
		mov	[wMatch],eax
		invoke	IsEqualGUID,[pRIID],IID_IClassFactory
		or	eax,[wMatch]
			.if	eax = TRUE					; asking for IUnknown or IClassFactory
				mov	eax,[this]
				mov	edx,[ppv]
				mov	[edx],eax				; so we point to ourselves

				push	eax
				mov	eax,[eax]
				call	[IClassFactory.AddRef + eax]		; increase reference count for new pointer

				mov	eax,S_OK				; signal all is well
				jmp	return4
			.endif
		mov	[ppv],0							; set pointer as NULL (bad)
		mov	eax,E_NOINTERFACE					; signal interface not supported
return4:	ret
endp

;================================================================

proc		AddRef_CF this
		inc	[pICF.ICFRefCount]
		mov	eax,[pICF.ICFRefCount]
		ret
endp

;================================================================

proc		Release_CF this
		dec	[pICF.ICFRefCount]
		mov	eax,[pICF.ICFRefCount]
		ret
endp

;================================================================

proc		CreateInstance pUnknownOuter,iid,ppv
local		pMyObject:DWORD,\
		hr:DWORD
		.if	[pUnknownOuter] <> 0
			mov	eax,CLASS_E_NOAGGREGATION			; no support for aggregation
		.else
			invoke	CoTaskMemAlloc,sizeof.oMB			; create new Msgbox object
			.if	eax <> 0
				mov	[oMB.IMsgbox + eax],IMsgboxVT		; initialize new object
				mov	[oMB.IMBRefCount + eax],1
			.endif
			mov	[pMyObject],eax					; save memory pointer
			.if	eax = 0						; create failed
				mov	eax,E_OUTOFMEMORY
			.else
				inc	[pICF.ICFRefCount]			; increase reference count of new object

; query new object for client-requested interface (QueryInterface_MC,iid,ppv)
				mov	eax,[pMyObject]
				push	[ppv]
				push	[iid]
				push	eax
				mov	eax,[eax]
				call	[IMsgbox.QueryInterface + eax]

				mov	[hr],eax
				.if	eax = 0					; query failed, no valid pointer, release object
					mov	eax,[pMyObject]
					push	eax
					mov	eax,[eax]
					call	[IMsgbox.Release + eax]
				.endif
			.endif
		.endif
		mov	eax,[hr]
		ret
endp

;================================================================

proc		LockServer pif,bLockServer
		.if	[bLockServer] = TRUE
			inc	[pICF.ICFRefCount]
		.else
			dec	[pICF.ICFRefCount]
		.endif
		mov	eax,S_OK
		ret
endp

;================================================================

; QueryInterface for the IMsgbox Interface
proc		QueryInterface_MC this,pRIID,ppv
local		wMatch:DWORD,\
		ppvt:DWORD
		invoke	IsEqualGUID,[pRIID],IID_IUnknown			; check for supported interfaces
		mov	[wMatch],eax
		invoke	IsEqualGUID,[pRIID],IID_IMsgbox
		or	eax,[wMatch]
		.if	eax = TRUE						; asking for IUnknown or IMsgbox
			mov	eax,[this]
			mov	edx,[ppv]
			mov	[edx],eax					; so we point to ourselves
			mov	[ppvt],eax

; increase reference count for new pointer
			mov	eax,[ppvt]					; get pointer to object
			push	eax
			mov	eax,[eax]
			call	[IMsgbox.AddRef + eax]

			mov	eax,S_OK					; signal all is well
			jmp	return5
		.endif

		xor	eax,eax							; return NULL (bad) pointer
		mov	edx,[ppv]
		mov	[edx],eax
		mov	eax,E_NOINTERFACE					; signal interface is not supported
return5:	ret
endp

;================================================================

proc		AddRef_MC this
		mov	eax,[this]
		inc	[oMB.IMBRefCount + eax]
		mov	eax,[oMB.IMBRefCount + eax]
		ret
endp

;================================================================

proc		Release_MC this
		mov	eax,[this]
		dec	[oMB.IMBRefCount + eax]
		mov	eax,[oMB.IMBRefCount + eax]
		.if	eax = 0							; reference count has dropped to zero, delete it
			invoke	CoTaskMemFree,[this]
			dec	[pICF.ICFRefCount]
			xor	eax,eax						; clear eax (count = 0)
		.endif
		ret
endp

;================================================================

proc		DoMsgbox this,hwnd,szMsg
		invoke	MessageBoxEx,0,[szMsg],szMessageByCOM,MB_OK,LANG_ENGLISH
		mov	eax,0
		ret
endp

;################################################################

section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',user32,'USER32.DLL',advapi32,'ADVAPI32.DLL',ole32,'OLE32.DLL'
import ole32,\
       StringFromGUID2,'StringFromGUID2',\
       IsEqualGUID,'IsEqualGUID',\
       CoTaskMemAlloc,'CoTaskMemAlloc',\
       CoTaskMemFree,'CoTaskMemFree'

;################################################################

section '.edata' export data readable
export 'msgbox.dll',\
	DllRegisterServer,'DllRegisterServer',\
	DllUnregisterServer,'DllUnregisterServer',\
	DllGetClassObject,'DllGetClassObject',\
	DllCanUnloadNow,'DllCanUnloadNow'

;################################################################

section '.reloc' fixups data discardable

;################################################################