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

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #15 on: December 17, 2019, 11:55:14 PM »
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.
Code: [Select]
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.
Code: [Select]
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

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #16 on: December 29, 2019, 04:05:59 AM »
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]

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #17 on: February 21, 2020, 08:55:04 AM »
Been playing with vector definitions and translated some of the header files:

- dvect.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

/*
 *  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

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                                             

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #18 on: May 08, 2020, 06:36:13 AM »
A sample from the GDI+ classes.
Code: [Select]
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

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #19 on: May 08, 2020, 06:47:13 AM »
Another one drawing a sphere.

source for the translated samples:
http://www.johnfindlay.plus.com/pellesc/GdiPlus/GdiPlus.html
Code: [Select]
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
« Last Edit: May 23, 2020, 11:07:03 AM by nidud »

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #20 on: May 13, 2020, 11:29:44 PM »
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

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #21 on: May 23, 2020, 10:48:58 AM »
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



        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()

jj2007

  • Member
  • *****
  • Posts: 10636
  • Assembler is fun ;-)
    • MasmBasic
Re: Object-oriented programming (OOP) in Asmc
« Reply #22 on: May 23, 2020, 08:19:14 PM »

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #23 on: May 26, 2020, 06:16:01 AM »
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 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 :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.
« Last Edit: May 26, 2020, 07:31:28 AM by nidud »

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #24 on: May 29, 2020, 04:04:14 AM »
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

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #25 on: May 30, 2020, 02:21:32 AM »
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 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)
« Last Edit: May 30, 2020, 10:51:01 AM by nidud »

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #26 on: June 04, 2020, 12:05:16 AM »
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:

   .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*)

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #27 on: June 05, 2020, 08:33:33 AM »
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)
        }

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #28 on: June 05, 2020, 09:40:10 AM »
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.

nidud

  • Member
  • *****
  • Posts: 1989
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #29 on: June 06, 2020, 10:17:25 AM »
Test case for the Metafile class.

https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-metafiles-about

Create a Metafile:
Code: [Select]
    .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:
Code: [Select]
    .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):