How to create IUnknown Interface (Example)

Share your advanced PureBasic knowledge/code with the community.
User avatar
mk-soft
Always Here
Always Here
Posts: 5313
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

How to create IUnknown Interface (Example)

Post by mk-soft »

The use and creation of interfaces is not described in detail in the PB help.
Here is an example for creating the interface IUnknown.

Update description

Code: Select all

;-TOP 

; IUnknown Example (Windows Only) by mk-soft, v1.01.2, date 27.09.2021

Interface iMyObject Extends IUnknown
  Set(*bStrVal.p-bstr)  ; Parameter as BSTR
  Get(*vResult.variant) ; Result as Variant BYREF
EndInterface
  
Structure sUnknown
  *vTable     ; First entry is always a pointer to table with the methods
  RefCount.i  ; Counter of reference
EndStructure

Structure sMyObject Extends sUnknown
  sValue.s
EndStructure

Declare NewObject()

;- IUnknown Methods

Procedure QueryInterface(*this.sUnknown, *riid, *ppvObject.integer)
  
  If *ppvObject = 0 Or *riid = 0
    ProcedureReturn #E_INVALIDARG
  EndIf
  
  If CompareMemory(*riid, ?IID_IUnknown, 16)
    *ppvObject\i = *this
    *this\RefCount + 1
    ProcedureReturn #S_OK
  ElseIf CompareMemory(*riid, ?IID_MyObject, 16)
    *ppvObject\i = NewObject()
    If *ppvObject\i
      ProcedureReturn #S_OK
    Else
      ProcedureReturn #E_OUTOFMEMORY
    EndIf
  Else
    *ppvObject\i = 0
    ProcedureReturn #E_NOINTERFACE
  EndIf
EndProcedure

Procedure AddRef(*this.sUnknown)
  *this\RefCount + 1
  Debug "MyObject [" + Hex(*this) + "] - AddRef: RefCount " + *this\RefCount
  ProcedureReturn *this\RefCount
EndProcedure

Procedure Release(*this.sUnknown)
  If *this\RefCount <= 1
    FreeStructure(*this)
    Debug "MyObject [" + Hex(*this) + "] - Release: Destroy Object"
    ProcedureReturn 0
  Else
    *this\RefCount - 1
    Debug "MyObject [" + Hex(*this) + "] - Release: RefCount " + *this\RefCount
    ProcedureReturn *this\RefCount
  EndIf
EndProcedure

;- MyObject Methods

Procedure Set(*this.sMyObject, *bstrVal)
  If *bstrVal
    *this\sValue = PeekS(*bstrVal)
    ProcedureReturn #S_OK
  Else
    ProcedureReturn #E_INVALIDARG
  EndIf
EndProcedure

Procedure Get(*this.sMyObject, *vResult.Variant)
  If *vResult = 0
    ProcedureReturn #E_INVALIDARG
  EndIf
  VariantClear_(*vResult) ; Make sure that is empty
  *vResult\vt = #VT_BSTR
  *vResult\bstrVal = SysAllocString_(*this\sValue)
  ProcedureReturn #S_OK
EndProcedure

;- Creating of Object

Procedure NewObject()
  Protected *Object.sMyObject
  *Object = AllocateStructure(sMyObject) ; Allocate memory for the object
  If *Object
    *Object\vTable = ?vTable_MyObject ; Set pointer to the methods table
    *Object\RefCount = 1 ; Set start reference
  EndIf
  Debug "MyObject [" + Hex(*Object) + "] - New"
  ProcedureReturn *Object
EndProcedure

DataSection ; Table of Methods. Same order as the interface description
  vTable_MyObject:
  Data.i @QueryInterface()
  Data.i @AddRef()
  Data.i @Release()
  Data.i @Set()
  Data.i @Get()
EndDataSection

DataSection ; Microsoft defaults
  IID_IUnknown: ; {00000000-0000-0000-C000-000000000046}
  Data.l $00000000
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46
EndDataSection

DataSection ; Create with API UuidCreate_(Uuid.iid)
  IID_MyObject:
  Data.l $6E17E613
  Data.w $6CE4, $4559
  Data.b $FF, $7E, $12, $FF, $FF, $1A, $FF, $FF
EndDataSection

; ****

;- Use of Object

Global *obj1.iMyObject, *obj2.iMyObject
Global s1.s, var.variant

