News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Virtual functions

Started by Demi, November 19, 2008, 06:56:45 PM

Previous topic - Next topic

Demi

   I been out of programming for awhile about 3 years and decided to write a game and engine. I been kicking this game idea around for about 8 years and I finally took the plunge. After playing around with C for a couple months, I turned back to assembly. Now doing it in assembly may seem like going in the wrong direction but MASM is just one awesome MACRO assembler. I have had MASM for going on 16 years and it is easier than all the ancient pre 60s compiler that requires you to spend more time formating than writing code. With 28 years behind me in assembly it just feels right.

   Anyway, I took out the MASM 6.0 disks and sat down to install. Well kind of a problem to install if you don't have a floppy drive on your machine. I took the disks to a friend to have him copy the disks to my pen drive and he informed me that the disks were ruined.

   I said "No problem, I'll go to Microsoft web site and get a copy. After all, I have a licence". I  searched and searched the site for MASM and found nothing so I did a web search. This site turned up and I downloaded the package. Although it does not have PWB it is still a great package. Glad to see that the MASM team is keeping it alive. I just wish everyone that programs could learn how easy MASM makes programming in assembly. MASM is one of Microsofts most powerful tools so I am kind of let down that they droped the ball. There are no books on subjects like this so I am making a book on these special things that kind of are unconventional. I would like to point out I have no offical education on programming and no school can teach this stuff. You have to learn it through experience.

I dug into the include files and looked at D3D9.INC. It is so shallow like nothing done with it. Of course it has the stubs to get the DLL loaded and some profiling. I got to thinking that maybe others are either just not interested or continue to write code and use virtual calls to the dlls or don't know how to access the power of c headers. I like structured programming myself and rather go a route that allows for C styled calls. If you understand a little about Jump tables and how they are implemented you can access any dll through the object pointers that are defined in the header file. Getting into the header file for D3D9 you will find the following:


typedef interface IDirect3D9                    IDirect3D9;
typedef interface IDirect3DDevice9              IDirect3DDevice9;
typedef interface IDirect3DStateBlock9          IDirect3DStateBlock9;
typedef interface IDirect3DVertexDeclaration9   
.........................
..............................
.............................. etc,.

There are a lot of interfaces in the D3D9.dll. These define interfaces  within the D3D9 DLL. Now if you look futher down you see the interface, There a tons of these interfaces all over the C headers you know and they allow access to the routines.

#define INTERFACE IDirect3D9

DECLARE_INTERFACE_(IDirect3D9, IUnknown)
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)(THIS) PURE;
    STDMETHOD_(ULONG,Release)(THIS) PURE;

    /*** IDirect3D9 methods ***/
    STDMETHOD(RegisterSoftwareDevice)(THIS_ void* pInitializeFunction) PURE;
    STDMETHOD_(UINT, GetAdapterCount)(THIS) PURE;
    STDMETHOD(GetAdapterIdentifier)(THIS_ UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER9* pIdentifier) PURE;
    STDMETHOD_(UINT, GetAdapterModeCount)(THIS_ UINT Adapter,D3DFORMAT Format) PURE;
    STDMETHOD(EnumAdapterModes)(THIS_ UINT Adapter,D3DFORMAT Format,UINT Mode,D3DDISPLAYMODE* pMode) PURE;
    STDMETHOD(GetAdapterDisplayMode)(THIS_ UINT Adapter,D3DDISPLAYMODE* pMode) PURE;
    STDMETHOD(CheckDeviceType)(THIS_ UINT Adapter,D3DDEVTYPE DevType,D3DFORMAT AdapterFormat,D3DFORMAT BackBufferFormat,BOOL bWindowed) PURE;
    STDMETHOD(CheckDeviceFormat)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat) PURE;
    STDMETHOD(CheckDeviceMultiSampleType)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType,DWORD* pQualityLevels) PURE;
    STDMETHOD(CheckDepthStencilMatch)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat) PURE;
    STDMETHOD(CheckDeviceFormatConversion)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SourceFormat,D3DFORMAT TargetFormat) PURE;
    STDMETHOD(GetDeviceCaps)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS9* pCaps) PURE;
    STDMETHOD_(HMONITOR, GetAdapterMonitor)(THIS_ UINT Adapter) PURE;
    STDMETHOD(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) PURE;
   
    #ifdef D3D_DEBUG_INFO
    LPCWSTR Version;
    #endif
};

