Author Topic: Object-oriented programming (OOP) in Asmc  (Read 2555 times)

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Object-oriented programming (OOP) in Asmc
« 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:
Code: [Select]
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.
« Last Edit: April 30, 2019, 09:09:22 PM by nidud »

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #1 on: March 29, 2018, 06:17:15 AM »
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.

Code: [Select]
;
; 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
« Last Edit: April 30, 2019, 09:11:21 PM by nidud »

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #2 on: April 03, 2018, 10:44:18 PM »
.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:
Code: [Select]
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:
Code: [Select]
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

« Last Edit: April 30, 2019, 09:21:29 PM by nidud »

Greenhorn

  • Member
  • **
  • Posts: 98
Re: Object-oriented programming (OOP) in Asmc
« Reply #3 on: April 16, 2018, 06:02:10 AM »
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"
Code: [Select]
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"
Code: [Select]
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"
Code: [Select]
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
Code: [Select]
test.asm(14) : error A2008: syntax error : p
This is because asmc assumes in the call function OpenFile from winbase.inc.


Regards
Greenhorn

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #4 on: April 16, 2018, 09:03:53 PM »
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.
« Last Edit: April 30, 2019, 09:23:19 PM by nidud »

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #5 on: June 25, 2018, 02:11:59 AM »
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

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #6 on: August 09, 2018, 10:18:47 PM »
Added some more samples to classdef directory.

Using RCX as mention above cleans up very well. In the TConsole sample RCX is used for all calls to the class. This is a static class auto installed using .pragma(init()), so the only external to the module is the pointer (Console):
Code: [Select]
include tdialog.inc

    .code

    assume rcx: ptr TConsole

main proc

  local cursor:CURSOR

    mov rcx,Console
    [rcx].CursorGet(&cursor)
    [rcx].Maxconsole()
    [rcx].Getch()
    [rcx].Moveconsole(100, 100)
    [rcx].Setconsole(50, 8)
    [rcx].Setattrib(0x1B)
    [rcx].Clrconsole()
    [rcx].CPrintf(10, 2, "TConsole::CPrintf()\n TConsole::CPuta()")
    [rcx].CPuta(10, 3, 19, 0x70)
    [rcx].Update()
    [rcx].CursorOff()
    [rcx].Getch()
    [rcx].Setattrib(0x07)
    [rcx].Clrconsole()
    [rcx].Update()
    [rcx].CursorSet(&cursor)
    [rcx].Setconsole(80, 25)
    ret

main endp

    end
« Last Edit: April 30, 2019, 09:36:19 PM by nidud »

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Using System V methods in a class
« Reply #7 on: August 29, 2018, 02:42:42 AM »
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
Code: [Select]
.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: [Select]
    .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():
Code: [Select]
    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( sizeof(InstructionSet) + sizeof(InstructionSetVtbl) )

        assume rsi:ptr InstructionSet

        mov rsi,rax
        mov rdi,rax
        xor eax,eax
        mov ecx,sizeof(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.
Code: [Select]
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
« Last Edit: April 30, 2019, 09:42:32 PM by nidud »

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #8 on: February 18, 2019, 10:46:51 PM »
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.
Code: [Select]
bar proto

.class foo

    bar proc

    .ends

main proc

 local p:ptr foo

    p.bar()
    ret

main endp
« Last Edit: April 30, 2019, 09:43:15 PM by nidud »

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #9 on: March 29, 2019, 09:42:41 PM »
A full sample that demonstrates the use of COM form the Windows Software Development Kit (SDK)\...\Samples\WinUI\Shell\AppPlatform\FileOperations. The sample uses the strsafe.lib and uuid.lib (updated).

Code: [Select]

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

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #10 on: April 04, 2019, 02:06:26 AM »
Added another sample 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.

Code: [Select]
        .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
« Last Edit: April 30, 2019, 09:44:49 PM by nidud »

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #11 on: April 22, 2019, 07:59:10 AM »
Added a DLL COM sample using CoGetClassObject().

This needs to be manually installed with a full path to the dll, and two new GUIDs 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 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

nidud

  • Member
  • *****
  • Posts: 1715
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #12 on: April 25, 2019, 06:44:42 AM »
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.

Code: [Select]
.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.

Code: [Select]
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
« Last Edit: April 30, 2019, 09:52:04 PM by nidud »