; Create Object
*obj1 = NewObject()
If *obj1
  *obj1\Set("Hello World!") ; <- PB allocate and free BSTR internaly
EndIf

; Create Object over QueryInterface
*obj1\AddRef()
r1 = *obj1\QueryInterface(?IID_MyObject, @*obj2)
If r1 = #S_OK
  *obj2\Set("I like Purebasic") ; <- PB allocate and free BSTR internaly
EndIf
*obj1\Release()

If *obj1\Get(@var) = #S_OK
  If var\vt = #VT_BSTR
    s1 = PeekS(var\bstrVal)
    Debug "Obj1 = " + s1
  EndIf
  VariantClear_(var)
EndIf

If *obj2\Get(@var) = #S_OK
  If var\vt = #VT_BSTR
    s1 = PeekS(var\bstrVal)
    Debug "Obj2 = " + s1
  EndIf
  VariantClear_(var)
EndIf

*obj1\Release()
*obj2\Release()
Last edited by mk-soft on Mon Sep 27, 2021 6:52 pm, edited 1 time in total.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4622
Joined: Sun Apr 12, 2009 6:27 am

Re: How to create IUnknown Interface (Example)

Post by RASHAD »

Hi mk-soft
You are always tend to do what we lack in PB
Thanks for your great effort
Egypt my love
infratec
Always Here
Always Here
Posts: 6810
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: How to create IUnknown Interface (Example)

Post by infratec »

Isn't the first

Code: Select all

*obj1\Release()
in line 140 wrong?

Oh, I see.

But AddRef() makes not much sense :wink:
User avatar
mk-soft
Always Here
Always Here
Posts: 5313
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: How to create IUnknown Interface (Example)

Post by mk-soft »

infratec wrote: Mon Sep 27, 2021 8:48 pm Isn't the first

Code: Select all

*obj1\Release()
in line 140 wrong?

Oh, I see.

But AddRef() makes not much sense :wink:
Doesn't make much sense in the example, but shows the interaction of AddRef() and Release() and must therefore also be implemented in the IUnknown methods.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
mk-soft
Always Here
Always Here
Posts: 5313
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: How to create IUnknown Interface (Example)

Post by mk-soft »

Objects are often used asynchronously or in threads. Therefore, the methods must be made thread-safe.

Code: Select all

;-TOP 

; IUnknown Example ThreadSafe (Windows Only) by mk-soft, v1.02.0, date 29.09.2021

Interface iMyObject Extends IUnknown
  Set(*bStrVal.p-bstr)  ; Parameter as BSTR
  Get(*vResult.variant) ; Result as Variant BYREF
EndInterface
  
Structure sUnknown
  *vTable     ; First entry is always a pointer to table with the methods
  RefCount.i  ; Counter of reference
  Mutex.i     ; For threadsafe
EndStructure

Structure sMyObject Extends sUnknown
  sValue.s
EndStructure

Declare NewObject()

;- IUnknown Methods

Procedure QueryInterface(*this.sUnknown, *riid, *ppvObject.integer)
  Protected r1
  LockMutex(*this\Mutex)
  If *ppvObject = 0 Or *riid = 0
    r1 = #E_INVALIDARG
  Else
    If CompareMemory(*riid, ?IID_IUnknown, 16)
      *ppvObject\i = *this
      *this\RefCount + 1
      r1 = #S_OK
    ElseIf CompareMemory(*riid, ?IID_MyObject, 16)
      *ppvObject\i = NewObject()
      If *ppvObject\i
        r1 = #S_OK
      Else
        r1 = #E_OUTOFMEMORY
      EndIf
    Else
      *ppvObject\i = 0
      r1 = #E_NOINTERFACE
    EndIf
  EndIf
  UnlockMutex(*this\Mutex)
  ProcedureReturn r1
EndProcedure

Procedure AddRef(*this.sUnknown)
  Protected r1
  LockMutex(*this\Mutex)
  *this\RefCount + 1
  r1 = *this\RefCount
  Debug "MyObject [" + Hex(*this) + "] - AddRef: RefCount " + r1
  UnlockMutex(*this\Mutex)
  ProcedureReturn r1
EndProcedure

