The MASM Forum
64 bit assembler => ASMC Development => Topic started by: nidud on March 28, 2018, 04:00:32 AM
-
This is based on the Component Object Model (COM) introduced by Microsoft in 1993. In order to access these objects some "unconventional" methods where added to the assembler. The way COM objects are accessed in this case are from the C implementation declared in the Microsoft header files. A typical class will then be like this:
typedef struct IShellFolderVtbl {
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IShellFolder * _This, REFIID riid, void **ppvObject);
...
END_INTERFACE
} IShellFolderVtbl;
interface IShellFolder {
CONST_VTBL struct IShellFolderVtbl *lpVtbl;
};
The assembler version:
IShellFolderVtbl struct
STDMETHOD QueryInterface, :REFIID, :ptr
...
IShellFolderVtbl ends
IShellFolder struct
lpVtbl PVOID ?
IShellFolder ends
LPIShellFolder typedef ptr IShellFolder
The objects are normally instantiated with a CreateInstance() function that takes the class as argument and we end up with a pointer or just AX. The call to the method is then something like this:
mov rcx,rax ; load args
mov rdx,riid
mov r8,ppvObject
mov rax,[rcx] ; make the call
call [rax].IShellFolderVtbl.QueryInterface
The assembler needs to know the type of the base class in order to use these unconventional methods.
local s:IShellFolder
s.QueryInterface(riid, ppvObject)
* lea rcx,s
local p:LPIShellFolder
p.QueryInterface(riid, ppvObject)
* mov rcx,p
assume rbx:ptr IShellFolder
[rbx].QueryInterface(riid, ppvObject)
* mov rcx,rbx
If PROC is used inside a structure (normally an error) Asmc assumes this to be a pointer to a function. A structure member with a placeholder for strcmp/stricmp may then be defined as follow:
Compare proc :LPSTR, :LPSTR
* T$0001 typedef proto :LPSTR, :LPSTR
* P$0001 typedef ptr T$0001
* Compare P$0001 ?
The next target is a simplified class definition based on the above logic including static/external callbacks. The .CLASSDEF <name> [args] directive creates two structures and add the Vtbl pointer to the base.
.class IShellFolder
QueryInterface proc :REFIID, :ptr
AddRef proc
Release proc
.ends
The first method closes the base and adds Vtbl to the name.
.class IShellFolder
* LPISHELLFOLDER typedef ptr IShellFolder
* LPISHELLFOLDERVtbl typedef ptr IShellFolderVtbl
* IShellFolder::IShellFolder proto
* IShellFolder struct
* lpVtbl LPISHELLFOLDERVtbl ?
* IShellFolder ends
* IShellFolderVtbl struct 8
QueryInterface proc :REFIID, :ptr
* T$000B typedef proto :ptr IShellFolder, :REFIID, :ptr
* P$000B typedef ptr T$000B
* QueryInterface P$000B ?
...
.ends
* IShellFolderVtbl ends
To define the Compare pointer as in the structure above locally in the base class the keyword LOCAL may be used. Locally defined functions are called directly without the _this argument omitted.
Compare proc local :LPSTR, :LPSTR
Keywords like public could be consider later I guess, or any keyword not normally used after PROC but this in turn creates a half-baked OOP framework to exploit.
-
Here's a class created on the stack. This one resets the stack on exit so the order in which they are deleted matters as oppose to delete everything in the end using LEAVE. Note that the start offset of the class (.base in this case) is off by 8 bytes hence the buffer is aligned 16.
;
; Build: asmc -pe -win64 $*.asm
;
include stdio.inc
.code
.class XPush :UINT
base QWORD ?
buffer OWORD 8 dup(?)
Release proc
.ends
assume rcx:LPXPUSH
option win64:rsp nosave noauto
XPush::Release proc
movaps xmm0,[rcx].buffer[0x00]
movaps xmm1,[rcx].buffer[0x10]
movaps xmm2,[rcx].buffer[0x20]
movaps xmm3,[rcx].buffer[0x30]
movaps xmm4,[rcx].buffer[0x40]
movaps xmm5,[rcx].buffer[0x50]
movaps xmm6,[rcx].buffer[0x60]
movaps xmm7,[rcx].buffer[0x70]
mov rax,[rsp]
mov rsp,[rcx].base
jmp rax
XPush::Release endp
XPush::XPush proc ReservedStack:UINT
.repeat
.if !rcx
mov r8,rsp
lea rax,[r8+rdx] ; stack base
lea rcx,[rdx+8*16+16] ; alloc size
sub rax,rcx
and al,-16
test [rax],al ; probe..
mov rcx,[r8] ; return addr
mov rsp,rax ; new stack
mov [rax],rcx
and dl,-16 ; shadow space
lea rcx,[rax+rdx+16]
add r8,8
mov [rcx].base,r8
.endif
movaps [rcx].buffer[0x00],xmm0
movaps [rcx].buffer[0x10],xmm1
movaps [rcx].buffer[0x20],xmm2
movaps [rcx].buffer[0x30],xmm3
movaps [rcx].buffer[0x40],xmm4
movaps [rcx].buffer[0x50],xmm5
movaps [rcx].buffer[0x60],xmm6
movaps [rcx].buffer[0x70],xmm7
lea rdx,[rax+0x80]
mov [rcx],rdx
lea rax,XPush@Release
mov [rdx],rax
mov rax,rcx
.until 1
ret
XPush::XPush endp
option win64:rbp auto
main proc
local p:LPXPUSH
printf("rsp: %p\n", rsp)
mov p,XPush::XPush( NULL, @ReservedStack )
printf("rsp: %p\n", rsp)
p.Release()
printf("rsp: %p\n", rsp)
ret
main endp
end main
-
.COMDEF
Added a separate directive for COM without any types added. This is now used in the windows include files. In addition to this a default method (Release()) is added to .CLASS.
Method1 proc local ; static
Method2 proc :ptr ; first virtual function
* Class ends
* ClassVtbl struct
* T$000B typedef proto :ptr Class
* P$000B typedef ptr T$000B
* Method2 P$000B ?
This didn't work (fixed): externdef Profile:ptr CProfile
This is indeed a good way of bundle up software where objects could be created on the fly or used as global pointers. Data may not be needed or accessed using methods so the public information may not be the same as in the local implementation.
Local definition
MAXPROFILES equ 32
PROFILE STRUC
string LPSTR ?
begin dq ?
result dq ?
PROFILE ENDS
.class CProfile
table PROFILE MAXPROFILES dup(<>)
Release proc
Start proc :UINT, :LPSTR
Stop proc :UINT
Save proc :UINT, :LPSTR
Clear proc
.ends
Global header
.class CProfile
Release proc
Start proc :dword, :ptr
Stop proc :dword
Save proc :dword, :ptr
Clear proc
.ends
externdef Profile:LPCPROFILE
Guess you may see this as protected or private members of the class. As for sheared members this may be done using pointers (as in COM) or inheritance for code reuse.
Two classes using the same methods:
.class CHeap BufferSize:UINT
heap_base PVOID ?
heap_free PVOID ?
Release proc
Alloc proc :UINT
Free proc :PVOID
Realloc proc :PVOID, :UINT
Aligned proc :UINT, :UINT
NewString proc :LPSTR
Coreleft proc
.ends
.class CStack BufferSize:UINT, ReservedStack:UINT
heap_base PVOID ?
heap_free PVOID ?
stack_ptr SIZE_T ?
Release proc
Alloc proc :UINT
Free proc :PVOID
Realloc proc :PVOID, :UINT
Aligned proc :UINT, :UINT
NewString proc :LPSTR
Coreleft proc
.ends
The linkage of these modules is based on usage so the instance of Profile is auto created and ready to go in the same way as the __argv table. The stack buffer (CStack) however needs to be created in a stack frame so this have to be dynamically created.
The heap functions may then be profiled as follow:
if 0 ; compare to LIBC.malloc()
include malloc.inc
else
malloc equ <memory.Alloc>
free equ <memory.Free>
endif
; global pointer
.class CStack :dword, :dword
Release proc
Alloc proc :dword
Free proc :ptr
.ends
.data
memory LPCSTACK 0
.code
; create a one million byte stack buffer
main proc
local stk ; create a standard stack frame
mov memory, CStack::CStack(NULL, 1000000, @ReservedStack)
heap_loop()
memory.Release()
Profile.Save(4, "CStack::CStack()")
ret
main endp
; allocate some memory
heap_loop proc uses rsi rdi rbx r12
Profile.Start(0, "main")
.for (r12d=1000 : r12d : r12d-- )
Profile.Start(1, "loop")
.break .if !malloc( 1000 * size_t )
mov rsi,rax
Profile.Start(2, "malloc")
.for ( rdi=rsi, ebx=0 : ebx < 1000 : ebx++ )
malloc( 512 )
stosq
.endf
Profile.Stop(2)
Profile.Start(3, "free")
.for ( rdi=rsi, ebx=0 : ebx < 1000 : ebx++ )
lodsq
free(rax)
.endf
Profile.Stop(3)
free(rdi)
Profile.Stop(1)
.endf
Profile.Stop(0)
ret
heap_loop endp
In this case the .Save() command append result to a text file in this format:
CStack::CStack() - 2018-4-2 21:13:58:
0: 52290 main
1: 46500 loop
2: 9524 malloc
3: 8708 free
LIBC.malloc() - 2018-4-2 21:14:58:
0: 71304 main
1: 65418 loop
2: 26041 malloc
3: 16665 free
As for a more sophisticated implementation of OOP this I think will be rather clunky in assembler. It will also demand a lot of compiling in addition to all the bloat produced. The 64-bit code seems to clean up very nicely using this approach, having RCX pinned to the class. Making a habit of also returning RCX may simplify even further.
foo proc
local p:LPCLASS
[rcx].Method1()
[rcx].Method2(rdx)
[rcx].Release()
ret
foo endp
Code produced:
0: 55 push rbp
1: 48 8b ec mov rbp,rsp
4: 48 83 ec 30 sub rsp,0x30
8: ff 51 10 call QWORD PTR [rcx+0x10]
b: 48 8b 01 mov rax,QWORD PTR [rcx]
e: ff 50 08 call QWORD PTR [rax+0x8]
11: 48 8b 01 mov rax,QWORD PTR [rcx]
14: ff 10 call QWORD PTR [rax]
16: c9 leave
17: c3 ret
-
Hi nidud,
there seems to be a name conflict when creating methods with names matching from protos in other includes, e.g. windows header files.
For example, if you create a method named OpenFile or CreateFile the call to it will throw an error.
"class.inc"
include windows.inc ;// OpenFile declared in winbase.inc
include stdio.inc
include malloc.inc
.classdef Class :LPSTR
string LPSTR ?
Print proc
OpenFile proc :LPTSTR ;// Class::OpenFile ( ) declaration
.ends
"class.asm"
include class.inc
.data
virtual_table PVOID 0
.code
Class::Print proc
printf( "%s\n", [rcx].Class.string )
ret
Class::Print endp
;/////////////////////////////////////////////////
;// Class::OpenFile procedure/method definition
;//
Class::OpenFile proc szPath:LPTSTR
xor rax, rax
ret
Class::OpenFile endp
Class::Class proc String:LPSTR
.repeat
.if !rcx
.break .if !malloc( sizeof(Class) )
mov rcx,rax
mov rdx,String
.endif
mov [rcx].Class.string,rdx
mov rax,virtual_table
mov [rcx].Class.lpVtbl,rax
mov rax,rcx
.until 1
ret
Class::Class endp
Install proc private
.if malloc( sizeof(ClassVtbl) )
mov virtual_table,rax
lea rcx,free
mov [rax].ClassVtbl.Release,rcx
lea rcx,Class@Print
mov [rax].ClassVtbl.Print,rcx
.endif
ret
Install endp
.pragma init Install 50
end
"test.asm"
include class.inc
.code
main proc
local p:LPCLASS, s:Class
.if Class::Class( NULL, "String" )
mov p,rax
p.Print()
p.OpenFile("test.inc") ;// error A2008: syntax error : p
p.Release()
.endif
.if Class::Class( &s, "String2" )
s.Print()
.endif
ret
main endp
end
Output test.asm(14) : error A2008: syntax error : p
This is because asmc assumes in the call function OpenFile from winbase.inc.
Regards
Greenhorn
-
there seems to be a name conflict when creating methods with names matching from protos in other includes, e.g. windows header files.
Yes. It follows the same logic as a struct members. I usually add an underscore in front of these names.
I guess it would be possible to add some exception to this but that will destroy the simplicity of it all. As of now the .COMDEF and .CLASSDEF directives only creates two structures and add members accordingly, so this will also work without these directives by simply declaring a COM object as done in C.
-
Added a more portable implementation for class::member
Instead of using '@' to separate class from member functions an underscore is now used. In addition to this regular types may also be used where *this is declared directly as oppose to a pointer.
Some examples using types.
sbyte::add proto :sbyte
oword::add proto :oword
* sbyte_add proto :sbyte, :sbyte
* oword_add proto :oword, :oword
The assembler will in this case convert oword to pointers but pass bytes as value.
foo proc
local c1:sbyte, c2:sbyte
local o1:oword, o2:oword
sbyte::add( c1, c2 )
oword::add( o1, o2 )
ret
foo endp
20a: 55 push rbp
20b: 48 8b ec mov rbp,rsp
20e: 48 83 ec 50 sub rsp,0x50
212: 8a 4d ff mov cl,BYTE PTR [rbp-0x1]
215: 8a 55 fe mov dl,BYTE PTR [rbp-0x2]
218: e8 e3 ff ff ff call 0x200
21d: 48 8d 4d e8 lea rcx,[rbp-0x18]
221: 48 8d 55 d8 lea rdx,[rbp-0x28]
225: e8 e0 ff ff ff call 0x20a
22a: c9 leave
22b: c3 ret
Using struct's and floats.
xword struc
l dq ?
h dq ?
xword ends
xword::add proto :xword
real16::add proto :real16
foo proc
local x1:xword, x2:xword
local r1:real16, r2:real16
xword::add( x1, x2 )
real16::add( r1, r2 )
ret
foo endp
214: 55 push rbp
215: 48 8b ec mov rbp,rsp
218: 48 83 ec 60 sub rsp,0x60
21c: 48 8d 4d f0 lea rcx,[rbp-0x10]
220: 48 8d 55 e0 lea rdx,[rbp-0x20]
224: e8 e1 ff ff ff call 0x20a
229: 0f 28 45 d0 movaps xmm0,XMMWORD PTR [rbp-0x30]
22d: 0f 28 4d c0 movaps xmm1,XMMWORD PTR [rbp-0x40]
231: e8 ca ff ff ff call 0x200
236: c9 leave
237: c3 ret
-
Added some more samples to class directory (https://github.com/nidud/asmc/tree/master/source/test/class).
Using RCX as mention above cleans up very well. In the TConsole (https://github.com/nidud/asmc/blob/master/source/test/class/TConsole/test.asm) sample RCX is used for all calls to the class.
-
The SYSCALL directive in 64-bit (in addition to the instruction) refers to the System V AMD64 calling convention. The argument passing in the AMD64 ABI uses six registers as oppose to four in the Windows ABI.
RDI RSI RDX RCX R8 R9 – syscall
RCX RDX R8 R9 – fastcall
This is convenient for class members given RDI/RSI is preserved through Windows API calls if the class pointer resides in a register (RDI in this case). You also get additional two registers for arguments so this is a good mix. However, the :vararg logic differ in System V so this can not be used with the Windows ABI functions unless the function called is local.
A sample class using syscall methods. This from https://msdn.microsoft.com/en-us/library/hskdteyh.aspx
.class InstructionSet syscall
nIds SINT ?
nExIds SINT ?
vendor LPSTR ?
brand LPSTR ?
isIntel SINT ?
isAMD SINT ?
f_1_ECX dd ?
f_1_EDX dd ?
f_7_EBX dd ?
f_7_ECX dd ?
f_81_ECX dd ?
f_81_EDX dd ?
Release proc
GetVendor proc
GetBrand proc
GetSSE3 proc
GetPCLMULQDQ proc
GetMONITOR proc
GetSSSE3 proc
GetFMA proc
GetCMPXCHG16B proc
GetSSE41 proc
GetSSE42 proc
GetMOVBE proc
GetPOPCNT proc
GetAES proc
GetXSAVE proc
GetOSXSAVE proc
GetAVX proc
GetF16C proc
GetRDRAND proc
GetMSR proc
GetCX8 proc
GetSEP proc
GetCMOV proc
GetCLFSH proc
GetMMX proc
GetFXSR proc
GetSSE proc
GetSSE2 proc
GetFSGSBASE proc
GetBMI1 proc
GetHLE proc
GetAVX2 proc
GetBMI2 proc
GetERMS proc
GetINVPCID proc
GetRTM proc
GetAVX512F proc
GetRDSEED proc
GetADX proc
GetAVX512PF proc
GetAVX512ER proc
GetAVX512CD proc
GetSHA proc
GetPREFETCHWT1 proc
GetLAHF proc
GetLZCNT proc
GetABM proc
GetSSE4a proc
GetXOP proc
GetTBM proc
GetSYSCALL proc
GetMMXEXT proc
GetRDTSCP proc
Get3DNOWEXT proc
Get3DNOW proc
.ends
It's possible to override the calling convention for the member functions but the default constructor and the Release() function follows the class calling convention if set.
Get3DNOW proc fastcall
As for the implementation of methods they need to be declared if the calling convention differ from the global default.
.code
option win64:noauto
assume rdi:LPINSTRUCTIONSET
InstructionSet::GetVendor proc syscall
mov rax,[rdi].vendor
ret
InstructionSet::GetVendor endp
InstructionSet::GetBrand proc syscall
mov rax,[rdi].brand
ret
InstructionSet::GetBrand endp
__declget macro method, reg, bit
InstructionSet::Get&method& proc syscall
mov eax,[rdi].reg
and eax,1 shl bit
ret
InstructionSet::Get&method& endp
endm
__declge2 macro method, reg, bit, is
InstructionSet::Get&method& proc syscall
bt [rdi].is,0
sbb eax,eax
and eax,[rdi].reg
and eax,1 shl bit
ret
InstructionSet::Get&method& endp
endm
__declget SSE3, f_1_ECX, 0
__declget PCLMULQDQ, f_1_ECX, 1
__declget MONITOR, f_1_ECX, 3
...
Given there's no inherent shadow space logic and thus no placeholder for arguments the names refer to the corresponding register:
mov rax,[this].vendor
The default constructor and Release():
option win64:rbp nosave
InstructionSet::InstructionSet proc syscall uses rbx
local cpui[4]:SINT
local vendor[0x20]:SBYTE
local brand[0x40]:SBYTE
local cpustring[512]:SBYTE
.if malloc( InstructionSet + InstructionSetVtbl )
assume rsi:ptr InstructionSet
mov rsi,rax
mov rdi,rax
xor eax,eax
mov ecx,InstructionSet
rep stosb
mov [rsi],rdi
lea rax,InstructionSet_Release
stosq
for q,<Vendor,Brand,SSE3,PCLMULQDQ,MONITOR,SSSE3,FMA,CMPXCHG16B,SSE41,SSE42,MOVBE,POPCNT,\
AES,XSAVE,OSXSAVE,AVX,F16C,RDRAND,MSR,CX8,SEP,CMOV,CLFSH,MMX,FXSR,SSE,SSE2,FSGSBASE,BMI1,\
HLE,AVX2,BMI2,ERMS,INVPCID,RTM,AVX512F,RDSEED,ADX,AVX512PF,AVX512ER,AVX512CD,SHA,\
PREFETCHWT1,LAHF,LZCNT,ABM,SSE4a,XOP,TBM,SYSCALL,MMXEXT,RDTSCP,3DNOWEXT,3DNOW>
lea rax,InstructionSet_Get&q&
stosq
endm
;;
;; Calling __cpuid with 0x0 as the function_id argument
;; gets the number of the highest valid function ID.
;;
mov [rsi].nIds,__cpuid(&cpui, 0)
.for (rdi = &cpustring, ebx = 0: ebx <= [rsi].nIds: ++ebx)
__cpuidex(&cpui, ebx, 0)
stosd
mov eax,cpui[4]
stosd
mov eax,ecx
stosd
mov eax,edx
stosd
.endf
;;
;; Capture vendor string
;;
lea rdi,brand
xor eax,eax
mov ecx,sizeof(brand) + sizeof(vendor)
rep stosb
lea rax,vendor
mov ecx,dword ptr cpustring[0x04]
mov [rax],ecx
mov ecx,dword ptr cpustring[0x0C]
mov [rax+4],ecx
mov ecx,dword ptr cpustring[0x08]
mov [rax+8],ecx
mov [rsi].vendor,_strdup(rax)
.if ( !strcmp(rax, "GenuineIntel") )
mov [rsi].isIntel,TRUE
.elseif ( !strcmp([rsi].vendor, "AuthenticAMD") )
mov [rsi].isAMD,TRUE
.endif
;;
;; load bitset with flags for function 0x00000001
;;
.if ([rsi].nIds >= 1)
mov eax,dword ptr cpustring[0x10][0x8]
mov edx,dword ptr cpustring[0x10][0xC]
mov [rsi].f_1_ECX,eax
mov [rsi].f_1_EDX,edx
.endif
;;
;; load bitset with flags for function 0x00000007
;;
.if ([rsi].nIds >= 7)
mov eax,dword ptr cpustring[0x70][0x4]
mov edx,dword ptr cpustring[0x70][0x8]
mov [rsi].f_7_EBX,eax
mov [rsi].f_7_ECX,edx
.endif
;;
;; Calling __cpuid with 0x80000000 as the function_id argument
;; gets the number of the highest valid extended ID.
;;
mov [rsi].nExIds,__cpuid(&cpui, 0x80000000)
.fors (rdi = &cpustring, ebx = 0x80000000: ebx <= [rsi].nExIds: ++ebx)
__cpuidex(&cpui, ebx, 0)
stosd
mov eax,cpui[4]
stosd
mov eax,ecx
stosd
mov eax,edx
stosd
.endf
;;
;; load bitset with flags for function 0x80000001
;;
.if ([rsi].nExIds >= 0x80000001)
mov eax,dword ptr cpustring[0x10][0x8]
mov edx,dword ptr cpustring[0x10][0xC]
mov [rsi].f_81_ECX,eax
mov [rsi].f_81_EDX,edx
.endif
;;
;; Interpret CPU brand string if reported
;;
.if ([rsi].nExIds >= 0x80000004)
mov rdx,rsi
lea rdi,brand
lea rsi,cpustring[0x20]
mov ecx,(sizeof(cpui) * 3) / 8
rep movsq
mov rsi,rdx
mov [rsi].brand,_strdup(&brand)
.endif
assume rsi:nothing
mov rax,rsi
.endif
ret
InstructionSet::InstructionSet endp
InstructionSet::Release proc syscall
free([rdi].vendor)
free([rdi].brand)
free(rdi)
ret
InstructionSet::Release endp
Create the class and print out some information using RDI as pointer.
main proc
mov rdi,InstructionSet::InstructionSet(NULL)
support_message macro isa_feature, is_supported
mov edx,' '
.if is_supported
mov edx,'x'
.endif
printf( "[%c] %s\n", edx, isa_feature )
retm<>
endm
printf( "%s\n", [rdi].GetVendor() )
printf( "%s\n", [rdi].GetBrand() )
support_message("3DNOW", [rdi].Get3DNOW())
support_message("3DNOWEXT", [rdi].Get3DNOWEXT())
support_message("ABM", [rdi].GetABM())
support_message("ADX", [rdi].GetADX())
support_message("AES", [rdi].GetAES())
support_message("AVX", [rdi].GetAVX())
support_message("AVX2", [rdi].GetAVX2())
support_message("AVX512CD", [rdi].GetAVX512CD())
support_message("AVX512ER", [rdi].GetAVX512ER())
support_message("AVX512F", [rdi].GetAVX512F())
support_message("AVX512PF", [rdi].GetAVX512PF())
support_message("BMI1", [rdi].GetBMI1())
support_message("BMI2", [rdi].GetBMI2())
support_message("CLFSH", [rdi].GetCLFSH())
support_message("CMPXCHG16B", [rdi].GetCMPXCHG16B())
support_message("CX8", [rdi].GetCX8())
support_message("ERMS", [rdi].GetERMS())
support_message("F16C", [rdi].GetF16C())
support_message("FMA", [rdi].GetFMA())
support_message("FSGSBASE", [rdi].GetFSGSBASE())
support_message("FXSR", [rdi].GetFXSR())
support_message("HLE", [rdi].GetHLE())
support_message("INVPCID", [rdi].GetINVPCID())
support_message("LAHF", [rdi].GetLAHF())
support_message("LZCNT", [rdi].GetLZCNT())
support_message("MMX", [rdi].GetMMX())
support_message("MMXEXT", [rdi].GetMMXEXT())
support_message("MONITOR", [rdi].GetMONITOR())
support_message("MOVBE", [rdi].GetMOVBE())
support_message("MSR", [rdi].GetMSR())
support_message("OSXSAVE", [rdi].GetOSXSAVE())
support_message("PCLMULQDQ", [rdi].GetPCLMULQDQ())
support_message("POPCNT", [rdi].GetPOPCNT())
support_message("PREFETCHWT1", [rdi].GetPREFETCHWT1())
support_message("RDRAND", [rdi].GetRDRAND())
support_message("RDSEED", [rdi].GetRDSEED())
support_message("RDTSCP", [rdi].GetRDTSCP())
support_message("RTM", [rdi].GetRTM())
support_message("SEP", [rdi].GetSEP())
support_message("SHA", [rdi].GetSHA())
support_message("SSE", [rdi].GetSSE())
support_message("SSE2", [rdi].GetSSE2())
support_message("SSE3", [rdi].GetSSE3())
support_message("SSE4.1", [rdi].GetSSE41())
support_message("SSE4.2", [rdi].GetSSE42())
support_message("SSE4a", [rdi].GetSSE4a())
support_message("SSSE3", [rdi].GetSSSE3())
support_message("SYSCALL", [rdi].GetSYSCALL())
support_message("TBM", [rdi].GetTBM())
support_message("XOP", [rdi].GetXOP())
support_message("XSAVE", [rdi].GetXSAVE())
[rdi].Release()
ret
main endp
end main
-
Hi nidud,
there seems to be a name conflict when creating methods with names matching from protos in other includes, e.g. windows header files.
For example, if you create a method named OpenFile or CreateFile the call to it will throw an error.
Made some updates to the oop parsing so this is now fixed.
bar proto
.class foo
bar proc
.ends
main proc
local p:ptr foo
p.bar()
ret
main endp
-
A full sample (https://github.com/nidud/asmc/tree/master/source/test/wininc/IFileOperation) that demonstrates the use of COM form the Windows Software Development Kit (SDK)\...\Samples\WinUI\Shell\AppPlatform\FileOperations. The sample uses the strsafe.lib (https://github.com/nidud/asmc/tree/master/source/strsafe) and uuid.lib (https://github.com/nidud/asmc/tree/master/source/uuid) (updated).
STRICT_TYPED_ITEMIDS equ 1
include windows.inc ;; Standard include
include Shellapi.inc ;; Included for shell constants such as FO_* values
include shlobj.inc ;; Required for necessary shell dependencies
include strsafe.inc ;; Including StringCch* helpers
c_szSampleFileName equ <"SampleFile">
c_szSampleFileNewname equ <"NewName">
c_szSampleFileExt equ <"txt">
c_szSampleSrcDir equ <"FileOpSampleSource">
c_szSampleDstDir equ <"FileOpSampleDestination">
c_cMaxFilesToCreate equ 10
.code
CreateAndInitializeFileOperation proc riid:REFIID, ppv:ptr ptr
local hr:HRESULT
local pfo:ptr IFileOperation
xor eax,eax
mov [rdx],rax
;; Create the IFileOperation object
mov hr,CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_ALL, &IID_IFileOperation, &pfo)
.if (SUCCEEDED(hr))
;; Set the operation flags. Turn off all UI
;; from being shown to the user during the
;; operation. This includes error, confirmation
;; and progress dialogs.
mov hr,pfo.SetOperationFlags(FOF_NO_UI)
.if (SUCCEEDED(hr))
mov hr,pfo.QueryInterface(riid, ppv)
.endif
pfo.Release();
.endif
mov eax,hr
ret
CreateAndInitializeFileOperation endp
;; Synopsis: Create the source and destination folders for the sample
;;
;; Arguments: psiSampleRoot - Item of the parent folder where the sample folders will be created
;; ppsiSampleSrc - On success contains the source folder item to be used for sample operations
;; ppsiSampleDst - On success contains the destination folder item to be used for sample operations
;;
;; Returns: S_OK if successful
CreateSampleFolders proc psiSampleRoot:ptr IShellItem,
ppsiSampleSrc:ptr ptr IShellItem, ppsiSampleDst:ptr ptr IShellItem
local hr:HRESULT
local pfo:ptr IFileOperation
local psiSrc:ptr IShellItem
local psiDst:ptr IShellItem
xor eax,eax
mov [rdx],rax
mov [r8],rax
mov psiSrc,rax
mov psiDst,rax
mov hr,CreateAndInitializeFileOperation(&IID_IFileOperation, &pfo)
.if (SUCCEEDED(hr))
;; Use the file operation to create a source and destination folder
mov hr,pfo.NewItem(psiSampleRoot, FILE_ATTRIBUTE_DIRECTORY, c_szSampleSrcDir, NULL, NULL)
.if (SUCCEEDED(hr))
mov hr,pfo.NewItem(psiSampleRoot, FILE_ATTRIBUTE_DIRECTORY, c_szSampleDstDir, NULL, NULL)
.if (SUCCEEDED(hr))
mov hr,pfo.PerformOperations()
.if (SUCCEEDED(hr))
;; Now that the folders have been created, create items for them. This is just an optimization so
;; that the sample does not have to rebind to these items for each sample type.
mov hr,SHCreateItemFromRelativeName(psiSampleRoot, c_szSampleSrcDir, NULL, &IID_IShellItem, &psiSrc)
.if (SUCCEEDED(hr))
mov hr,SHCreateItemFromRelativeName(psiSampleRoot, c_szSampleDstDir, NULL, &IID_IShellItem, &psiDst)
.if (SUCCEEDED(hr))
mov rcx,ppsiSampleSrc
mov rax,psiSrc
mov [rcx],rax
mov rcx,ppsiSampleDst
mov rax,psiDst
mov [rcx],rax
;; Caller takes ownership
xor eax,eax
mov psiSrc,rax
mov psiDst,rax
.endif
.endif
.if (psiSrc)
psiSrc.Release()
.endif
.if (psiDst)
psiDst.Release()
.endif
.endif
.endif
.endif
.endif
mov eax,hr
ret
CreateSampleFolders endp
;; Synopsis: Creates all of the files needed by this sample the requested known folder
;;
;; Arguments: psiFolder - Folder that will contain the sample files
;;
;; Returns: S_OK if successful
CreateSampleFiles proc uses rsi psiFolder:ptr IShellItem
local hr:HRESULT
local pfo:ptr IFileOperation
local szSampleFileName[MAX_PATH]:WCHAR
mov hr,CreateAndInitializeFileOperation(&IID_IFileOperation, &pfo)
.if (SUCCEEDED(hr))
mov hr,StringCchPrintfW(&szSampleFileName, ARRAYSIZE(szSampleFileName), "%s.%s", c_szSampleFileName, c_szSampleFileExt)
.if (SUCCEEDED(hr))
;; the file to be used for the single copy sample
mov hr,pfo.NewItem(psiFolder, FILE_ATTRIBUTE_NORMAL, &szSampleFileName, NULL, NULL)
;; the files to be used for the multiple copy sample
.for (esi = 0: SUCCEEDED(hr) && esi < c_cMaxFilesToCreate: esi++)
mov hr,StringCchPrintfW(&szSampleFileName, ARRAYSIZE(szSampleFileName), "%s%u.%s", c_szSampleFileName, rsi, c_szSampleFileExt)
.if (SUCCEEDED(hr))
mov hr,pfo.NewItem(psiFolder, FILE_ATTRIBUTE_NORMAL, &szSampleFileName, NULL, NULL)
.endif
.endf
.if (SUCCEEDED(hr))
mov hr,pfo.PerformOperations()
.endif
.endif
pfo.Release()
.endif
mov eax,hr
ret
CreateSampleFiles endp
;; Synopsis: Deletes the files/folders created by this sample
;;
;; Arguments: psiSrc - Source folder item
;; psiDst - Destination folder item
;;
;; Returns: S_OK if successful
DeleteSampleFiles proc psiSrc:ptr IShellItem, psiDst:ptr IShellItem
local hr:HRESULT
local pfo:ptr IFileOperation
mov hr,CreateAndInitializeFileOperation(&IID_IFileOperation, &pfo)
.if (SUCCEEDED(hr))
mov hr,pfo.DeleteItem(psiSrc, NULL)
.if (SUCCEEDED(hr))
mov hr,pfo.DeleteItem(psiDst, NULL)
.if (SUCCEEDED(hr))
mov hr,pfo.PerformOperations()
.endif
.endif
pfo.Release()
.endif
mov eax,hr
ret
DeleteSampleFiles endp
;; Synopsis: This example copies a single item from the sample source folder
;; to the sample dest folder using a new item name.
;;
;; Arguments: psiSrc - Source folder item
;; psiDst - Destination folder item
;;
;; Returns: S_OK if successful
CopySingleFile proc psiSrc:ptr IShellItem, psiDst:ptr IShellItem
local hr:HRESULT
local pfo:ptr IFileOperation
local szNewName[MAX_PATH]:WCHAR
local szSampleFileName[MAX_PATH]:WCHAR
local psiSrcFile:ptr IShellItem
;; Create the IFileOperation object
mov hr,CreateAndInitializeFileOperation(&IID_IFileOperation, &pfo)
.if (SUCCEEDED(hr))
mov hr,StringCchPrintfW(&szSampleFileName, ARRAYSIZE(szSampleFileName), "%s.%s", c_szSampleFileName, c_szSampleFileExt)
.if (SUCCEEDED(hr))
mov hr,SHCreateItemFromRelativeName(psiSrc, &szSampleFileName, NULL, &IID_IShellItem, &psiSrcFile)
.if (SUCCEEDED(hr))
mov hr,StringCchPrintfW(&szNewName, ARRAYSIZE(szNewName), "%s.%s", c_szSampleFileNewname, c_szSampleFileExt)
.if (SUCCEEDED(hr))
mov hr,pfo.CopyItem(psiSrcFile, psiDst, &szNewName, NULL)
.if (SUCCEEDED(hr))
mov hr,pfo.PerformOperations()
.endif
.endif
psiSrcFile.Release()
.endif
.endif
pfo.Release()
.endif
mov eax,hr
ret
CopySingleFile endp
;; Synopsis: Creates an IShellItemArray containing the sample files to be used
;; in the CopyMultipleFiles sample
;;
;; Arguments: psiSrc - Source folder item
;;
;; Returns: S_OK if successful
CreateShellItemArrayOfSampleFiles proc uses rsi rdi psiSrc:ptr IShellItem, riid:REFIID, ppv:ptr ptr
local hr:HRESULT
local psfSampleSrc:ptr IShellFolder
local rgpidlChildren[c_cMaxFilesToCreate]:PITEMID_CHILD ;= {0};
local szSampleFileName[MAX_PATH]:WCHAR
local psia:ptr IShellItemArray
xor eax,eax
mov [r8],rax
lea rdi,rgpidlChildren
mov [rdi],rax
mov hr,psiSrc.BindToHandler(NULL, &BHID_SFObject, &IID_IShellFolder, &psfSampleSrc)
.if (SUCCEEDED(hr))
.for (esi = 0: SUCCEEDED(hr) && esi < ARRAYSIZE(rgpidlChildren): esi++)
mov hr,StringCchPrintfW(&szSampleFileName, ARRAYSIZE(szSampleFileName), "%s%u.%s", c_szSampleFileName, esi, c_szSampleFileExt)
.if (SUCCEEDED(hr))
mov hr,psfSampleSrc.ParseDisplayName(NULL, NULL, &szSampleFileName, NULL, &[rdi+rsi*8], NULL)
.endif
.endf
.if (SUCCEEDED(hr))
mov hr,SHCreateShellItemArray(NULL, psfSampleSrc, c_cMaxFilesToCreate, &rgpidlChildren, &psia)
.if (SUCCEEDED(hr))
mov hr,psia.QueryInterface(riid, ppv)
psia.Release()
.endif
.endif
.for (esi = 0: esi < ARRAYSIZE(rgpidlChildren): esi++)
CoTaskMemFree([rdi+rsi*8])
.endf
psfSampleSrc.Release()
.endif
mov eax,hr
ret
CreateShellItemArrayOfSampleFiles endp
;; Synopsis: This example creates multiple files under the specified folder
;; path and copies them to the same directory with a new name.
;;
;; Arguments: psiSrc - Source folder item
;; psiDst - Destination folder item
;;
;; Returns: S_OK if successful
CopyMultipleFiles proc psiSrc:ptr IShellItem, psiDst:ptr IShellItem
local hr:HRESULT
local pfo:ptr IFileOperation
local psiaSampleFiles:ptr IShellItemArray
;; Create the IFileOperation object
mov hr,CreateAndInitializeFileOperation(&IID_IFileOperation, &pfo)
.if (SUCCEEDED(hr))
mov hr,CreateShellItemArrayOfSampleFiles(psiSrc, &IID_IShellItemArray, &psiaSampleFiles)
.if (SUCCEEDED(hr))
mov hr,pfo.CopyItems(psiaSampleFiles, psiDst)
.if (SUCCEEDED(hr))
mov hr,pfo.PerformOperations()
.endif
psiaSampleFiles.Release()
.endif
pfo.Release()
.endif
mov eax,hr
ret
CopyMultipleFiles endp
wmain proc
local hr:HRESULT
local psiDocuments:ptr IShellItem
local psiSampleSrc:ptr IShellItem
local psiSampleDst:ptr IShellItem
mov hr,CoInitializeEx(NULL, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE)
.if (SUCCEEDED(hr))
;; Get the documents known folder. This folder will be used to create subfolders
;; for the sample source and destination
mov hr,SHCreateItemInKnownFolder(&FOLDERID_Documents, KF_FLAG_DEFAULT_PATH, NULL, &IID_IShellItem, &psiDocuments)
.if (SUCCEEDED(hr))
mov hr,CreateSampleFolders(psiDocuments, &psiSampleSrc, &psiSampleDst)
.if (SUCCEEDED(hr))
mov hr,CreateSampleFiles(psiSampleSrc)
.if (SUCCEEDED(hr))
mov hr,CopySingleFile(psiSampleSrc, psiSampleDst)
.if (SUCCEEDED(hr))
mov hr,CopyMultipleFiles(psiSampleSrc, psiSampleDst)
.endif
.endif
DeleteSampleFiles(psiSampleSrc, psiSampleDst)
psiSampleSrc.Release()
psiSampleDst.Release()
.endif
psiDocuments.Release()
.endif
CoUninitialize()
.endif
xor eax,eax
ret
wmain endp
end
-
Added another sample (https://github.com/nidud/asmc/tree/master/source/test/wininc/Touch) form the (SDK)\...\Samples\Touch\MTGestures.
This is a typical .class sample from C++ using a windows touch screen interface. Mainly used to test some added math functions and WinInc updates. It basically just draw a rectangle which is resized and centred on a desktop but if you have the hardware (which i don't) you supposed to be able to pan, zoom, and rotate the object using the GestureEngine class.
.case GID_ROTATE
.switch (gi.dwFlags)
.case GF_BEGIN
mov [rsi]._dwArguments,0
.endc
.default
movzx eax,gi.ptsLocation.x
mov [rsi]._ptFirst.x,eax
movzx eax,gi.ptsLocation.y
mov [rsi]._ptFirst.y,eax
ScreenToClient(hWnd, &[rsi]._ptFirst)
mov eax,dword ptr gi.ullArguments
cvtsi2sd xmm0,rax
movsd xmm2,GID_ROTATE_ANGLE_FROM_ARGUMENT(xmm0)
mov eax,[rsi]._dwArguments
cvtsi2sd xmm0,rax
subsd xmm2,GID_ROTATE_ANGLE_FROM_ARGUMENT(xmm0)
[rsi].ProcessRotate(xmm2, [rsi]._ptFirst.x, [rsi]._ptFirst.y)
InvalidateRect(hWnd, NULL, TRUE)
mov [rsi]._dwArguments,LODWORD(gi.ullArguments)
.endc
.endsw
.endc
-
Added a DLL COM sample (https://github.com/nidud/asmc/tree/master/source/test/comdll) using CoGetClassObject().
This needs to be manually installed (https://github.com/nidud/asmc/blob/master/source/test/comdll/install.asm) with a full path to the dll, and two new GUIDs (https://github.com/nidud/asmc/blob/master/source/test/comdll/locals.inc) needs to be generated using guidgen.exe. This is added to HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID. Ones installed the OS will load the DLL from this location and call DllGetClassObject() to get a pointer to a IClassFactory.
.if !CoGetClassObject(&CLSID_IConfig, CLSCTX_INPROC_SERVER,
0, &IID_IClassFactory, &class)
.if !class.CreateInstance(0, &IID_IConfig, &config)
config.create( "Version" )
The sample is modified from this article:
https://www.codeproject.com/Articles/13601/COM-in-plain-C
The class used is copied from the IConfig (https://github.com/nidud/asmc/tree/master/source/test/classdef/IConfig) sample. This is a .classdef with full data access which is stripped in a COM component. The user-definition is then smaller than the actual implementation.
.comdef IConfig
QueryInterface proc :REFIID, :ptr
AddRef proc
Release proc
read proc :string_t
write proc :string_t
find proc :string_t
create proc :string_t, :vararg
getvalue proc :string_t, :string_t
delete proc :string_t
.ends
-
Added .NEW directive for .CLASS.
This assumes a default constructor named <class>::<class>() that allocate a new instance if NULL is passed or just sets the vtable if not.
.class C :byte
atom byte ?
delete proc
get proc
set proc :byte
.ends
.data
vtable CVtbl { free, C_get, C_set }
.code
assume rcx:ptr C
C::C proc val:byte
.repeat
.if !rcx
.break .if !malloc( sizeof(C) )
mov rcx,rax
.endif
lea rax,vtable
mov [rcx].lpVtbl,rax
mov [rcx].atom,val
mov rax,rcx
.until 1
ret
C::C endp
C::get proc
movzx eax,[rcx].atom
ret
C::get endp
C::set proc val:byte
mov [rcx].atom,dl
ret
C::set endp
The directive is similar to LOCAL but allows late allocation.
putval proc c:ptr C
printf("Value: %c\n", c.get())
ret
putval endp
main proc
.new c:C('C') ; C::C(&c, 1)
putval(&c)
.new p:ptr C('C') ; mov p,C::C(NULL, 'C')
putval(p)
p.delete()
xor eax,eax
ret
main endp
-
Added : public class to .class and .comdef
.comdef IUnknown
QueryInterface proc :REFIID, :ptr
AddRef proc
Release proc
.ends
.comdef IClassFactory : public IUnknown
CreateInstance proc :ptr, :REFIID, :ptr
LockServer proc :BOOL
.ends
The stack levels are currently added by anonymous structures and not exploded. The access is direct but declaration in levels.
.class a : byte
A byte ?
A proc
.ends
.class b : public a
B byte ?
B proc
.ends
.class c : public b
C byte ?
C proc
.ends
.class d : public c
D byte ?
D proc
.ends
Access:
local x:d
mov x.A,a
mov x.B,b
mov x.C,c
mov x.D,d
x.A()
x.B()
x.C()
x.D()
-
Applied the public directive to the include files and this cleans up very well, reducing the share size of it with up to 200K. As a result you end up with a lot of empty .comdefs:
.comdef IUnknown
Release proc
.ends
.comdef ID3D11DeviceChild : public IUnknown
GetDevice proc :ptr
.ends
.comdef ID3D11Resource : public ID3D11DeviceChild
GetType proc
.ends
.comdef ID3D11Buffer : public ID3D11Resource
GetDesc proc :ptr
.ends
.comdef ID3D11Texture2D : public ID3D11Buffer
.ends
If the base class (ID3D11Texture2D in this case) was empty the Vtbl struct was not created so this is fixed in v2.31.02.
A somewhat experimental mechanism to add inline class members is added. This is an interesting combination of INVOKE and MACRO. The implementation use INVOKE to load the arguments (as normal) and if exist, Class_Method MACRO, this will be used instead. This already works for constructors using the .NEW directive and it's also possible to override a PROTO with a MACRO.
The restrictions for these macros is 64-bit and register arguments. The stack may be set/reset by INVOKE depending on flags used. Max 4 arguments (including *this) in FAST/VECTOR-CALL and 6 in SYSCALL.
.comdef IUnknown
Release proc :qword, :qword
.ends
; inline
IUnknown_IUnknown macro this
exitm<>
endm
IUnknown_Release macro this, a1, a2
add a1,a2
mov rax,this
exitm<>
endm
Note that the arguments are always 64-bit in size. First argument always used (RCX/RDI). The stack is aligned 16 and RSP points to the shadow space for the call.
.if eax
.new p:IUnknown()
.if p.Release(1, [rdi])
nop
.endif
.endif
The constructor just add the class as a LOCAL so no code is added there.
test eax, eax
jz ?_001
mov r8, [rdi] ; arg 3
mov edx, 1 ; arg 2
lea rcx, [rbp-8H] ; arg 1
add rdx, r8 ; macro..
mov rax, rcx ; macro..
test rax, rax
jz ?_001
nop
?_001:
-
Here's a inline version of the InstructionSet class. The whole class is now just a header so no external code needed. Note that the constructor is defined as a PROTO by the CLASS directive so UNDEF is needed there but the member functions will be resolved by INVOKE.
include intrin.inc
MAXCPUSTRING equ 512
.class InstructionSet_Internal
nIds int_t ?
nExIds int_t ?
isIntel int_t ?
isAMD int_t ?
union
struct
f_1_ECX uint_t ?
f_1_EDX uint_t ?
ends
f_1_RAX uint64_t ?
ends
union
struct
f_7_EBX uint_t ?
f_7_ECX uint_t ?
ends
f_7_RAX uint64_t ?
ends
union
struct
f_81_ECX uint_t ?
f_81_EDX uint_t ?
ends
f_81_RAX uint64_t ?
ends
.ends
.class InstructionSet : public InstructionSet_Internal
GetVendor proc ; getters
GetBrand proc
GetSSE3 proc
GetPCLMULQDQ proc
GetMONITOR proc
GetSSSE3 proc
GetFMA proc
GetCMPXCHG16B proc
GetSSE41 proc
GetSSE42 proc
GetMOVBE proc
GetPOPCNT proc
GetAES proc
GetXSAVE proc
GetOSXSAVE proc
GetAVX proc
GetF16C proc
GetRDRAND proc
GetMSR proc
GetCX8 proc
GetSEP proc
GetCMOV proc
GetCLFSH proc
GetMMX proc
GetFXSR proc
GetSSE proc
GetSSE2 proc
GetFSGSBASE proc
GetBMI1 proc
GetHLE proc
GetAVX2 proc
GetBMI2 proc
GetERMS proc
GetINVPCID proc
GetRTM proc
GetAVX512F proc
GetRDSEED proc
GetADX proc
GetAVX512PF proc
GetAVX512ER proc
GetAVX512CD proc
GetSHA proc
GetPREFETCHWT1 proc
GetLAHF proc
GetLZCNT proc
GetABM proc
GetSSE4a proc
GetXOP proc
GetTBM proc
GetSYSCALL proc
GetMMXEXT proc
GetRDTSCP proc
Get3DNOWEXT proc
Get3DNOW proc
.ends
undef InstructionSet_InstructionSet ; flip the PROTO to a MACRO
InstructionSet_InstructionSet macro this ; .NEW cpu : InstructionSet()
.new vendor[16]:char_t ; add to local stack
.new brand[64]:char_t
ifidn @SubStr(this,1,1),<&>
lea rax,@SubStr(this,2)
else
mov rax,this
endif
lea rdx,vendor
; new stack frame
push rsi
push rdi
push rbx
push rbp
mov rbp,rsp
sub rsp,MAXCPUSTRING+16+8
mov [rbp-24],rdx
mov rsi,rax
assume rsi: ptr InstructionSet
; get the number of the highest valid function ID.
mov rdi,rsp
xor r8d,r8d
xor eax,eax
xor ecx,ecx
cpuid
mov [rsi].nIds,eax
.fors ( r9 = rdi: r8d <= [rsi].nIds: r8d++, r9 += 16 )
mov eax,r8d
xor ecx,ecx
cpuid
mov [r9+0x00],eax
mov [r9+0x04],ebx
mov [r9+0x08],ecx
mov [r9+0x0C],edx
.endf
; Capture vendor string
mov rdx,[rbp-24]
mov dword ptr [rdx+0x00],[rdi+0x04]
mov dword ptr [rdx+0x04],[rdi+0x0C]
mov dword ptr [rdx+0x08],[rdi+0x08]
mov dword ptr [rdx+0x0C],0
mov rax,[rdx]
mov rdx,[rdx+8]
mov rcx,"IeniuneG"
mov rbx,"letn"
.if ( rax == rcx && rdx == rbx )
mov [rsi].isIntel,TRUE
.else
mov rcx,"itnehtuA"
mov rbx,"DMAc"
.if ( rax == rcx && rdx == rbx )
mov [rsi].isAMD,TRUE
.endif
.endif
; load bitset with flags for function 0x00000001
.if ( [rsi].nIds >= 1 )
mov [rsi].f_1_RAX,[rdi+0x10][0x8]
.endif
; load bitset with flags for function 0x00000007
.if ( [rsi].nIds >= 7 )
mov [rsi].f_7_RAX,[rdi+0x70][0x4]
.endif
; get the number of the highest valid extended ID.
mov r8d,0x80000000
mov eax,r8d
xor ecx,ecx
cpuid
mov [rsi].nExIds,eax
.fors ( r9 = rdi: r8d <= [rsi].nExIds: r8d++, r9 += 16 )
mov eax,r8d
xor ecx,ecx
cpuid
mov [r9+0x00],eax
mov [r9+0x04],ebx
mov [r9+0x08],ecx
mov [r9+0x0C],edx
.endf
; load bitset with flags for function 0x80000001
.if ( [rsi].nExIds >= 0x80000001 )
mov [rsi].f_81_RAX,[rdi+0x10][0x8]
.endif
; Interpret CPU brand string if reported
.if ( [rsi].nExIds >= 0x80000004 )
lea rsi,[rdi+0x20]
mov rdi,[rbp-24]
sub rdi,64
mov ecx,3*16
rep movsb
.endif
assume rsi: nothing
leave
pop rbx
pop rdi
pop rsi
exitm<>
endm
InstructionSet_GetVendor macro this
lea rax,vendor
exitm<>
endm
InstructionSet_GetBrand macro this
lea rax,brand
exitm<>
endm
InstructionSetInline macro name, reg, bit, condition
InstructionSet_Get&name& macro this
ifnb <condition>
bt [this].InstructionSet.condition,0
sbb eax,eax
and eax,[this].InstructionSet.reg
and eax,1 shl bit
else
mov eax,[this].InstructionSet.reg
and eax,1 shl bit
endif
exitm<>
endm
endm
InstructionSetInline SSE3, f_1_ECX, 0
InstructionSetInline PCLMULQDQ, f_1_ECX, 1
InstructionSetInline MONITOR, f_1_ECX, 3
InstructionSetInline SSSE3, f_1_ECX, 9
InstructionSetInline FMA, f_1_ECX, 12
InstructionSetInline CMPXCHG16B, f_1_ECX, 13
InstructionSetInline SSE41, f_1_ECX, 19
InstructionSetInline SSE42, f_1_ECX, 20
InstructionSetInline MOVBE, f_1_ECX, 22
InstructionSetInline POPCNT, f_1_ECX, 23
InstructionSetInline AES, f_1_ECX, 25
InstructionSetInline XSAVE, f_1_ECX, 26
InstructionSetInline OSXSAVE, f_1_ECX, 27
InstructionSetInline AVX, f_1_ECX, 28
InstructionSetInline F16C, f_1_ECX, 29
InstructionSetInline RDRAND, f_1_ECX, 30
InstructionSetInline MSR, f_1_EDX, 5
InstructionSetInline CX8, f_1_EDX, 8
InstructionSetInline SEP, f_1_EDX, 11
InstructionSetInline CMOV, f_1_EDX, 15
InstructionSetInline CLFSH, f_1_EDX, 19
InstructionSetInline MMX, f_1_EDX, 23
InstructionSetInline FXSR, f_1_EDX, 24
InstructionSetInline SSE, f_1_EDX, 25
InstructionSetInline SSE2, f_1_EDX, 26
InstructionSetInline FSGSBASE, f_7_EBX, 0
InstructionSetInline BMI1, f_7_EBX, 3
InstructionSetInline HLE, f_7_EBX, 4, isIntel
InstructionSetInline AVX2, f_7_EBX, 5
InstructionSetInline BMI2, f_7_EBX, 8
InstructionSetInline ERMS, f_7_EBX, 9
InstructionSetInline INVPCID, f_7_EBX, 10
InstructionSetInline RTM, f_7_EBX, 11, isIntel
InstructionSetInline AVX512F, f_7_EBX, 16
InstructionSetInline RDSEED, f_7_EBX, 18
InstructionSetInline ADX, f_7_EBX, 19
InstructionSetInline AVX512PF, f_7_EBX, 26
InstructionSetInline AVX512ER, f_7_EBX, 27
InstructionSetInline AVX512CD, f_7_EBX, 28
InstructionSetInline SHA, f_7_EBX, 29
InstructionSetInline PREFETCHWT1, f_7_ECX, 0
InstructionSetInline LAHF, f_81_ECX, 0
InstructionSetInline LZCNT, f_81_ECX, 5, isIntel
InstructionSetInline ABM, f_81_ECX, 5, isAMD
InstructionSetInline SSE4a, f_81_ECX, 6, isAMD
InstructionSetInline XOP, f_81_ECX, 11, isAMD
InstructionSetInline TBM, f_81_ECX, 21, isAMD
InstructionSetInline SYSCALL, f_81_EDX, 11, isIntel
InstructionSetInline MMXEXT, f_81_EDX, 22, isAMD
InstructionSetInline RDTSCP, f_81_EDX, 27, isIntel
InstructionSetInline 3DNOWEXT, f_81_EDX, 30, isAMD
InstructionSetInline 3DNOW, f_81_EDX, 31, isAMD
The main code will then be the same and the INLINE (macro) versus EXTERN (proto) issue is controlled by the header.
include stdio.inc
include tchar.inc
include InstructionSet.inc
.data
count int_t 0
.code
support_message proc isa_feature:string_t, is_supported:int_t
.if is_supported
printf( "%-16s", isa_feature )
inc count
.endif
.if count == 4
mov count,0
printf( "\n\t" )
.endif
ret
support_message endp
main proc
.new cpu:InstructionSet()
printf(
"\n"
"CPU Information\n"
"\n"
" Vendor: %s\n", cpu.GetVendor() )
printf(
" Brand: %s\n"
"\n"
" Supported features:\n"
"\n\t", cpu.GetBrand() )
support_message("MMX", cpu.GetMMX())
support_message("SSE", cpu.GetSSE())
support_message("SSE2", cpu.GetSSE2())
support_message("SSE3", cpu.GetSSE3())
support_message("SSE4.1", cpu.GetSSE41())
support_message("SSE4.2", cpu.GetSSE42())
support_message("SSE4a", cpu.GetSSE4a())
support_message("SSSE3", cpu.GetSSSE3())
support_message("AVX", cpu.GetAVX())
support_message("AVX2", cpu.GetAVX2())
support_message("AVX512CD", cpu.GetAVX512CD())
support_message("AVX512ER", cpu.GetAVX512ER())
support_message("AVX512F", cpu.GetAVX512F())
support_message("AVX512PF", cpu.GetAVX512PF())
support_message("3DNOW", cpu.Get3DNOW())
support_message("3DNOWEXT", cpu.Get3DNOWEXT())
support_message("ABM", cpu.GetABM())
support_message("ADX", cpu.GetADX())
support_message("AES", cpu.GetAES())
support_message("BMI1", cpu.GetBMI1())
support_message("BMI2", cpu.GetBMI2())
support_message("CLFSH", cpu.GetCLFSH())
support_message("CMPXCHG16B", cpu.GetCMPXCHG16B())
support_message("CX8", cpu.GetCX8())
support_message("ERMS", cpu.GetERMS())
support_message("F16C", cpu.GetF16C())
support_message("FMA", cpu.GetFMA())
support_message("FSGSBASE", cpu.GetFSGSBASE())
support_message("FXSR", cpu.GetFXSR())
support_message("HLE", cpu.GetHLE())
support_message("INVPCID", cpu.GetINVPCID())
support_message("LAHF", cpu.GetLAHF())
support_message("LZCNT", cpu.GetLZCNT())
support_message("MMXEXT", cpu.GetMMXEXT())
support_message("MONITOR", cpu.GetMONITOR())
support_message("MOVBE", cpu.GetMOVBE())
support_message("MSR", cpu.GetMSR())
support_message("OSXSAVE", cpu.GetOSXSAVE())
support_message("PCLMULQDQ", cpu.GetPCLMULQDQ())
support_message("POPCNT", cpu.GetPOPCNT())
support_message("PREFETCHWT1", cpu.GetPREFETCHWT1())
support_message("RDRAND", cpu.GetRDRAND())
support_message("RDSEED", cpu.GetRDSEED())
support_message("RDTSCP", cpu.GetRDTSCP())
support_message("RTM", cpu.GetRTM())
support_message("SEP", cpu.GetSEP())
support_message("SHA", cpu.GetSHA())
support_message("SYSCALL", cpu.GetSYSCALL())
support_message("TBM", cpu.GetTBM())
support_message("XOP", cpu.GetXOP())
support_message("XSAVE", cpu.GetXSAVE())
printf( "\n\n" )
ret
main endp
end _tstart
CPU Information
Vendor: GenuineIntel
Brand: Intel(R) Core(TM) i5-6500T CPU @ 2.50GHz
Supported features:
MMX SSE SSE2 SSE3
SSE4.1 SSE4.2 SSSE3 AVX
AVX2 ADX AES BMI1
BMI2 CLFSH CMPXCHG16B CX8
ERMS F16C FMA FSGSBASE
FXSR HLE INVPCID LAHF
LZCNT MONITOR MOVBE MSR
OSXSAVE PCLMULQDQ POPCNT RDRAND
RDSEED RDTSCP RTM SEP
SYSCALL XSAVE
-
Added a few extensions to the macro call and a .template directive. This is the same as CLASS and COMDEF but without any default constructor or V-table pointer.
The limits for macros is max registers in FASTCALL (4) and SYSCALL (14). Arguments are sized up according to the proto type.
.template F fastcall
m_db db ?
m_dw dw ?
m_r4 real4 ?
Init proc :word, :byte, :real4
.ends
.template S syscall
m_db db ?
m_dw dw ?
m_dd dd ?
m_dq dq ?
m_r4 real4 ?
Init proc :word, :byte, :real4, :qword, :dword
.ends
The arguments may then be used directly without knowing the actual value.
F_Init macro this, a, b, c ; rcx, dx, r8b, xmm3
assume this:ptr F
mov [this].m_dw,a
mov [this].m_db,b
exitm<movss [this].m_r4,c>
endm
S_Init macro this, a, b, c, d, e ; rdi, si, dl, xmm0, rcx, r8d
assume this:ptr S
mov [this].m_dw,a
mov [this].m_db,b
movss [this].m_r4,c
mov [this].m_dq,d
exitm<mov [this].m_dd,e>
endm
This is just regular types.
local f:F, s:S
Arguments are parsed by the INVOKE directive.
f.Init(s.m_dw,s.m_db,3.0)
s.Init(f.m_dw,f.m_db,f.m_r4,4,5)
mov eax, 1077936128
movd xmm3, eax
mov r8b, byte ptr [rbp-20H]
mov dx, word ptr [rbp-1FH]
lea rcx, [rbp-9H]
mov r8d, 5
mov ecx, 4
movd xmm0, dword ptr [rbp-6H]
mov dl, byte ptr [rbp-9H]
mov si, word ptr [rbp-8H]
lea rdi, [rbp-20H]
-
Been playing with vector definitions and translated some of the header files:
- dvect.inc (https://github.com/nidud/asmc/blob/master/include/dvec.inc)
/*
* Definition of a C++ class interface to Intel(R) Pentium(R) 4 processor SSE2 intrinsics.
*
* File name : dvec.h class definitions
*
* Concept: A C++ abstraction of Intel(R) Pentium(R) 4 processor SSE2
* designed to improve programmer productivity. Speed and accuracy are
* sacrificed for utility. Facilitates an easy transition to compiler
* intrinsics or assembly language.
*
*/
- fvec.inc (https://github.com/nidud/asmc/blob/master/include/fvec.inc)
/*
* Definition of a C++ class interface to Streaming SIMD Extension intrinsics.
*
*
* File name : fvec.h Fvec class definitions
*
* Concept: A C++ abstraction of Streaming SIMD Extensions designed to improve
*
* programmer productivity. Speed and accuracy are sacrificed for utility.
*
* Facilitates an easy transition to compiler intrinsics
*
* or assembly language.
*
* F32vec4: 4 packed single precision
* 32-bit floating point numbers
*/
In addition a simple test file: dvec.asm (https://github.com/nidud/asmc/blob/master/source/test/dvec/test.asm)
To accomplish this a directive (.operator) is added and a new argument type (:ABS) for immediate values.
.OPERATOR [ name | OP ] [[ : args ]] [[ { ... } ]]
Arithmetic Operators
Operator Name Description
.operator + radd - Add
.operator - rsub - Subtract
.operator * rmul - Multiply
.operator / rdiv - Divide
.operator % rmod - Modulus
.operator ++ rinc - Increment
.operator -- rdec - Decrement
Bitwise Operators
.operator & rand - Binary AND Operator
.operator | ror - Binary OR Operator
.operator ^ rxor - Binary XOR Operator
.operator ~ rnot - Binary Ones Complement Operator
.operator &~ randn - Binary AND NOT Operator
.operator << rshl - Binary Left Shift Operator
.operator >> rshr - Binary Right Shift Operator
Assignment Operators
.operator = mequ - Simple assignment operator
.operator += madd - Add AND assignment operator
.operator -= msub - Subtract AND assignment operator
.operator *= mmul - Multiply AND assignment operator
.operator /= mdiv - Divide AND assignment operator
.operator %= mmod - Modulus AND assignment operator
.operator ~= mnot - Bitwise NOT assignment operator
.operator <<= mshl - Left shift AND assignment operator
.operator >>= mshr - Right shift AND assignment operator
.operator &= mand - Bitwise AND assignment operator
.operator &~= mandn - Bitwise AND NOT assignment operator
.operator ^= mxor - Bitwise exclusive OR and assignment operator
.operator |= mand - Bitwise inclusive OR and assignment operator
The size of the three first parameters are added to the name.
.operator = :qword, :dword { ; mequ84
mov [this],_1
retm<this>
}
Inline functions are rendered as macros with fixed argument names. The name may hold a register, a memory location or immediate value depending on calling convention.
class_mequ84 macro this, _1, _2
mov [this],_1
retm<this>
endm
Immediate values must be defined as :ABS.
.operator >> :abs { exitm<_mm_srli_epi64(xmm0, _1)> }
The classes are defined as templates and thus not a real class so the whole concept is a pure virtual construct. This means the size of the object is equal to the size of the vector used.
Example.
;; 1 element, a __m128i data type
.template M128
vec __m128i <>
.operator = :vec128_t {
exitm<_mm_store_ps([this], _1)>
}
.operator __m128i {
exitm<_mm_store_ps(xmm0, [this])>
}
.operator &= :vec128_t {
_mm_and_si128(xmm0, _1)
exitm<_mm_store_ps([this],xmm0)>
}
.operator |= :vec128_t {
_mm_or_si128(xmm0, _1)
exitm<_mm_store_ps([this],xmm0)>
}
.operator ^= :vec128_t {
_mm_xor_si128(xmm0, _1)
exitm<_mm_store_ps([this],xmm0)>
}
.operator & :vec128_t {
exitm<_mm_and_si128(xmm0, _1)>
}
.operator | :vec128_t {
exitm<_mm_or_si128(xmm0, _1)>
}
.operator ^ :vec128_t {
exitm<_mm_xor_si128(xmm0, _1)>
}
.operator andnot :vec128_t {
exitm<_mm_andnot_si128(xmm0, _1)>
}
.ends
Test case:
test_M128 proc v:ptr M128
assume rcx:ptr M128
[rcx].mequ16 (xmm1)
[rcx].__m128i ()
[rcx].mand16 (xmm1)
[rcx].mor16 (xmm1)
[rcx].mxor16 (xmm1)
[rcx].rand16 (xmm1)
[rcx].ror16 (xmm1)
[rcx].rxor16 (xmm1)
[rcx].andnot (xmm1)
ret
test_M128 endp
Code produced:
push rbp
mov rbp, rsp
sub rsp, 32
movaps xmmword ptr [rcx], xmm1
movaps xmm0, xmmword ptr [rcx]
pand xmm0, xmm1
movaps xmmword ptr [rcx], xmm0
por xmm0, xmm1
movaps xmmword ptr [rcx], xmm0
pxor xmm0, xmm1
movaps xmmword ptr [rcx], xmm0
pand xmm0, xmm1
por xmm0, xmm1
pxor xmm0, xmm1
pandn xmm0, xmm1
leave
ret
-
A sample from the GDI+ classes.
include windows.inc
include gdiplus.inc
include tchar.inc
.code
WndProc proc hWnd:HWND, message:UINT, wParam:WPARAM, lParam:LPARAM
.switch edx
.case WM_PAINT
.new ps:PAINTSTRUCT
BeginPaint(rcx, &ps)
.new G:Graphics()
.if G.FromHDC2(ps.hdc, rax) == Ok
.new P1:PointF(0.0, 0.0)
.new P2:PointF(300.0, 300.0)
.new B:LinearGradientBrush()
B.Create(&P1, &P2, Red, Blue)
G.FillRectangleI(&B, 0, 0, 300, 300)
B.Release()
.endif
G.Release()
EndPaint(hWnd, &ps)
.endc
.case WM_DESTROY
PostQuitMessage(0)
.endc
.default
.return DefWindowProc(rcx, edx, r8, r9)
.endsw
xor eax,eax
ret
WndProc endp
_tWinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE, lpCmdLine:LPTSTR, nShowCmd:SINT
local wc:WNDCLASSEX, msg:MSG, hwnd:HANDLE
xor eax,eax
mov wc.cbSize, WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.cbClsExtra, eax
mov wc.cbWndExtra, eax
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszMenuName, rax
mov wc.hInstance, hInstance
mov wc.lpfnWndProc, &WndProc
mov wc.lpszClassName, &@CStr("Gradient")
mov wc.hIcon, LoadIcon(0, IDI_APPLICATION)
mov wc.hIconSm, rax
mov wc.hCursor, LoadCursor(0, IDC_ARROW)
.ifd RegisterClassEx(&wc)
.if CreateWindowEx(0, "Gradient", "gdiplus.Graphics(Gradient)", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, 0)
mov hwnd,rax
;; Initialize GDI+.
.new gdiplus:ptr GdiPlus()
ShowWindow(hwnd, SW_SHOWNORMAL)
UpdateWindow(hwnd)
.while GetMessage(&msg,0,0,0)
TranslateMessage(&msg)
DispatchMessage(&msg)
.endw
gdiplus.Release()
mov rax,msg.wParam
.endif
.endif
ret
_tWinMain endp
end _tstart
-
Another one drawing a sphere.
source for the translated samples:
http://www.johnfindlay.plus.com/pellesc/GdiPlus/GdiPlus.html
include windows.inc
include gdiplus.inc
include tchar.inc
.code
WndProc proc hWnd:HWND, message:UINT, wParam:WPARAM, lParam:LPARAM
.switch edx
.case WM_PAINT
.new ps:PAINTSTRUCT
.new count:SINT
.new FullTranslucent:ARGB
.new g:Graphics()
BeginPaint(hWnd, &ps)
g.FromHDC(ps.hdc)
; Create a GraphicsPath object
.new p:GraphicsPath()
; Add an ellipse to the path
p.AddEllipse(200, 0, 200, 200)
; Create a path gradient based on the ellipse
.new b:PathGradientBrush(&p)
; Set the middle color of the path
b.SetCenterColor(ColorAlpha(Green, 180))
; Set the entire path boundary to Alpha Black using translucency
mov count,1
mov FullTranslucent,ColorAlpha(Black, 230)
b.SetSurroundColors(&FullTranslucent, &count)
; Draw the ellipse, keeping the exact coords we defined for the path
; We use AntiAlias drawing mode.
; To get a better antialising we enlarge area (+2 and -4).
g.SetSmoothingMode(SmoothingModeAntiAlias)
g.FillEllipseI(&b, 200 + 2, 0 + 2, 200 - 4, 200 - 4)
b.Release()
p.Release()
; Second Sphere
.new p:GraphicsPath()
p.AddEllipse(200, 100, 150, 150)
.new b:PathGradientBrush(&p)
b.SetCenterColor(ColorAlpha(Yellow, 180))
mov FullTranslucent,ColorAlpha(Red, 200)
mov count,1
b.SetSurroundColors(&FullTranslucent, &count)
g.FillEllipseI(&b, 200 + 2, 100 + 2, 150 - 4, 150 - 4)
b.Release()
p.Release()
g.Release()
EndPaint(hWnd, &ps)
.endc
.case WM_DESTROY
PostQuitMessage(0)
.endc
.case WM_CHAR
.gotosw(WM_DESTROY) .if r8d == VK_ESCAPE
.endc
.default
.return DefWindowProc(rcx, edx, r8, r9)
.endsw
xor eax,eax
ret
WndProc endp
_tWinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE, lpCmdLine:LPTSTR, nShowCmd:SINT
local wc:WNDCLASSEX, msg:MSG, hwnd:HANDLE
xor eax,eax
mov wc.cbSize, WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.cbClsExtra, eax
mov wc.cbWndExtra, eax
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszMenuName, rax
mov wc.hInstance, hInstance
mov wc.lpfnWndProc, &WndProc
mov wc.lpszClassName, &@CStr("Sphere")
mov wc.hIcon, LoadIcon(0, IDI_APPLICATION)
mov wc.hIconSm, rax
mov wc.hCursor, LoadCursor(0, IDC_ARROW)
.ifd RegisterClassEx(&wc)
.if CreateWindowEx(0, "Sphere", "gdiplus.Graphics(Sphere)", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 600, 400, NULL, NULL, hInstance, 0)
mov hwnd,rax
;; Initialize GDI+.
.new gdiplus:ptr GdiPlus()
ShowWindow(hwnd, SW_SHOWNORMAL)
UpdateWindow(hwnd)
.while GetMessage(&msg,0,0,0)
TranslateMessage(&msg)
DispatchMessage(&msg)
.endw
gdiplus.Release()
mov rax,msg.wParam
.endif
.endif
ret
_tWinMain endp
end _tstart
-
Some changes added to construction of classes.
- Constructors must now be added to the class and arguments definition for the type is ignored.
- Constructors added inside a class will not be a member of the class but defined as a PROTO type.
Example:
.template template
atom db ?
template proc :ptr
Release proc
.ends
template_template proto :ptr template, :ptr
template struct
atom db ?
template ends
templateVtbl struct
Release P$0001 ?
templateVtbl ends
A class or comdef will add a pointer:
.class class : public template
class proc :ptr
.ends
class_class proto :ptr class, :ptr
class struct 8
lpVtbl LPCLASSVtbl ?
template <>
class ends
classVtbl struct
templateVtbl <>
classVtbl ends
But only if lpVtbl don't exist:
.class class1 : public class
.operator class1 :ptr {
exitm<class(_1)>
}
.ends
class1_class1 proto :ptr class1, :ptr
class1_class1 macro this, _1
exitm<class(_1)>
endm
class1 struct 8
class <>
class1 ends
class1Vtbl struct
classVtbl <>
class1Vtbl ends
-
Some new changes.
In the GDI+ classes (and others) you have duplicated names for constructors and other functions. The inline macros may now have unused arguments as normally is the case in macros in general. However, these arguments needs to be declared as :ABS for operators.
Default values may then be used, and input with the same arg-count but different size may share the same name. A test case for drawing a Path using a Pen with default values and AddLine for both float and integer values.
This sample draws a person: https://www.codemag.com/article/0305031
(https://www.codemag.com/Article/Image/0305031/EggerBasic_Figure3.bmp)
Person.AddEllipse(23, 1, 14, 14)
Person.AddLine(18, 16, 42, 16)
Person.AddLine(50, 40, 44, 42)
Person.AddLine(38, 25, 37, 42)
Person.AddLine(45, 75, 37, 75)
Person.AddLine(30, 50, 23, 75)
Person.AddLine(16, 75, 23, 42)
Person.AddLine(22, 25, 16, 42)
Person.AddLine(10, 40, 18, 16)
The Pen constructor defaults to 1.0, and Path/Scale also have additional arguments.
.new Person:GraphicsPath()
.new p:Pen(Blue)
g.ScaleTransform(4.0, 4.0)
g.DrawPath(&p, &Person)
g.ResetTransform()
-
Very nice, and thanks for the inspiration (http://masm32.com/board/index.php?topic=6483.msg93685#msg93685) :thumbsup:
-
Some new additions to the MACRO/PROC merger.
proc :vararg
macro :vararg
...
.operator :vararg {
exitm<>
}
This combination is difficult to reason with so the macro needs the actual arg-list here. Invoke will skip loading arguments when the vararg start so in this case only RCX will be loaded. The macro receives the list but skips the ADDR prefix of the class, so this assumes a reference to a static class.
Pen proc :vararg
Pen_Pen macro this, _1, _2:=<1.0>, _3:=<0>
ifb <_1>
this.Pen0()
elseif typeof(_1) eq 2
this.Pen1(_1, _2, _3, rcx)
else
this.Pen2(_1, _2)
endif
lea rax,this
exitm<>
endm
typeof() will now accept an ADDR prefix so typeof(_1) --> typeof(addr p) = 8.
Note that this above is not a pointer here but a reference to the actual class. This means you dont need to save it between calls. The list is passed as a regular macro list.
echo this
for arg,<_1>
echo arg
endm
The GDI+ sample (https://github.com/nidud/asmc/blob/master/source/test/gdiplus/Person/test.asm) above is now written as a test for this concept, using static objects, and it cleans up very well. The code size for the GDI part is 910, total of 1389 byte. So, easy to use with minimum overhead.
Very nice, and thanks for the inspiration (http://masm32.com/board/index.php?topic=6483.msg93685#msg93685) :thumbsup:
:biggrin:
Nice to inspire so maybe consider using a modular library too?
The code should be less than 5K but is now above 50K.
-
Some changes made to inline functions and templates.
An inline function will not generate any stack frame unless needed. This in case the procedure do not allocate any stack.
stack proto :byte, :byte, :byte, :byte, :byte { exitm<> }
nostack proto :byte, :byte, :byte, :byte { exitm<> }
Operators added to the class structure follows a strict naming logic which is difficult to figure out by the assembler given you will need the proto type in order to do something meaningful in an expression.
A simplified template based on types will enable parsing of such expressions. The logic is to assign *this to the logical vector (AL to ZMM0) based on the type used.
float typedef real4
.template float vectorcall
.operator = :float {
movss this,_1
retm <this>
}
.operator + :float {
addss this,_1
retm <this>
}
.operator - :float {
subss this,_1
retm <this>
}
.operator / :float {
divss this,_1
retm <this>
}
.operator * :float {
mulss this,_1
retm <this>
}
.operator == :float {
comiss this,_1
retm <this>
}
.operator ++ { exitm<float::add(this, 1.0)> }
.operator -- { exitm<float::sub(this, 1.0)> }
.ends
There is no decoration here so the name is add.
float :: add ( xmm0, 1.0 )
The operator call skips *this.
float :: + ( 1.0 )
And the parsing is recursive.
float :: = ( xmm1 ) + ( a ) / ( b ) * ( c ) == ( 3.0 )
movss xmm0, xmm1
movd xmm1, dword ptr [a]
addss xmm0, xmm1
movd xmm1, dword ptr [_b]
divss xmm0, xmm1
movd xmm1, dword ptr [c]
mulss xmm0, xmm1
mov eax, 1077936128
movd xmm1, eax
comiss xmm0, xmm1
Using absolute values (:ABS) instead of float:
movss xmm0, xmm1
addss xmm0, dword ptr [a]
divss xmm0, dword ptr [_b]
mulss xmm0, dword ptr [c]
mov eax, 1077936128
movd xmm1, eax
comiss xmm0, xmm1
A small update.
The exitm<> is now automatically added if RETM or EXITM is not the last token and brackets are not needed for the recursive parsing unless there are more than one token.
Example
ostream typedef ptr
.template ostream
.operator << :ptr {}
.ends
cout equ <ostream::>
cout << "string" << "string2" << "string3"
The vector type is located in the CPU and PTR adapts to push size. The argument array therefor starts with _1 and this* added to the end with a default value of the vector.
ostream_shl proto :ptr
ostream_shl macro _1, this:=<rax>
exitm<>
endm
So RCX will be the first argument here (_1) and this* the return type.
* lea rcx, DS0000
* lea rcx, DS0001
* lea rcx, DS0002
-
So to continue with this construct the problem is the method selection based on arguments where multiple function share the same name. It's a difficult task, especially in assembler given registers may be used as input so you will at best be able to approximate based on count and size. Problems get solved in general by naming them so using types is the real solution here.
Types may be sorted internally by the assembler so I added a macro that names types as follows.
typeid( [ type, ] expression )
Immediate values:
imm_32 ?i32
imm_64 ?i64
imm_128 ?i128
imm_float ?flt
Registers:
reg_8 ?r8
reg_16
reg_32
reg_64
reg_128
reg_256
reg_512
Basic types:
mem_byte ?byte
mem_sbyte
mem_word
mem_sword
mem_real2
mem_dword
mem_sdword
mem_real4
mem_fword
mem_qword
mem_sqword
mem_real8
mem_tbyte
mem_real10
mem_oword
mem_real16
mem_yword
mem_zword
mem_proc
mem_near
mem_far
Basic pointers:
ptr_byte ?pbyte
ptr_sbyte
...
ptr_far
ptr_ptr
ptr_void
So this will enable type-based selection.
ostream typedef ptr
cout equ <ostream::>
.template ostream
.operator ptr_sbyte :ptr sbyte {
mov rax,_1
}
.operator ptr_word :ptr word {
mov rax,_1
}
.operator << :abs {
cout typeid(_1)(_1)
}
.ends
cout << "Ascii string" << ( L"Unicode string" )
In the regression test (https://github.com/nidud/asmc/blob/master/source/asmc/regress/src/bin/2.31.38.asm) the basic types are enumerated. User types expand in the same way:
types proto :vararg {
for arg,<this>
% echo typeid(arg)
endm
}
local rc:RECT
types( rc, addr rc )
mem_RECT
ptr_RECT
EDIT:
Optional argument.
.operator ostream?pRECT :ptr RECT {
mov eax,[_1].RECT.top
}
.operator ostream?psbyte :ptr sbyte {
mov rax,_1
}
.operator ostream?pword :ptr word {
mov rax,_1
}
.operator << :abs {
cout typeid(ostream, _1)(_1)
}
This may extend: typeid(ostream, _1)typeid(?, _2)(_1, _2)
-
Did some more testing using the GDI+ classes.
Constructors:
LinearGradientBrush(PointF*, PointF*, ARGB, ARGB)
LinearGradientBrush(Point*, Point*, ARGB, ARGB)
LinearGradientBrush(RectF*, ARGB, ARGB, LinearGradientMode)
LinearGradientBrush(Rect*, ARGB, ARGB, LinearGradientMode)
LinearGradientBrush(RectF*, ARGB, ARGB, REAL, BOOL isAngleScalable = FALSE)
LinearGradientBrush(Rect*, ARGB, ARGB, REAL, BOOL isAngleScalable = FALSE)
...
.new p:LinearGradientBrush(pPointF, pPointF, argb, argb)
.new p:LinearGradientBrush(pPoint, pPoint, argb, argb)
.new p:LinearGradientBrush(pRectF, argb, argb, 0)
.new p:LinearGradientBrush(pRect, argb, argb, 0)
.new p:LinearGradientBrush(pRectF, argb, argb, 0.0)
.new p:LinearGradientBrush(pRectF, argb, argb, 0.0, 0)
.new p:LinearGradientBrush(pRect, argb, argb, 0.0)
.new p:LinearGradientBrush(pRect, argb, argb, 0.0, 0)
Functions:
DrawLine(Pen*, REAL, REAL, REAL, REAL)
DrawLine(Pen*, PointF*, PointF*)
DrawLine(Pen*, INT, INT, INT, INT)
DrawLine(Pen*, Point*, Point*)
...
p.DrawLine(pPen, 0.0, 0.0, 0.0, 0.0)
p.DrawLine(pPen, pPointF, pPointF)
p.DrawLine(pPen, 0, 0, 0, 0)
p.DrawLine(pPen, ebx, ebx, ebx, ebx)
p.DrawLine(pPen, pPoint, pPoint)
Added some new Graphics samples (https://github.com/nidud/asmc/blob/master/source/test/gdiplus/Graphics/):
.new g:Graphics(hdc)
.new p:PointF(20.0, 20.0)
.new b:SolidBrush(Green)
.new f:Font(L"Arial", 16.0)
g.DrawString(L"Sample Text", 11, &f, &p, NULL, &b)
b.Release()
f.Release()
g.Release()
A simplified Bitmap sample:
.data
hBitmap HBITMAP 0
.code
WndProc proc hWnd:HWND, message:UINT, wParam:WPARAM, lParam:LPARAM
.switch edx
.case WM_CREATE
.new gdiplus:GdiPlus()
.new bitmap:Bitmap(L"image.png")
bitmap.GetHBITMAP(0, &hBitmap)
bitmap.Release()
gdiplus.Release()
.endc
Bitmap class constructors:
Bitmap(WCHAR*, BOOL = FALSE)
Bitmap(IStream*, BOOL = FALSE)
Bitmap(INT, INT, INT, PixelFormat, BYTE*)
Bitmap(INT, INT, PixelFormat = PixelFormat32bppARGB)
Bitmap(INT, INT, Graphics*)
Bitmap(IDirectDrawSurface7*)
Bitmap(BITMAPINFO*, VOID*)
Bitmap(HBITMAP, HPALETTE)
Bitmap(HICON)
Bitmap(HINSTANCE, WCHAR*)
-
The recursive parsing of types from the leftmost parameter is done with VARARG expansion so this (inline functions using VARARG) skips loading the first argument (this*) given it is hidden, to prevent reloading. The rest of the array is optional but should be virtual if possible until the final call.
Here the base is static and all TextureBrush() functions starts with the same argument (Image*) so this is loaded at the first level.
.operator TextureBrush :ptr Image, :abs=<WrapModeTile>, :abs, :vararg {
mov this.nativeBrush,NULL
mov rcx,rdx
ifnb <_3>
this.typeid(TextureBrush, _2)(rdx, _2, _3, _4)
else
GdipCreateTexture([rcx].Image.nativeImage, _2, addr this.nativeBrush)
endif
mov this.lastResult,eax
}
Here all the rest is loaded before the call.
.operator TextureBrush?pRectF :abs, :ptr RectF, :ptr ImageAttributes, :vararg {
xor edx,edx
.if r9
mov rdx,[r9].ImageAttributes.nativeImageAttr
.endif
GdipCreateTextureIA([rcx].Image.nativeImage, rdx,\
[r8].RectF.X,\
[r8].RectF.Y,\
[r8].RectF.Width,\
[r8].RectF.Height,\
addr this.nativeBrush)
}
This continues without loading.
.operator TextureBrush?i32 :abs, :abs, :abs, :vararg {
this.typeid(TextureBrush?i32, _3)(_1, _2, _3, _4)
}
Here the array (minus the first arg) is passed directly.
.operator TextureBrush?i32?flt :abs, :vararg {
GdipCreateTexture2([rcx].Image.nativeImage, _2, addr this.nativeBrush)
}
In this case real4 and r128 could be solved by EQU, but these calls do not generate any code.
.operator TextureBrush?i32?real4 :vararg {
this.TextureBrush?i32?flt(_1)
}
-
Most of the GDI+ classes are now somewhat functional and a few more working samples are made. There's a simple parsing test made for all these classes but live samples are needed to see how this plays out.
The Graphics test is done in WM_PAINT with a macro after BeginPaint().
OnPaint macro hdc
.new g:Graphics(hdc)
...
g.Release()
The size of these classes are rounded up to 16-byte and holds the native pointer and status value. The latter is 4 byte so a scratch value is added. This holds return values like float, BOOL, and other integer sized necessities.
.template Region
nativeRegion ptr_t ?
lastResult Status ?
scratch int_t ?
...
.operator GetHRGN :ptr Graphics, :vararg {
GdipGetRegionHRgn(this.nativeRegion, [rdx].Graphics.nativeGraphics, addr this.scratch)
this.SetStatus()
mov eax,this.scratch
}
This is all stack work so the bloat factor is relatively low. Inserting an image will create the following code:
.new i:Image(L"image.png")
g.DrawImage(&i, 100.0, 100.0, 60.0, 50.0, 150.0, 48.0, UnitPixel)
i.Release()
...
mov qword ptr [rbp-68H], 0 ; .new i:Image()
lea rdx, [DS0000]
mov rcx, rdx
lea rdx, [rbp-68H]
call GdipLoadImageFromFile
mov dword ptr [rbp-60H], eax ; status
lea rdx, [rbp-68H] ; g.DrawImage()
test rdx, rdx
jz ?_002
mov rdx, qword ptr [rdx]
?_002: mov dword ptr [rsp+40H], 2
mov dword ptr [rsp+38H], 1111490560
mov dword ptr [rsp+30H], 1125515264
mov dword ptr [rsp+28H], 1112014848
mov dword ptr [rsp+20H], 1114636288
movd xmm3, dword ptr [F0000]
movd xmm2, dword ptr [F0000]
mov rcx, qword ptr [rbp-58H]
call GdipDrawImagePointRect
test eax, eax ; update status on error..
cmove eax, dword ptr [rbp-50H]
mov dword ptr [rbp-50H], eax
mov rcx, qword ptr [rbp-68H] ; image.Release()
call GdipDisposeImage
A Bitmap is derived Image class and holds more diverse input, so the DrawImage() function apparently also accept a bitmap as input:
.new hIcon:HICON
.if ExtractIcon(hWnd, @CatStr(<!">, @Environ(HOMEDRIVE),<!">) "\\Windows\\regedit.exe", 2)
mov hIcon,rax
.new b:Bitmap(hIcon)
g.DrawImage(&b, 200.0, 100.0, 0.0, 0.0, 150.0, 48.0, UnitPixel)
b.Release()
DestroyIcon(hIcon)
.endif
There are some 20 more test samples attached.
-
Test case for the Metafile class.
https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-metafiles-about
Create a Metafile:
.case WM_PAINT
BeginPaint(rcx, &ps)
.new m:Metafile(L"MyDiskFile.emf", ps.hdc)
.new g:Graphics(&m)
g.SetSmoothingMode(SmoothingModeAntiAlias)
g.RotateTransform(30.0)
.new p:GraphicsPath()
p.AddEllipse(0, 0, 200, 100)
.new r:Region(&p)
g.SetClip(&r)
.new b:Pen(Blue)
g.DrawPath(&b, &p)
.for ebx = 0: ebx <= 300: ebx += 10
mov eax,300
sub eax,ebx
g.DrawLine(&b, 0, 0, eax, ebx)
.endf
g.Release()
m.Release()
EndPaint(hWnd, &ps)
View the file:
.case WM_PAINT
BeginPaint(rcx, &ps)
.new g:Graphics(rax)
.new i:Image(L"MyDiskFile.emf")
g.DrawImage(&i, 10, 10)
i.Release()
g.Release()
EndPaint(hWnd, &ps)
.endc
Should look something like this (with black background):
(https://docs.microsoft.com/en-us/windows/win32/gdiplus/images/aboutgdip05-art00.png)
-
Test case for the Effect class.
https://docs.microsoft.com/en-us/windows/win32/api/gdipluseffects/nl-gdipluseffects-effect
The Effect class serves as a base class for eleven classes that you can use to apply effects and adjustments to bitmaps. The following classes descend from Effect.
Blur
Sharpen
Tint
RedEyeCorrection
ColorMatrixEffect
ColorLUT
BrightnessContrast
HueSaturationLightness
ColorBalance
Levels
ColorCurve
.case WM_PAINT
BeginPaint(rcx, &ps)
.new g:Graphics(rax)
.new i:Image(L"Photograph.jpg")
.new r:RectF
.new m:Matrix(1.0, 0.0, 0.0, 1.0, 20.0, 280.0)
.new P:ColorBalanceParams
.new E:ColorBalance()
mov r.X,0.0
mov r.Y,0.0
i.GetWidth()
cvtsi2ss xmm0,eax
movss r.Width,xmm0
i.GetHeight()
cvtsi2ss xmm0,eax
movss r.Height,xmm0
;; Integer in the range -100 through 100 that specifies a change in the
;; amount of red in the image. If the value is 0, there is no change.
mov P.cyanRed,-60
;; Integer in the range -100 through 100 that specifies a change in the
;; amount of green in the image. If the value is 0, there is no change.
mov P.magentaGreen,40
;; Integer in the range -100 through 100 that specifies a change in the
;; amount of blue in the image. If the value is 0, there is no change.
mov P.yellowBlue,-50
E.SetParameters(&P)
;; Draw the image with no change.
g.DrawImage(&i, 20.0, 20.0, r.Width, r.Height)
;; Draw the image with applied Effect
g.DrawImage(&i, &r, &m, &E, NULL, UnitPixel)
i.Release()
g.Release()
EndPaint(hWnd, &ps)
.endc
-
The ImageAttributes class.
https://docs.microsoft.com/en-us/windows/win32/api/gdiplusimageattributes/nl-gdiplusimageattributes-imageattributes
An ImageAttributes object contains information about how bitmap and metafile colors are manipulated during rendering. An ImageAttributes object maintains several color-adjustment settings, including color-adjustment matrices, grayscale-adjustment matrices, gamma-correction values, color-map tables, and color-threshold values.
include windows.inc
include gdiplus.inc
include tchar.inc
.code
WndProc proc uses rsi hWnd:HWND, message:UINT, wParam:WPARAM, lParam:LPARAM
local ps:PAINTSTRUCT
.switch edx
.case WM_PAINT
BeginPaint(rcx, &ps)
.new g:Graphics(rax)
;; Create a palette that has four entries.
.new p:ptr ColorPalette(4)
mov rcx,rax
[rcx].ColorPalette.SetPalette(0, Aqua)
[rcx].ColorPalette.SetPalette(1, White)
[rcx].ColorPalette.SetPalette(2, Red)
[rcx].ColorPalette.SetPalette(3, Green)
;; Display the four palette colors with no adjustment.
.new b:SolidBrush(Black)
.for (esi = 0: esi < 4: ++esi)
p.GetPalette(rsi)
b.SetColor(eax)
imul r8d,esi,30
add r8d,20
g.FillRectangle(&b, r8d, 20, 20, 20)
.endf
;; Create a remap table that converts green to blue.
.new map:ColorMap
mov map.oldColor,Green
mov map.newColor,Blue
;; Create an ImageAttributes object, and set its bitmap remap table.
.new imAtt:ImageAttributes()
imAtt.SetRemapTable(1, &map, ColorAdjustTypeBitmap)
;; Adjust the palette.
imAtt.GetAdjustedPalette(p, ColorAdjustTypeBitmap)
;; Display the four palette colors after the adjustment.
.for (esi = 0: esi < 4: ++esi)
p.GetPalette(rsi)
b.SetColor(eax)
imul r8d,esi,30
add r8d,20
g.FillRectangle(&b, r8d, 50, 20, 20)
.endf
p.Release()
g.Release()
EndPaint(hWnd, &ps)
.endc
.case WM_DESTROY
PostQuitMessage(0)
.endc
.case WM_CHAR
.gotosw(WM_DESTROY) .if r8d == VK_ESCAPE
.endc
.default
.return DefWindowProc(rcx, edx, r8, r9)
.endsw
xor eax,eax
ret
WndProc endp
_tWinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE, lpCmdLine:LPTSTR, nShowCmd:SINT
local wc:WNDCLASSEX, msg:MSG, hwnd:HANDLE
xor eax,eax
mov wc.cbSize, WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.cbClsExtra, eax
mov wc.cbWndExtra, eax
mov wc.hbrBackground, COLOR_ACTIVEBORDER
mov wc.lpszMenuName, rax
mov wc.hIcon, rax
mov wc.hIconSm, rax
mov wc.hInstance, rcx
mov wc.lpfnWndProc, &WndProc
mov wc.lpszClassName, &@CStr("GetAdjustedPalette")
mov wc.hCursor, LoadCursor(0, IDC_ARROW)
.ifd RegisterClassEx(&wc)
.if CreateWindowEx(0, "GetAdjustedPalette", "GetAdjustedPalette", WS_OVERLAPPEDWINDOW,
100, 80, 400, 160, NULL, NULL, hInstance, 0)
mov hwnd,rax
;; Initialize GDI+.
.new gdiplus:GdiPlus()
ShowWindow(hwnd, SW_SHOWNORMAL)
UpdateWindow(hwnd)
.while GetMessage(&msg, 0, 0, 0)
TranslateMessage(&msg)
DispatchMessage(&msg)
.endw
gdiplus.Release()
mov rax,msg.wParam
.endif
.endif
ret
_tWinMain endp
end _tstart
-
New sample
WIC Viewer GDI+ (https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/wic/wicviewergdiplus)
This code sample shows a Windows application using WIC to decode an image file and GDI+ to render the image to the screen.
Files
WICViewerGDIPlus.inc (https://github.com/nidud/asmc/blob/master/source/test/wininc/wicviewergdiplus/WicViewerGdiPlus.inc): Header file that declares application class DemoApp interface
WICViewerGDIPlus.asm (https://github.com/nidud/asmc/blob/master/source/test/wininc/wicviewergdiplus/WicViewerGdiPlus.asm): Implementation of the application class interface
Building the Sample
To build the sample using the command prompt:
asmc64 -pe -ws -gui WICViewerGDIPlus.asm
Running the Sample
After the application is launched, load an image file through file open menu. Window resizing is supported.
Changes
The @CStr() macro now handles zero terminated strings. It also accept additional L"" on split strings but only the first one is valid.
DemoApp::LocateImageFile proc hWnd:HWND, pszFileName:LPWSTR, cchFileName:DWORD
local ofn:OPENFILENAME
mov word ptr [r8],0
ZeroMemory(&ofn, sizeof(ofn))
mov ofn.lStructSize, sizeof(ofn)
mov ofn.hwndOwner, hWnd
mov ofn.lpstrFilter, &@CStr(
L"All Image Files\0" L"*.bmp;*.dib;*.wdp;*.mdp;*.hdp;*.gif;*.png;*.jpg;*.jpeg;*.tif;*.ico\0"
L"Windows Bitmap\0" L"*.bmp;*.dib\0"
L"High Definition Photo\0" L"*.wdp;*.mdp;*.hdp\0"
L"Graphics Interchange Format\0" L"*.gif\0"
L"Portable Network Graphics\0" L"*.png\0"
L"JPEG File Interchange Format\0" L"*.jpg;*.jpeg\0"
L"Tiff File\0" L"*.tif\0"
L"Icon\0" L"*.ico\0"
L"All Files\0" L"*.*\0" )
mov ofn.lpstrFile, pszFileName
mov ofn.nMaxFile, cchFileName
mov ofn.lpstrTitle, &@CStr(L"Open Image")
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST
;; Display the Open dialog box.
GetOpenFileName(&ofn)
ret
DemoApp::LocateImageFile endp
-
WIC Animated Gif (https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/wic/wicanimatedgif)
WIC added support for reading/writing GIF metadata which is essential for rendering Animated GIFs.
This code sample is a simple Windows application that demonstrates decoding various frames in an GIF file, reading appropriate metadata for each frame, composing frames, and rendering the animation with Direct2D.
Files
sample.gif: sample gif file
WICAnimatedGif.inc (https://github.com/nidud/asmc/blob/master/source/test/wininc/wicanimatedgif/WICAnimatedGif.inc): Header file that declares application class DemoApp interface
WICAnimatedGif.asm (https://github.com/nidud/asmc/blob/master/source/test/wininc/wicanimatedgif/WicAnimatedGif.asm): Implementation of the application class interface
-
Hi nidud,
very cool examples, thanks for the work! :thumbsup:
-
IAmsiStream interface sample (https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/AmsiStream)
Demonstrates how to use the Antimalware Scan Interface to scan a stream.
The sample implements the
IAmsiStream interface (https://msdn.microsoft.com/en-us/library/windows/desktop/dn889589(v=vs.85).aspx) so that an antimalware provider can use it to scan the contents of a stream.
The sample demonstrates a stream where the data comes from a file
and a stream where the data comes from an in-memory buffer.
Files
AmsiStream.inc (https://github.com/nidud/asmc/blob/master/source/test/wininc/AmsiStream/AmsiStream.inc): Header file
AmsiStream.asm (https://github.com/nidud/asmc/blob/master/source/test/wininc/AmsiStream/AmsiStream.asm): Implementation
Note: this needs Windows 10
-
printf versus cout
Files
https://github.com/nidud/asmc/tree/master/source/test/class/cout
Using cout:
cout << "Ascii string" << endl
cout << ( L"Unicode string" ) << endl
cout << endl
cout << "signed char (-1): " << signed_char << endl
cout << "signed short (-1): " << signed_short << endl
cout << "signed int (-1): " << signed_int << endl
cout << "signed int64 (-1): " << signed_int64 << endl
cout << "unsigned char (-1): " << bl << endl
cout << "unsigned short (-1): " << bx << endl
cout << "unsigned int (-1): " << ebx << endl
cout << "unsigned int64 (-1): " << rbx << endl
cout << endl
Using printf:
printf( "Ascii string\n" )
wprintf( L"Unicode string\n\n" )
printf( "signed char (-1): %d\n", signed_char )
printf( "signed short (-1): %d\n", signed_short )
printf( "signed int (-1): %d\n", signed_int )
printf( "signed int64 (-1): %d\n", signed_int64 )
printf( "unsigned char (-1): %u\n", bl )
printf( "unsigned short (-1): %u\n", bx )
printf( "unsigned int (-1): %u\n", ebx )
printf( "unsigned int64 (-1): %llu\n\n", rbx )
cout.exe 7680 byte
stdio.exe 26624 byte
Note that this only apply to static linking using the Asmc LIBC-64.
-
Added assignment of struct members. This apply to structs with default constructors (templates) and works with the .new directive.
Examples using the RECT structure (https://github.com/nidud/asmc/blob/master/include/windef.inc#L257). This also allow direct assignment.
RECT()
RECT(1, 2, 3)
RECT() : left (100),
top (200),
right (640),
bottom (480)
foo( RECT(100, 200, 640, 480) )
.new RECT() : right(1)
.new rc:RECT() : right(2)
.new rp:ptr RECT() : right(3)
rp.Clear()
rp.Init(1, 2)
Note that the way a new RECT is constructed here there will only be one instance created (named __rc) except from rc.
-
Added a small bug-fix to the MacroLocals (https://github.com/nidud/asmc/commit/aee00fc9bea4e5c55ae3241560ad7c0d5f9b441d) counter. This is now reset between passes so the local label may now be used as a stable id.
Example
.class T
x dd ?
y dd ?
.operator T :abs=<0>, :abs=<0>, :vararg {
local n
.new n:T
mov n.x,_1
mov n.y,_2
lea rax,n
}
.ends
main proc
T()
T(1, 2)
ret
main endp
Label names:
??0000 T rbp - 0010
??0001 T rbp - 0020
Code produced:
push rbp
mov rbp, rsp
sub rsp, 64
mov dword ptr [rbp-8H], 0
mov dword ptr [rbp-4H], 0
lea rax, [rbp-10H]
mov dword ptr [rbp-18H], 1
mov dword ptr [rbp-14H], 2
lea rax, [rbp-20H]
leave
ret
-
Assignment of members based on input.
The main reason for these constructs is to optimize code based on type. The typeid operator (https://en.cppreference.com/w/cpp/language/typeid) is in C++ part of the standard library header typeinfo.
The Asmc implementation is similar and used as type identification for macro arguments. It's a text macro and may be used for direct branching in a class or a regular macro. This simplify and also gives a more accurate type definition than TYPE and OPATTR that is often used for the same purpose.
The preprocessor also handles real math so this opens up more possibilities for doing the same as more advanced compilers do. This also means that more code is moved to the header files but less needed on the user end.
As an example the ARGB color constants (https://github.com/nidud/asmc/blob/master/include/d2d1helper.inc) are defined as a DWORD and expanded to a D3DCOLORVALUE (https://github.com/nidud/asmc/blob/master/include/d2dBaseTypes.inc#L8) struct of 4 float values. The input will then in most cases be immediate values:
.new color:D3DCOLORVALUE(Black, 0.0)
The code for each float:
mov eax,_1
and eax,sc_redMask
shr eax,sc_redShift
cvtsi2ss xmm0,eax
divss xmm0,255.0
movss [rcx].D3DCOLORVALUE.r,xmm0
Or the simplified version:
%mov [rcx].D3DCOLORVALUE.r, @CatStr(%((_1 and sc_redMask) shr sc_redShift), <.0>) / 255.0
-
Inheritance (https://www.tutorialspoint.com/cplusplus/cpp_inheritance.htm) of objects has been limited to data members and functions. The inline functions in the base was therefor not expanded in derived classes.
This is now added by simple equations of the function names if defined.
include iostream
;; Base class
.class Shape
width int_t ?
height int_t ?
.operator setWidth :int_t {
mov [this].Shape.width,_1
}
.operator setHeight :int_t {
mov [this].Shape.height,_1
}
.ends
;; Derived class
.class Rectangle : public Shape
.operator getArea {
mov eax,[this].Shape.width
mul [this].Shape.height
}
.ends
.code
main proc
local Rect:Rectangle
Rect.setWidth(5)
Rect.setHeight(7)
;; Print the area of the object.
cout << "Total area: " << ( Rect.getArea() ) << endl
exit(0)
main endp
end main
Derived classes are added as anonymous structures for class and classVtbl.
* RectangleVtbl struct
* ShapeVtbl <>
* Rectangle_setWidth equ <Shape_setWidth>
* Rectangle_setHeight equ <Shape_setHeight>
...
-
New sample
WIC Viewer GDI+ (https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/wic/wicviewergdiplus)
This code sample shows a Windows application using WIC to decode an image file and GDI+ to render the image to the screen.
Files
WICViewerGDIPlus.inc (https://github.com/nidud/asmc/blob/master/source/test/wininc/wicviewergdiplus/WicViewerGdiPlus.inc): Header file that declares application class DemoApp interface
WICViewerGDIPlus.asm (https://github.com/nidud/asmc/blob/master/source/test/wininc/wicviewergdiplus/WicViewerGdiPlus.asm): Implementation of the application class interface
Building the Sample
To build the sample using the command prompt:
asmc64 -pe -ws -gui WICViewerGDIPlus.asm
Running the Sample
After the application is launched, load an image file through file open menu. Window resizing is supported.
Changes
The @CStr() macro now handles zero terminated strings. It also accept additional L"" on split strings but only the first one is valid.
DemoApp::LocateImageFile proc hWnd:HWND, pszFileName:LPWSTR, cchFileName:DWORD
local ofn:OPENFILENAME
mov word ptr [r8],0
ZeroMemory(&ofn, sizeof(ofn))
mov ofn.lStructSize, sizeof(ofn)
mov ofn.hwndOwner, hWnd
mov ofn.lpstrFilter, &@CStr(
L"All Image Files\0" L"*.bmp;*.dib;*.wdp;*.mdp;*.hdp;*.gif;*.png;*.jpg;*.jpeg;*.tif;*.ico\0"
L"Windows Bitmap\0" L"*.bmp;*.dib\0"
L"High Definition Photo\0" L"*.wdp;*.mdp;*.hdp\0"
L"Graphics Interchange Format\0" L"*.gif\0"
L"Portable Network Graphics\0" L"*.png\0"
L"JPEG File Interchange Format\0" L"*.jpg;*.jpeg\0"
L"Tiff File\0" L"*.tif\0"
L"Icon\0" L"*.ico\0"
L"All Files\0" L"*.*\0" )
mov ofn.lpstrFile, pszFileName
mov ofn.nMaxFile, cchFileName
mov ofn.lpstrTitle, &@CStr(L"Open Image")
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST
;; Display the Open dialog box.
GetOpenFileName(&ofn)
ret
DemoApp::LocateImageFile endp
That´s really really good. It can open webp files as well. Can you try making it to 32 bits, nidud ? And also making it open heic files too ?
-
Can you try making it to 32 bits, nidud ?
The samples here are primarily written to test inline functions and this is not fully supported in 32 bit yet.
It is however a Windows classic sample so it should be possible to build a 32 bit version from the original source:
https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/wic/wicviewergdiplus
And also making it open heic files too ?
I assume you may have to download some support for that? In any case it should be possible to add this to the list and see what happens.
-
Added two Direct2D samples from the Windows-classic samples.
GDI/Direct2D Interoperability Sample (https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/Direct2D/GdiInteropSample)
Direct2D Hello World Sample (https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/Direct2D/Direct2DHelloWorld)
These are similar to the WIC Animated Gif sample which also use Direct2D but they use the dwrite.inc (https://github.com/nidud/asmc/blob/master/include/dwrite.inc) header file as well. The interface in these headers are pure C++ so the translation may not always be correct for all methods, especially value returned by reference like the ID2D1RenderTarget::GetSize method:
D2D1_SIZE_F GetSize();
The struct will be passed in RDX here (the class in RCX) so the C version and the COMDEF entry should be something like this:
D2D1_SIZE_F *GetSize(D2D1_SIZE_F*);
GetSize proc :ptr D2D1_SIZE_F
Note that the data used here is not static but rather allocated by the class which is passed in the lpParam to CreateWindowEx(). The module handle (hInstance) however is a static global label created by the ORG directive. This has to be the first entry in the code segment to work:
.code
org -0x1000
__ImageBase label IMAGE_DOS_HEADER
org 0
mainCRTStartup proc
The class pointer is picked up in WndProc:
.if edx == WM_CREATE
mov r8,[r9].CREATESTRUCT.lpCreateParams
SetWindowLongPtrW(rcx, GWLP_USERDATA, PtrToUlong(r8))
mov result,1
.else
mov pDemoApp,GetWindowLongPtrW(rcx, GWLP_USERDATA)
The class itself is allocated on the stack in WinMain().
wWinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE, lpCmdLine:LPWSTR, nCmdShow:SINT
local vtable:DemoAppVtbl
;; Ignore the return value because we want to run the program even in the
;; unlikely event that HeapSetInformation fails.
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0)
CoInitialize(NULL)
.if (SUCCEEDED(eax))
.new app:DemoApp(&vtable)
app.Initialize()
.if (SUCCEEDED(eax))
app.RunMessageLoop()
.endif
CoUninitialize()
.endif
.return 0
wWinMain endp
Files
Direct2DHelloWorld.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/Direct2DHelloWorld/Direct2DHelloWorld.inc)
Direct2DHelloWorld.asm (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/Direct2DHelloWorld/Direct2DHelloWorld.asm)
GdiInteropSample.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GdiInteropSample/GdiInteropSample.inc)
GdiInteropSample.asm (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GdiInteropSample/GdiInteropSample.asm)
-
Some movement.
Direct2D Simple Path Animation Sample (https://docs.microsoft.com/en-us/samples/microsoft/windows-classic-samples/direct2dsimplepathanimation/)
Demonstrates how to create a simple path animation with Direct2D.
Files
Animation.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/SimplePathAnimationSample/Animation.inc) - Defines animation helpers.
SimplePathAnimationSample.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/SimplePathAnimationSample/SimplePathAnimationSample.inc) - The header file for the DemoApp class.
SimplePathAnimationSample.asm (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/SimplePathAnimationSample/SimplePathAnimationSample.asm) - Contains the application entry point and the implementation of the DemoApp class.
-
Simple Direct2D Application (https://docs.microsoft.com/en-us/samples/microsoft/windows-classic-samples/simple-direct-2d-application/)
Draws shapes, text, and images with Direct2D.
This sample loads two images. One from disk and one from resource. However, the image is rather big (770K) so the app just download the image from the MS site instead. The first run will therefor take some time to load.
Files
SimpleDirect2dApplication.asm (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/SimpleDirect2DApplication/SimpleDirect2dApplication.asm): Contains the application entry point and the implementation of the DemoApp class.
SimpleDirect2dApplication.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/SimpleDirect2DApplication/SimpleDirect2dApplication.inc): The header file for the DemoApp class.
Building the Sample
To build the sample using the command prompt:
asmc64 -pe -ws -gui SimpleDirect2dApplication.asm
-
Direct2D Geometry Realization Sample (https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/Direct2D/GeometryRealizationSample)
This sample shows how to use opacity masks and A8 targets to enhance performance for anti-aliased geometries. It also shows how to use meshes to enhance performance for aliased geometries.
Files
GeometryRealization.asm (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GeometryRealizationSample/GeometryRealization.asm): Implements the IGeometryRealization interface.
GeometryRealization.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GeometryRealizationSample/geometryrealization.inc): Defines the IGeometryRealization interface and related types.
GeometryRealizationSample.asm (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GeometryRealizationSample/GeometryRealizationSample.asm): Implements the DemoApp class, which creates a window and demonstrates the IGeometryRealization interface.
GeometryRealizationSample.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GeometryRealizationSample/GeometryRealizationSample.inc): Defines the the DemoApp class.
RingBuffer.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GeometryRealizationSample/ringbuffer.inc): The header file for the RingBuffer class. RingBuffer works like a standard array, except that when it fills up, data at the beginning is overwritten.
stdafx.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/GeometryRealizationSample/stdafx.inc): Defines standard system include files, or project specific include files that are used frequently, but are changed infrequently.
Building the Sample
Generate PE binary file:
asmc64 -pe -Cs -ws -gui GeometryRealizationSample.asm
Using LINKW:
asmc64 -q -ws -Cs GeometryRealization.asm
asmc64 -q -ws -Cs GeometryRealizationSample.asm
linkw system gui_64W file GeometryRealizationSample, GeometryRealization
Running the Sample
This sample uses the following controls:
Up Arrow: Increases the number of primitives rendered
Down Arrow: Decreases the number of primitives rendered
Spacebar: Pauses/resumes the animation.
Mouse Wheel: Zooms in and out.
'T' key: Toggles between hardware and software rendering.
'R' key: Toggles between rendering geometry with and without realizations.
'A' key: Toggles between rendering modes.
'S' key: Toggles Draw Stroke (outlines).
-
Direct2D List View Sample (https://docs.microsoft.com/en-us/samples/microsoft/windows-classic-samples/direct2dlistview/)
This sample shows how to create and use a bitmap atlas to create and animate a list of items. The list view loads the files and directories from the current directory with the icons. The directories and files in the list view can be sorted in the alphabetical and reverse alphabetical order.
Files
ListViewSample.asm (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/ListViewSample/ListViewSample.asm): Contains the application entry point and the implementation of the ListViewAppclass.
ListViewSample.inc (https://github.com/nidud/asmc/blob/master/source/test/Direct2D/ListViewSample/ListViewSample.inc): The header file for the ListViewApp class.
Running the Sample
Use the following keys to sort the list:
'A' key: sorts the list alphabetically.
'Z' Key: sorts the list in reverse alphabetical order.
'D' Key: sorts the list alphabetically with directories first.
Changes
Support for a second pointer added to INVOKE. This is added for accessing members of a class and static levels of functions in regular structures.
Example
.class ListViewApp
m_pD2DFactory LPID2D1Factory ?
m_pWICFactory LPIWICImagingFactory ?
m_pDWriteFactory LPIDWriteFactory ?
m_pRT LPID2D1HwndRenderTarget ?
m_pTextFormat LPIDWriteTextFormat ?
m_pBlackBrush LPID2D1SolidColorBrush ?
m_pBindContext LPIBindCtx ?
m_pBitmapAtlas LPID2D1Bitmap ?
These pointers may now be accessed directly:
.new dpiX:FLOAT
.new dpiY:FLOAT
this.m_pD2DFactory.GetDesktopDpi(&dpiX, &dpiY)
this.m_pTextFormat.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)
It adds a move to resolve the pointer using RAX but the arguments are sorted before the call so there is no risk of trashing registers.
* lea r8, dpiY
* lea rdx, dpiX
* mov rax, this
* mov rcx, [rax].ListViewApp.m_pD2DFactory
* mov rax, [rcx]
* call [rax].ID2D1FactoryVtbl.GetDesktopDpi
-
Added some more of the mf headers and a console sample.
mfapi.inc (https://github.com/nidud/asmc/blob/master/include/mfapi.inc)
mferror.inc (https://github.com/nidud/asmc/blob/master/include/mferror.inc)
mfidl.inc (https://github.com/nidud/asmc/blob/master/include/mfidl.inc)
mfobjects.inc (https://github.com/nidud/asmc/blob/master/include/mfobjects.inc)
mfreadwrite.inc (https://github.com/nidud/asmc/blob/master/include/mfreadwrite.inc)
mftransform.inc (https://github.com/nidud/asmc/blob/master/include/mftransform.inc)
AudioClip sample (https://docs.microsoft.com/en-us/samples/microsoft/windows-classic-samples/audioclip/)
Demonstrates using the IMFSourceReader API to get uncompressed media data from a media file. This sample application reads audio data from a media file and writes the uncompressed audio to a WAVE file.
Files
AudioClip.asm (https://github.com/nidud/asmc/blob/master/source/test/mf/AudioClip/AudioClip.asm)
-
Added mfplay.inc and a gui sample.
SimplePlay Sample (https://docs.microsoft.com/en-us/windows/win32/medfound/simpleplay-sample)
Shows how to play a media file using the MFPlay API.
APIs Demonstrated
This sample demonstrates the following Microsoft Media Foundation interfaces:
IMFPMediaItem (https://github.com/nidud/asmc/blob/master/include/mfplay.inc#L140)
IMFPMediaPlayer (https://github.com/nidud/asmc/blob/master/include/mfplay.inc#L89)
IMFPMediaPlayerCallback (https://github.com/nidud/asmc/blob/master/include/mfplay.inc#L321)
Files
SimplePlay.asm (https://github.com/nidud/asmc/blob/master/source/test/mf/SimplePlay/SimplePlay.asm)
-
Extended the .pragma comment() directive to include direct linker configurations. The option /manifestdependency is also added to LINKW.
This option produce a manifest in the name.exe.manifest format.
.pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
UI Automation simple provider sample
This sample application creates a simple custom control that implements Microsoft UI Automation provider interfaces. By implementing these interfaces, assistive technologies such as screen readers can programmatically access information about the control and make the control accessible to users with disabilities.
The control is a simple custom button that supports the Invoke control pattern. Clicking the button causes it to change color. You can also tab to the button and click it by pressing the spacebar.
-
Added some small changes to the link directive.
The text passed to the linker may have multiple lines:
.pragma comment(linker,
"/manifestdependency:\""
"type='win32' "
"name='Microsoft.Windows.Common-Controls' "
"version='6.0.0.0' "
"processorArchitecture='*' "
"publicKeyToken='6595b64144ccf1df' "
"language='*'"
"\""
)
Options stored in the .drectve section are separated by a space char so multiple options as above needs quotes.
Asmc adds the following to this section:
-export:
-import:
-entry:
-defaultlib:
LINKW support the above plus -manifestdependency:
The pragma directive should not include any spaces. Slash is allowed but converted to - in the output. This removes some warnings when LIBC is used with LINK.
.pragma comment(linker, "/merge:.CRT=.rdata")
-
Added assignment to .new name:type = value
.new bGotMemory:BOOL = FALSE
.new hDXGI:HINSTANCE = LoadLibrary( "dxgi.dll" )
A new sample using this feature from the DirectX-SDK-samples (https://github.com/walbourn/directx-sdk-samples/tree/master/VideoMemory).
VideoMemory
Display the amount of available memory.
Files
VideoMemory.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/VideoMemory/VideoMemory.asm)
VidMemViaD3D9.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/VideoMemory/VidMemViaD3D9.asm)
VidMemViaDDraw.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/VideoMemory/VidMemViaDDraw.asm)
VidMemViaDxDiag.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/VideoMemory/VidMemViaDxDiag.asm)
VidMemViaDXGI.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/VideoMemory/VidMemViaDXGI.asm)
VidMemViaWMI.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/VideoMemory/VidMemViaWMI.asm)
-
Added a Tutorial from the DirectX-SDK.
Files
- CreateDevice.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/Tutorials/CreateDevice.asm)
- Vertices.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/Tutorials/Vertices.asm)
- Matrices.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/Tutorials/Matrices.asm)
- Lights.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/Tutorials/Lights.asm)
- Textures.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/Tutorials/Textures.asm)
- Meshes.asm (https://github.com/nidud/asmc/blob/master/source/test/DirectX/Tutorials/Meshes.asm)