;------------------------------------------------------------------------------------------
;
;   FileOpenDialog.asm - Jeremy Gordon's adaptation of the Microsoft sample at
;	https://learn.microsoft.com/en-us/windows/win32/learnwin32/example--the-open-dialog-box
;	February 2025
;
;   This demonstrates using the FileOpenDialog COM interface through the CoCreateInstance API
;	and also using the ShellItem interface
;	This is easy to do in assembler, in fact easier and more obvious than in "C"
;
;	This is written in GoAsm, one of the GoTools available from www.GoDevTool.com
;
;	Assemble using GoAsm FileOpenDialog.asm (produces PE COFF file)
;	Then link using:-
;   GoLink FileOpenDialog.obj user32.dll kernel32.dll ole32.dll -files -debug coff
;   
;------------------------------------------------------------------------------------------
;
;Here is a structure declaration containing the vtable for the FileOpenDialog interface.
;At compile time this gives the offset into the vtable of the particular function being called
;This table comes from ShObjidl_core.h
IFileOpenDialog STRUCT
		QueryInterface 		DD 0 ;+0h (This,riid,ppvObject)
		AddRef 				DD 0 ;+4h (This)
		Release 			DD 0 ;+8h (This)
		Show				DD 0 ;+0Ch (This,hwndOwner)	
		SetFileTypes		DD 0 ;+10h (This,cFileTypes,rgFilterSpec)	
		SetFileTypeIndex	DD 0 ;+14h (This,iFileType)	
		GetFileTypeIndex	DD 0 ;+18h (This,piFileType)	
		Advise				DD 0 ;+1Ch (This,pfde,pdwCookie)	
		Unadvise			DD 0 ;+20h (This,dwCookie)	
		SetOptions			DD 0 ;+24h (This,fos)	
		GetOptions			DD 0 ;+28h (This,pfos) 
		SetDefaultFolder	DD 0 ;+2Ch (This,psi)	
		SetFolder			DD 0 ;+30h (This,psi)	
		GetFolder			DD 0 ;+34h (This,ppsi)	
		GetCurrentSelection	DD 0 ;+38h (This,ppsi)	
		SetFileName			DD 0 ;+3Ch (This,pszName)	
		GetFileName			DD 0 ;+40h (This,pszName)	
		SetTitle			DD 0 ;+44h (This,pszTitle)	
		SetOkButtonLabel	DD 0 ;+48h (This,pszText)	
		SetFileNameLabel	DD 0 ;+4Ch (This,pszLabel)	
		GetResult			DD 0 ;+50h (This,ppsi)	
		AddPlace			DD 0 ;+54h (This,psi,fdap)	
		SetDefaultExtension	DD 0 ;+58h (This,pszDefaultExtension)	
		Close				DD 0 ;+5Ch (This,hr)	
		SetClientGuid		DD 0 ;+60h (This,guid)	
		ClearClientData		DD 0 ;+64h (This)	
		SetFilter			DD 0 ;+68h (This,pFilter)	
		GetResults			DD 0 ;+6Ch (This,ppenum)	
		GetSelectedItems	DD 0 ;+70h (This,ppsai)	
IFileOpenDialog ends
;
;Here is the vtable for the IShellItem interface which is also used here
;this comes from ShObjidl_core.h
IShellItem STRUCT
		QueryInterface 		DD 0 ;+0h (This,riid,ppvObject)
		AddRef 				DD 0 ;+4h (This)
		Release 			DD 0 ;+8h (This)
		BindToHandler		DD 0 ;+0Ch (This,pbc,bhid,riid,ppv)
		GetParent			DD 0 ;+10h (This,ppsi)
		GetDisplayName		DD 0 ;+14h (This,sigdnName,ppszName)
		GetAttributes		DD 0 ;+18h (This,sfgaoMask,psfgaoAttribs)
		Compare				DD 0 ;+1Ch (This,psi,hint,piOrder)