Procedure Release(*this.sUnknown)
  Protected r1
  LockMutex(*this\Mutex)
  If *this\RefCount
    *this\RefCount -1
  EndIf
  r1 = *this\RefCount
  UnlockMutex(*this\Mutex)  
  If r1 = 0
    FreeMutex(*this\Mutex)
    FreeStructure(*this)
    Debug "MyObject [" + Hex(*this) + "] - Release: Destroy Object"
  Else
    Debug "MyObject [" + Hex(*this) + "] - Release: RefCount " + r1
  EndIf
  ProcedureReturn r1
EndProcedure

;- MyObject Methods

Procedure Set(*this.sMyObject, *bstrVal)
  Protected r1
  LockMutex(*this\Mutex)
  If *bstrVal
    *this\sValue = PeekS(*bstrVal)
    r1 = #S_OK
  Else
    r1 = #E_INVALIDARG
  EndIf
  UnlockMutex(*this\Mutex)
  ProcedureReturn r1
EndProcedure

Procedure Get(*this.sMyObject, *vResult.Variant)
  Protected r1
  LockMutex(*this\Mutex)
  If *vResult
    VariantClear_(*vResult) ; Make sure that is empty
    *vResult\bstrVal = SysAllocString_(*this\sValue)
    If *vResult\bstrVal
      *vResult\vt = #VT_BSTR
      r1 = #S_OK
    Else
      r1 = #E_OUTOFMEMORY
    EndIf
  Else
    r1 = #E_INVALIDARG
  EndIf
  UnlockMutex(*this\Mutex)
  ProcedureReturn r1
EndProcedure

;- Creating of Object

Procedure NewObject()
  Protected *Object.sMyObject
  *Object = AllocateStructure(sMyObject) ; Allocate memory for the object
  If *Object
    *Object\vTable = ?vTable_MyObject ; Set pointer to the methods table
    *Object\RefCount = 1              ; Set start reference
    *Object\Mutex = CreateMutex()     ; Set Mutex for threadsafe
  EndIf
  Debug "MyObject [" + Hex(*Object) + "] - New"
  ProcedureReturn *Object
EndProcedure

DataSection ; Table of Methods. Same order as the interface description
  vTable_MyObject:
  Data.i @QueryInterface()
  Data.i @AddRef()
  Data.i @Release()
  Data.i @Set()
  Data.i @Get()
EndDataSection

DataSection ; Microsoft defaults
  IID_IUnknown: ; {00000000-0000-0000-C000-000000000046}
  Data.l $00000000
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46
EndDataSection

DataSection ; Create with API UuidCreate_(Uuid.iid)
  IID_MyObject:
  Data.l $6E17E613
  Data.w $6CE4, $4559
  Data.b $FF, $7E, $12, $FF, $FF, $1A, $FF, $FF
EndDataSection

; ****

;- Use of Object

Procedure MyThread(*Object.iMyObject)
  Protected s1.s, var.variant
  Delay(100)
  If *Object\Get(@var) = #S_OK
    If var\vt = #VT_BSTR
      s1 = PeekS(var\bstrVal)
      Debug "Thread Object = " + s1
    EndIf
    VariantClear_(var)
  EndIf
  *Object\Release() ; Object is no longer needed
EndProcedure

Global *obj1.iMyObject, *obj2.iMyObject
Global s1.s, var.variant

; Create Object
*obj1 = NewObject()
If *obj1
  *obj1\Set("Hello World!") ; <- PB allocate and free BSTR internaly
EndIf

; AddRef and Release
*obj1\AddRef()
*obj1\Release()

; Create Object over QueryInterface
r1 = *obj1\QueryInterface(?IID_MyObject, @*obj2)
If r1 = #S_OK
  *obj2\Set("I like Purebasic") ; <- PB allocate and free BSTR internaly
EndIf

; Get 1
If *obj1\Get(@var) = #S_OK
  If var\vt = #VT_BSTR
    s1 = PeekS(var\bstrVal)
    Debug "Obj1 = " + s1
  EndIf
  VariantClear_(var)
EndIf

; Get 2
If *obj2\Get(@var) = #S_OK
  If var\vt = #VT_BSTR
    s1 = PeekS(var\bstrVal)
    Debug "Obj2 = " + s1
  EndIf
  VariantClear_(var)
EndIf

; Start thread
*obj2\Set("Use objekt inside thread")
*obj2\AddRef() ; Always AddRef before start thread
hThread = CreateThread(@MyThread(), *obj2)

; Release objects
*obj1\Release()
*obj2\Release()

WaitThread(hThread)
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Post Reply