Did you notice that all the calls are defined for you? BUT these are not conventional calls. These are pointers within the structure of the *LPDIRECT3D9 which is accessed when you INVOKE Direct3DCreate9, SDK_VERSION which is defined in the D3D9.LIB

INVOKE Direct3DCreate9,32  ;sdk_version
mov g_pD3D,eax         ;save the pointer

The INVOKE Direct3DCreate9 returns a pointer to the INTERFACE of IDirect3D9. That is a pointer to a jump table which jumps to the routine and the routine does the return.

OK so you wonder if you can get the arguments to the routine, will it work? YES!

So how do you get those arguments to the routine?
If you look futher in the header you find...

typedef struct IDirect3D9 *LPDIRECT3D9, *PDIRECT3D9;

#if !defined(__cplusplus) || defined(CINTERFACE)
#define IDirect3D9_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b)
#define IDirect3D9_AddRef(p) (p)->lpVtbl->AddRef(p)
#define IDirect3D9_Release(p) (p)->lpVtbl->Release(p)
#define IDirect3D9_RegisterSoftwareDevice(p,a) (p)->lpVtbl->RegisterSoftwareDevice(p,a)
#define IDirect3D9_GetAdapterCount(p) (p)->lpVtbl->GetAdapterCount(p)
#define IDirect3D9_GetAdapterIdentifier(p,a,b,c) (p)->lpVtbl->GetAdapterIdentifier(p,a,b,c)
#define IDirect3D9_GetAdapterModeCount(p,a,b) (p)->lpVtbl->GetAdapterModeCount(p,a,b)
#define IDirect3D9_EnumAdapterModes(p,a,b,c,d) (p)->lpVtbl->EnumAdapterModes(p,a,b,c,d)
#define IDirect3D9_GetAdapterDisplayMode(p,a,b) (p)->lpVtbl->GetAdapterDisplayMode(p,a,b)
#define IDirect3D9_CheckDeviceType(p,a,b,c,d,e) (p)->lpVtbl->CheckDeviceType(p,a,b,c,d,e)
#define IDirect3D9_CheckDeviceFormat(p,a,b,c,d,e,f) (p)->lpVtbl->CheckDeviceFormat(p,a,b,c,d,e,f)
#define IDirect3D9_CheckDeviceMultiSampleType(p,a,b,c,d,e,f) (p)->lpVtbl->CheckDeviceMultiSampleType(p,a,b,c,d,e,f)
#define IDirect3D9_CheckDepthStencilMatch(p,a,b,c,d,e) (p)->lpVtbl->CheckDepthStencilMatch(p,a,b,c,d,e)
#define IDirect3D9_CheckDeviceFormatConversion(p,a,b,c,d) (p)->lpVtbl->CheckDeviceFormatConversion(p,a,b,c,d)
#define IDirect3D9_GetDeviceCaps(p,a,b,c) (p)->lpVtbl->GetDeviceCaps(p,a,b,c)
#define IDirect3D9_GetAdapterMonitor(p,a) (p)->lpVtbl->GetAdapterMonitor(p,a)
#define IDirect3D9_CreateDevice(p,a,b,c,d,e,f) (p)->lpVtbl->CreateDevice(p,a,b,c,d,e,f)


This tells you the order to make the calls

lpVtbl is the Long pointer to the Virtual Table

so you know..

You make a stack frame just like the INVOKE EXCEPT you do not make a call because the interface is not defined in a include file as a procedure. You make a call to the POINTER. Well since you make a call to a pointer and you process the arguments, you have to put them in the stack YOURSELF.