IShellItem ends
;
;------------------------------------------------------------------------
;
; The CoInvoke MACRO makes it easier to use the interfaces
; This makes use of GoAsm's ARGCOUNT
; ARGCOUNT is an assemble-time counter returning the number
; of arguments known to GoAsm when INVOKE is used
; See example below
;
;------------------------------------------------------------------------
;
CoInvoke(%o,%p,%a,%b,%c,%d) MACRO
    #if ARGCOUNT == 6
    PUSH %d
    #endif
    #if ARGCOUNT >= 5
    PUSH %c
    #endif
    #if ARGCOUNT >= 4
    PUSH %b
    #endif
    #if ARGCOUNT >= 3
    PUSH %a
    #endif
    mov eax,[%o]
    push eax
    mov eax,[eax]
    call [eax+%p]
ENDM
;
;for example CoInvoke(iFILEOPENDIALOG,IFileOpenDialog.Show,0)
;resolves to push 0, mov eax,[iFILEOPENDIALOG], push eax, mov eax,[eax], call [eax+0Ch]
;
GUID STRUCT
    DD 0
    DW 0
    DW 0
    DB 0
    DB 7 DUP 0 
ENDS
;
DATA SECTION
;
iFILEOPENDIALOG DD 0
FILEOPENRESULT	DD 0
FILEPATH		DD 0
;
;the COM class is identified by a unique number (a CLSID)
;the COM interface is also identified by a unique number (an IID)
;these numbers are declared in ShObjidl_core.h as follows
;CLSID_FileOpenDialog is DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7
;IID_IFileOpenDialog is d57c7288-d4ad-4768-be02-9d969532d960
;these are GUID type numbers and are a dword, word, word, word and 8 bytes
;Since Windows writes such numbers in memory/data in little-endian format it writes each 
;component backwards (least significant byte first).  Windows breaks with convention by
;regarding the last word as two bytes (which are therefore written in sequence).  This is
;all dealt with as follows together with the GUID structure above
CLSID_FileOpenDialog GUID <0DC1C5A9Ch,0E88Ah,4ddeh,0A5h,<0A1h,60h,0F8h,2Ah,20h,0AEh,0F7h>>
IID_IFileOpenDialog GUID <0d57c7288h,0d4adh,4768h,0beh,<2h,9dh,96h,95h,32h,0d9h,60h>>
;
MESSAGE_BUFFER DB 256 DUP 0
;
CODE SECTION
;
START:
INVOKE CoInitializeEx,0,0			;0=COINIT_MULTITHREADED
INVOKE CoCreateInstance,ADDR CLSID_FileOpenDialog,0,17h,ADDR IID_IFileOpenDialog,ADDR iFILEOPENDIALOG	 
;17h=CLSCTX_ALL (finds most convenient)
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
CoInvoke(iFILEOPENDIALOG,IFileOpenDialog.Show,0)
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
CoInvoke(iFILEOPENDIALOG,IFileOpenDialog.GetResult,ADDR FILEOPENRESULT)
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
CoInvoke(FILEOPENRESULT,IShellItem.GetDisplayName,0,ADDR FILEPATH)	;0=SIGDN_NORMALDISPLAY
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
MOV ESI,[FILEPATH]
INVOKE MessageBoxW,0,ESI,ADDR L'result',0      	;ok button only
INVOKE CoTaskMemFree,ESI
JMP >.fin
L2:
MOV EDI,ADDR MESSAGE_BUFFER
PUSH 0,256D,EDI			;256=size of buffer
PUSH 0,EAX,0			;eax=error result
PUSH 1000h              ;look at system message table
CALL FormatMessageA     ;format message from system into buffer
INVOKE MessageBoxA,0,EDI,ADDR 'error',0      	;ok button only
.fin:
MOV ECX,[FILEOPENRESULT]
JECXZ >L3
CoInvoke(FILEOPENRESULT,IShellItem.Release)
L3:
MOV ECX,[iFILEOPENDIALOG]
JECXZ >L4
CoInvoke(iFILEOPENDIALOG,IFileOpenDialog.Release)
L4:
INVOKE CoUninitialize
INVOKE ExitProcess,0
