;################################################################

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_CF,\
		AddRef_CF,\
		Release_CF,\
		CreateInstance,\
		LockServer

interface	IMsgbox,\
		QueryInterface_MC,\
		AddRef_MC,\
		Release_MC,\
		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 ClassFactory object structure
pICF			IClassFactory						; interface object
ICFRefCount		dd 1							; reference count

; declare the msgbox object structure
pIMB			IMsgbox							; interface object
IMBRefCount		dd 1							; reference count

;################################################################

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

		invoke	RegOpenKey,HKEY_CLASSES_ROOT,szCLSID,[hKey]
		.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

		invoke	RegOpenKey,HKEY_CLASSES_ROOT,szCLSID,[hSubkey]

		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	[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,\
		pFactory:DWORD
		invoke	IsEqualGUID,[pCLSID],CLSID_Msgbox
			.if	eax = TRUE
				mov	eax,pICF				; get pointer to object
				push	[ppv]
				push	[pIID]
				push	eax
				mov	eax,[eax]
				call	dword [eax + 0]				; get requested pointer (QueryInterface_CF,pIID,ppv)

				mov	[hr],eax				; determine result

				mov	eax,pICF				; get pointer to object
				push	eax
				mov	eax,[eax]
				call	dword [eax + 8]				; release pointer
			.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

				mov	eax,pICF				; get pointer to object
				push	eax
				mov	eax,[eax]
				call	dword [eax + 4]				; 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	[ICFRefCount]
		mov	eax,[ICFRefCount]
		ret
endp

;================================================================

proc		Release_CF this
		dec	[ICFRefCount]
		mov	eax,[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,8				; create new Msgbox object
			.if	eax <> 0
				mov	edx,eax					; initialize new object
				mov	dword [edx + 0],pIMB
				mov	dword [edx + 4],1
			.endif
			mov	[pMyObject],eax					; save memory pointer
			.if	eax = 0						; create failed
				mov	eax,E_OUTOFMEMORY
			.else
				inc	[ICFRefCount]				; increase reference count of new object
				mov	eax,[pMyObject]				; pointer to object
				push	[ppv]
				push	[iid]
				push	eax
				mov	eax,[eax]
				call	dword [eax + 0]				; query new object for client-requested interface (QueryInterface_MC,iid,ppv)

				mov	[hr],eax
				.if	eax = 0
					mov	eax,[pMyObject]			; pointer to object
					push	eax
					mov	eax,[eax]
					call	dword [eax + 8]			; Release_MC
				.endif
			.endif
		.endif
		mov	eax,[hr]
		ret
endp

;================================================================

proc		LockServer pif,bLockServer
		.if	[bLockServer] = TRUE
			inc	[ICFRefCount]
		.else
			dec	[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

			mov	eax,[ppvt]					; get pointer to object
			push	eax
			mov	eax,[eax]
			call	dword [eax + 4]					; increase reference count for new pointer

			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	dword [eax + 4]						; IMBRefCount
		mov	dword eax,[eax + 4]					; IMBRefCount
		ret
endp

;================================================================

proc		Release_MC this
		mov	eax,[this]
		dec	dword [eax + 4]						; IMBRefCount
		mov	dword eax,[eax + 4]					; IMBRefCount
		.if	eax = 0							; reference count has dropped to zero, delete it
			invoke	CoTaskMemFree,[this]
			dec	[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',\
	GuardedDeleteKey,'GuardedDeleteKey',\					;force fasm
	QueryInterface_CF,'QueryInterface_CF',\
	AddRef_CF,'AddRef_CF',\
	Release_CF,'Release_CF',\
	CreateInstance,'CreateInstance',\
	LockServer,'LockServer',\
	QueryInterface_MC,'QueryInterface_MC',\
	AddRef_MC,'AddRef_MC',\
	Release_MC,'Release_MC',\
	DoMsgbox,'DoMsgbox'

;################################################################

section '.reloc' fixups data discardable

;################################################################