OK sound complicated. It really isn't and you can build macros that make it look EXACTLY like C code

Now lets take the biggie one here and break it down

    STDMETHOD(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) PURE;

STDMETHOD is the standard calling convention so if you were to invoke you would pass the stack UINT,  D3DDEVTYPE, HWND, DWORD , D3DPRESENT_PARAMETERS*, IDirect3DDevice9**

push uint
push d3ddevtype
mov eax,hwnd
push eax
push DWORD
lea eax,d3dpresent_parameters
push eax
mov eax,offset Idirect3ddevice9
push eax
call CreateDevice

emm.. well you can't call it.

you need to build your own stack frame and call the pointer

Since you are emulating the stack you have to build the stackframe backwards.

mov eax,offset IDirect3DDevice9   ;a pointer and will be filled in by
                  ;the engine and point to another
                  ;interface. Look at the list.
mov dword ptr [esp+24],eax               (arg6*4)
lea eax, [d3dpresent_parameters]
mov dword ptr [esp+20],eax               (arg5*4)
mov dword ptr [esp+16],DWORD                  (arg4*4)
mov eax,hwnd
mov dword ptr [esp+12],eax               (arg3*4)
mov dword ptr [esp+8],d3ddevtype            (arg2*4)
mov dword ptr [esp+4],uint               (arg1*4)

Well we are still missing one and guess what that is

the pointer!

g_pD3D

BUT this pointer is special in that it points to the jump table defined in DECLARE interface STDMETHOD if you count them you get 17 but it is zero based or 0 to 16 so we count down and get 16 as the interface number.

16*4 = 64. Four bytes make a dword and the jump table is dwords. SO we do this:

mov eax,g_pD3D
mov dword ptr[esp],eax
mov edx,dword ptr[eax]
call [edx+64]
sub esp,28    ;you have to clean the stack for these kind of calls.

well that was a lot of work just to get the interface and I am sure if you program you don't want to keep typin this every time so we make a macro in line 16

we call it IDirect3D9_CreateDevice MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ , e:REQ , f:REQ

      mov eax,&p&
      mov dword ptr[esp],eax
      mov edx,dword ptr[eax]
      mov eax,offset &f&
      mov dword ptr [esp+24],eax
      lea eax, [&e&]
      mov dword ptr [esp+20],eax
      mov dword ptr [esp+16],&d&
      mov eax,&c&
      mov dword ptr [esp+12],eax
      mov dword ptr [esp+8],&b&
      mov dword ptr [esp+4],&a&
      call [edx+64]
      sub esp,28

ENDM

Then when we want to get the device we do this

IDirect3D9_CreateDevice g_pD3D, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface

In C it looks like this

g_pD3D-->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);

YAH! making it easier arn't we?

Well lets take this a bit futher and unleash some of MASM's true power

I don't want to edit every line with all that code so lets make a couple macros to make it even easier.

    sproc macro p:req      ;standard procedure

        mov eax,&p&
        mov dword ptr[esp],eax
        mov edx,dword ptr[eax]

    ENDM

    mcall MACRO c:REQ, s:REQ    ;memory access call

       if c EQ 0
           call dword ptr[edx]
       else
           call dword ptr[edx+c]
       endif
       sub esp,s

    ENDM

    const MACRO a:REQ, c:REQ     ;constant
        if c EQ 0
            mov dword ptr[esp],&a&
        else
            mov dword ptr[esp+c],&a&
        endif
    ENDM

    adptr MACRO a:REQ, c:REQ     ;address pointer
        mov eax,&a&
        stnct c
    ENDM

    ppptr MACRO a:REQ, c:REQ     ;offset pointer
        mov  eax,offset &a&
        stnct c
    ENDM

    poter MACRO a:REQ, c:REQ    ;address access pointer
        lea eax,[&a&]
        stnct c
    ENDM

    stnct MACRO c:req         ;makes repeated counts easier
        if c EQ 0
            mov dword ptr[esp],eax
        else
            mov dword ptr[esp+c],eax
        endif
    ENDM

Since every line is a pointer and it is all standardized we can make a few comments in each macro and it will make the entire process even easier

so now we take the macro and edit it like this.

   IDirect3D9_CreateDevice MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ , e:REQ , f:REQ
   ;(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) PURE;
   sproc &p&
   ppptr &f&,24
   poter &e&,20
   const &d&,16
   adptr &c&,12
   const &b&,8
   const &a&,4
   mcall 64,28

   ENDM
well since we now have it standarized we can automate the whole process!

YAH!!!

;---------------------------------------------------
;              Generated macro file                 
;   Making function calls easier in assembly. - DEMI
;---------------------------------------------------

    sproc macro p:req

        mov eax,&p&
        mov dword ptr[esp],eax
        mov edx,dword ptr[eax]

    ENDM

    mcall MACRO c:REQ, s:REQ

       if c EQ 0
           call dword ptr[edx]
       else
           call dword ptr[edx+c]
       endif
       sub esp,s

    ENDM

    const MACRO a:REQ, c:REQ
        if c EQ 0
            mov dword ptr[esp],&a&
        else
            mov dword ptr[esp+c],&a&
        endif
    ENDM

    adptr MACRO a:REQ, c:REQ
        mov eax,&a&
        stnct c
    ENDM

    ppptr MACRO a:REQ, c:REQ
        mov  eax,offset &a&
        stnct c
    ENDM

    poter MACRO a:REQ, c:REQ
        lea eax,[&a&]
        stnct c
    ENDM

    stnct MACRO c:req
        if c EQ 0
            mov dword ptr[esp],eax
        else
            mov dword ptr[esp+c],eax
        endif
    ENDM

   IDirect3D9_QueryInterface MACRO p:REQ, a:REQ , b:REQ
   ;(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE;
   sproc &p&
   ppptr &b&,8
   const &a&,4
   mcall 0,12

   ENDM

   IDirect3D9_AddRef MACRO p:REQ
   ; <-- Returns a value --> (ULONG,AddRef)(THIS) PURE;
   sproc &p&
   mcall 4,4

   ENDM

   IDirect3D9_Release MACRO p:REQ
   ; <-- Returns a value --> (ULONG,Release)(THIS) PURE;
   sproc &p&
   mcall 8,4

   ENDM

   IDirect3D9_RegisterSoftwareDevice MACRO p:REQ, a:REQ
   ;(RegisterSoftwareDevice)(THIS_ void* pInitializeFunction) PURE;
   sproc &p&
   adptr &a&,4
   mcall 12,8

   ENDM

   IDirect3D9_GetAdapterCount MACRO p:REQ
   ; <-- Returns a value --> (UINT, GetAdapterCount)(THIS) PURE;
   sproc &p&
   mcall 16,4

   ENDM

   IDirect3D9_GetAdapterIdentifier MACRO p:REQ, a:REQ , b:REQ , c:REQ
   ;(GetAdapterIdentifier)(THIS_ UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER9* pIdentifier) PURE;
   sproc &p&
   adptr &c&,12
   const &b&,8
   const &a&,4
   mcall 20,16

   ENDM

   IDirect3D9_GetAdapterModeCount MACRO p:REQ, a:REQ , b:REQ
   ; <-- Returns a value --> (UINT, GetAdapterModeCount)(THIS_ UINT Adapter,D3DFORMAT Format) PURE;
   sproc &p&
   const &b&,8
   const &a&,4
   mcall 24,12

   ENDM

   IDirect3D9_EnumAdapterModes MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ
   ;(EnumAdapterModes)(THIS_ UINT Adapter,D3DFORMAT Format,UINT Mode,D3DDISPLAYMODE* pMode) PURE;
   sproc &p&
   adptr &d&,16
   const &c&,12
   const &b&,8
   const &a&,4
   mcall 28,20

   ENDM

   IDirect3D9_GetAdapterDisplayMode MACRO p:REQ, a:REQ , b:REQ
   ;(GetAdapterDisplayMode)(THIS_ UINT Adapter,D3DDISPLAYMODE* pMode) PURE;
   sproc &p&
   adptr &b&,8
   const &a&,4
   mcall 32,12

   ENDM

   IDirect3D9_CheckDeviceType MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ , e:REQ
   ;(CheckDeviceType)(THIS_ UINT Adapter,D3DDEVTYPE DevType,D3DFORMAT AdapterFormat,D3DFORMAT BackBufferFormat,BOOL bWindowed) PURE;
   sproc &p&
   const &e&,20
   const &d&,16
   const &c&,12
   const &b&,8
   const &a&,4
   mcall 36,24

   ENDM

   IDirect3D9_CheckDeviceFormat MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ , e:REQ , f:REQ
   ;(CheckDeviceFormat)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat) PURE;
   sproc &p&
   const &f&,24
   const &e&,20
   const &d&,16
   const &c&,12
   const &b&,8
   const &a&,4
   mcall 40,28

   ENDM

   IDirect3D9_CheckDeviceMultiSampleType MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ , e:REQ , f:REQ
   ;(CheckDeviceMultiSampleType)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType,DWORD* pQualityLevels) PURE;
   sproc &p&
   adptr &f&,24
   const &e&,20
   const &d&,16
   const &c&,12
   const &b&,8
   const &a&,4
   mcall 44,28

   ENDM

   IDirect3D9_CheckDepthStencilMatch MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ , e:REQ
   ;(CheckDepthStencilMatch)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat) PURE;
   sproc &p&
   const &e&,20
   const &d&,16
   const &c&,12
   const &b&,8
   const &a&,4
   mcall 48,24

   ENDM

   IDirect3D9_CheckDeviceFormatConversion MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ
   ;(CheckDeviceFormatConversion)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SourceFormat,D3DFORMAT TargetFormat) PURE;
   sproc &p&
   const &d&,16
   const &c&,12
   const &b&,8
   const &a&,4
   mcall 52,20

   ENDM

   IDirect3D9_GetDeviceCaps MACRO p:REQ, a:REQ , b:REQ , c:REQ
   ;(GetDeviceCaps)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS9* pCaps) PURE;
   sproc &p&
   adptr &c&,12
   const &b&,8
   const &a&,4
   mcall 56,16

   ENDM

   IDirect3D9_GetAdapterMonitor MACRO p:REQ, a:REQ
   ; <-- Returns a value --> (HMONITOR, GetAdapterMonitor)(THIS_ UINT Adapter) PURE;
   sproc &p&
   const &a&,4
   mcall 60,8

   ENDM

   IDirect3D9_CreateDevice MACRO p:REQ, a:REQ , b:REQ , c:REQ , d:REQ , e:REQ , f:REQ
   ;(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) PURE;
   sproc &p&
   ppptr &f&,24
   poter &e&,20
   const &d&,16
   adptr &c&,12
   const &b&,8
   const &a&,4
   mcall 64,28

   ENDM

Demi

If anyone is interested in taking the test bed code and making it mature give me a shout. I have to many things going to maintain another program.

Mark Jones

Nice work Demi, and welcome back. Curious, did you see the OpenGL sub-forum?

http://www.masm32.com/board/index.php?board=41.0
"To deny our impulses... foolish; to revel in them, chaos." MCJ 2003.08

Demi

#3
Quote from: Mark Jones on November 20, 2008, 06:13:13 AM
Nice work Demi, and welcome back. Curious, did you see the OpenGL sub-forum?

http://www.masm32.com/board/index.php?board=41.0

No I did not see them. Thanks for pointing me in that direction.  I been doing the SDK stuff and figuring out how to access the DLL. I folowed the SDK examples and the code looks almost like C.

I removed the code and am putting it in the OpenGL forum.


[attachment deleted by admin]