Announcement

Collapse
No announcement yet.

passing interfaces - no type checking?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • John Strasser
    replied
    Bob:

    Thanks for compiling all those older posts into the 1 sticky. Makes it much easier for me.

    Originally Posted by Michael Mattias
    Is it my imagination, or is picking up this 'Object Stuff' easier for those of us (e.g. moi) who have NOT previously used a product featuring same than for those who have?
    What makes it "easier" Mike is that you are learning it for the first time. Your "mental reflexes" aren't already set up. Someone never really exposed to OOP or COM can essentially treat this as a new feature set. Others are looking for ways to compare the PB version of OOP/COM with what they used before (in my case C++/C++ Builder/Delphi).

    So my "instincts" were to look for the similar implementation/capabilities expecting just a syntax change "print" vs "printf" kind of thing.

    While in reality, other than understanding the concept, treating everything about how PB (and other languages) implement OOP as a separate language feature makes it easier to learn. Precisely because the differences are such that there is no direct analog for many things (as different aspects of OOP are implemented).

    It's like learning and driving for years here in the US where we drive on the right side of the road. And then suddenly going to one of those backwards countries where they drive on the left side of the road When a situation comes up where you have to react on instict (to avoid a collision) your instincts tell you to pull the wheel to the right, when you really have to pull the wheel to the left.

    JS

    Leave a comment:


  • Bob Zale
    replied
    Originally posted by Michael Mattias View Post
    Is it my imagination, or is picking up this 'Object Stuff' easier for those of us (e.g. moi) who have NOT previously used a product featuring same than for those who have?
    That's an interesting observation, and probably not your imagination. The real truth is that objects are fairly simple structures. But, in the past, they've been defined as having magical, mystical properties which destroy all evil on the planet. {smile} Comical, but almost true. I've read some amazing descriptions which sound incredible and are truly not credible. To those who've used objects before, some of these unusual definitions have surely had some impact. From a marketing perspective, there's a certain logic to this. If you can maintain a wall of difficulty or a wall of secrecy, then your programming tool looks like the "perfect and only solution".

    With PowerBASIC, we've tried hard to puncture that wall. We've given simple and straightforward definitions without all the mystical overtones. It just may be that prior knowledge of objects deflects some of the real, underlying simplicity.

    What's an object? A chunk of data private to the object, neatly packaged with a group of subroutines which manipulate the data and provide any other functionality you need. It's just that simple.

    Data private to the object is known as Instance Variables. The definition of the group of subroutines is known as the Interface, and the definition of the entire object is known as the Class. The Class definition is used to create the object -- the object is said to be an instance of the Class.

    What's a COM object? Everything the same as above, except that's it's possible to have multiple interfaces, as an option. Multiple groups of subroutines -- you get to pick and choose which one you wish to use at any particular time. Plus, the object can be located outside of your code module. That's it. The whole difference.

    I'd like to recommend that all of our friends check out "The Life and Times of an Object", here at the top of the "Programming with Objects" forum.

    Leave a comment:


  • Chris Holbrook
    replied
    Originally posted by José Roca View Post
    A COM interface is simply a related group of functions. As long as it can provide pointers to these functions, you can implement it as you wish. Classes are just a convenient way to do it...
    Well said. If the goal was just Object programming, PowerBasic could have acheived it years ago - it's not rocket science, and has been around for decades. One can only surmise that they did not want to. I would guess that COM was the prize and OOP was the target of opportunity - which is not to slight their implementation if it - it's just COM-flavored, that's all.

    Leave a comment:


  • José Roca
    replied
    Not to mention DirectX. Now that it can be used without wrappers, almost nobody seems interested in it.

    Leave a comment:


  • Fred Harris
    replied
    For years folks have been screeming at PowerBASIC - "We Need OOP!" "How Am I Gonna Compete Without COM?" "In Visual Basic We Did It This Way!"

    Remember the old saying about being careful about what you ask for because you just might get it!

    Leave a comment:


  • John Montenigro
    replied
    Originally posted by Fred Harris View Post
    ... the jump from standard object oriented programming to interface based programming ( COM ) is just about as painful as from procedural programming to OOP.
    That's not the kind of thing I was hoping to hear... wish someone had a roadmap for that journey!

    Leave a comment:


  • José Roca
    replied
    A COM interface is simply a related group of functions. As long as it can provide pointers to these functions, you can implement it as you wish. Classes are just a convenient way to do it, with additional advantages such being able, with appropriate compiler support, of calling an interface method through its name, not its position in the array of pointers know as the virtual table.
    Last edited by José Roca; 16 Feb 2009, 10:59 AM.

    Leave a comment:


  • Fred Harris
    replied
    I'd rather know that an invalid interface call was rejected in compilation
    Well, that's easy to achieve with normal function calls. COM isn't for everybody and everything. It was designed in the early 90s to solve some extremely vexing problems; problems actually the significance of which rivaled the complexity of designing GUIs operating systems. And those problems were the dynamic composition (at run-time) of computer systems using binary components, and the safe versioning and upgrading of software. Although that was the end result; not the initial more modest though still difficult goal, i.e., "How in the world do you integrate an Excel spreadsheet into a Word document?"

    Here's another version of the above program that shows a little bit better testing at run time of what is going on. It uses the dll in only slightly modified form downloadable from my tutorial at...

    http://www.jose.it-berater.org/smffo...p?topic=2980.0

    Code:
    'code causing output statements from within dll...
    
    'HRESULT __stdcall CA::QueryInterface(REFIID riid, void** ppv)
    '{
    ' *ppv=0;
    ' if(riid==IID_IUnknown)
    ' {
    '    *ppv=(I_X*)this;
    '    printf("Called CA::QueryInterface() For IID_IUnknows\n");
    ' }
    ' else if(riid==IID_I_X)
    ' {
    '    *ppv=(I_X*)this;
    '    printf("Called CA::QueryInterface() For IID_I_X\n");
    ' }
    ' else if(riid==IID_I_Y)
    ' {
    '    *ppv=(I_Y*)this;
    '    printf("Called CA::QueryInterface() For IID_I_Y\n");
    ' }
    ' if(*ppv)
    ' {
    '    AddRef();
    '    return S_OK;
    ' }
    ' printf("Called CA::QueryInterface()\n");
    '
    ' return(E_NOINTERFACE);
    '}
    '
    #Compile               Exe "CAClient.exe"
    #Dim                   All
    #Include               "Win32Api.inc"
    $CLSID_CA              =Guid$("{20000000-0000-0000-0000-000000000004}")  'Class ID of Class CA, ie., Class A
    $IID_IX                =Guid$("{20000000-0000-0000-0000-000000000005}")  'Interface X
    $IID_IY                =Guid$("{20000000-0000-0000-0000-000000000006}")  'Interface Y
    $IID_IZ                =Guid$("{20000000-0000-0000-0000-000000000007}")  'Interface Z
    $IID_PieInTheSky       =Guid$("{20000000-0000-0000-0000-000000000009}")  'Interface PieInTheSky
    
    
    Interface I_X $IID_IX : Inherit IUnknown
      Method Fx1(ByVal iNum As Long) As Long
      Method Fx2(ByVal iNum As Long) As Long
    End Interface
    
    
    Interface I_Y $IID_IY : Inherit IUnknown
      Method Fy1(ByVal iNum As Long) As Long
      Method Fy2(ByVal iNum As Long) As Long
    End Interface
    
    
    Interface I_Z $IID_IZ : Inherit IUnknown
      Method Fz1(ByVal iNum As Long) As Long
      Method Fz2(ByVal iNum As Long) As Long
    End Interface
    
    
    Sub Bar(IUnknwn As IUnknown, xIID As Guid)
      Select Case xIID
        Case $IID_IX
          Local ix As I_X
          Print "xIID = $IID_IX"
          ix=IUnknwn
          If IsObject(ix) Then
            Print "ix is a ledgitimate object, and we are now in the I_X VTable!"
            Call ix.Fx1(1) : Call ix.Fx2(1)
            Set ix=Nothing
          Else
            Print "This IUnknown has no knowledge of " GuidTxt$(xIID)
          End If
        Case $IID_IY
          Local iy As I_Y
          Print "xIID = $IID_IY"
          iy=IUnknwn
          If IsObject(iy) Then
            Print "iy is a ledgitimate object!"
            Call iy.Fy1(2) : Call iy.Fy2(2)
            Set iy=Nothing
          Else
            Print "This IUnknown has no knowledge of " GuidTxt$(xIID)
          End If
        Case $IID_IZ
          Local iz As I_Z
          Print "xIID = $IID_IZ"
          iz=IUnknwn
          If IsObject(iz) Then
            Print "iz is a ledgitimate object!"
            Call iz.Fz1(2) : Call iz.Fz2(2)
            Set iz=Nothing
          Else
            Print "You Need To Pay Me Some Extra Bucks If You Want "
            Print "To Use " GuidTxt$(xIID)
          End If
        Case Else
          Print "...And If You Want This You Had Better Really Open Your Wallet!"
      End Select
    End Sub
    
    
    Function PBMain() As Long
      Local IUnk As IUnknown
    
      IUnk=NewCom Clsid $CLSID_CA
      If IsObject(IUnk) Then
         Call Bar(IUnk,$IID_IX)
         Call Bar(IUnk,$IID_IY)
         Call Bar(IUnk,$IID_IZ)
         Call Bar(IUnk,$IID_PieInTheSky)
         Set IUnk=Nothing
      Else
         Print $CLSID_CA "Couldn't Be Instantiated!"
      End If
      Waitkey$
    
      PBMain=0
    End Function
    
    'Called CA::QueryInterface() For IID_IUnknows
    'Called CA::AddRef()
    'Called CA::AddRef()
    'Called CA::Release()
    'Called CA::QueryInterface() For IID_IUnknows
    'Called CA::AddRef()
    'Called CA::Release()
    'xIID = $IID_IX
    'Called CA::QueryInterface() For IID_I_X
    'Called CA::AddRef()
    'ix is a ledgitimate object, and we are now in the I_X VTable!
    'Called Fx1()  :  iNum = 1
    'Called Fx2()  :  iNum = 1
    'Called CA::Release()
    'xIID = $IID_IY
    'Called CA::QueryInterface() For IID_I_Y
    'Called CA::AddRef()
    'iy is a ledgitimate object!
    'Called Fy1()  :  iNum = 2
    'Called Fy2()  :  iNum = 2
    'Called CA::Release()
    'xIID = $IID_IZ
    'Called CA::QueryInterface()
    'You Need To Pay Me Some Extra Bucks If You Want
    'To Use {20000000-0000-0000-0000-000000000007}
    '...And If You Want This You Had Better Really Open Your Wallet!
    'Called CA::Release()

    Leave a comment:


  • Michael Mattias
    replied
    >OOP and COM - though closely related, are not exactly the same

    Good observervation although I'm not so sure they are even that closely related, since you can use the Component Object Model architecture without using Object-Oriented Programming syntax and techniques.

    eg. Jose Roca did a lot of work and has provided a bunch of code to do exactly that... for use when the PB compilers did not support O-O-P.

    Leave a comment:


  • Chris Holbrook
    replied
    Originally posted by Fred Harris View Post
    As you can see, Foo() was modified to take a generic IUknown pointer, and an integer arguement to specify which interface was desired.
    So Foo can test the interface and reject -at run time - an invalid interface. I would rather know that an invalid interface call was rejected in compilation, than risk the future of civilisation when my home-built PowerBasic-powered nanobot goes ape.

    Alternatively, one would have to rely on a convention - such as calling a method to return the interface name - to give a practical level of confidence that the correct interface had been passed. Such conventions rely on human factors and are thus inherently prone to failure. Enjoy civilisation while you can, Fred!.

    Leave a comment:


  • Fred Harris
    replied
    So why (does PB) dress it up as something it is not - either a specific (and by implication unique) object reference or a specific interface reference.
    Well, that was just a contrived example of mine to see just what the behaviours were of interfaces as function parameters. This is, after all, somewhat silly...

    Code:
    Sub Foo(ifx As I_X)
      Call ifx.Fx1(75)
    End Sub
    ...and is just a wrapper on Fx1(). However, in real apps I can think of it being reasonable to pass an object to various functions. The thing to do though rather than passing a specific interface might be to pass a generic IUnknown pointer which could be used in other procedures to acquire the specific interface required there. I'll provide both a PowerBASIC & C++ version of this using my CA class from my example in Jose's forum mentioned previously. First the C++ version...

    Code:
    //CAClient.cpp 
    #include <windows.h>
    #include <stdio.h>
    const CLSID CLSID_CA={0x20000000,0x0000,0x0000,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4}};
    const IID   IID_I_X ={0x20000000,0x0000,0x0000,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x5}};
    const IID   IID_I_Y ={0x20000000,0x0000,0x0000,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6}};
    
    
    interface I_X : IUnknown
    {
     virtual HRESULT __stdcall Fx1(int)=0;
     virtual HRESULT __stdcall Fx2(int)=0;
    };
    
    
    interface I_Y : IUnknown
    {
     virtual HRESULT __stdcall Fy1(int)=0;
     virtual HRESULT __stdcall Fy2(int)=0;
    };
    
    
    void Foo(IUnknown* pIUnk, int ifDesired)
    {
     if(ifDesired==1)  //ifDesired = interface desired
     {
        I_X* pIX=NULL;
        pIUnk->QueryInterface(IID_I_X,(void**)&pIX);
        pIX->Fx1(1);
        pIX->Fx2(1);
        pIX->Release();
     }
     if(ifDesired==2)  //ifDesired = interface desired
     {
        I_Y* pIY=NULL;
        pIUnk->QueryInterface(IID_I_Y,(void**)&pIY);
        pIY->Fy1(2);
        pIY->Fy2(2);
        pIY->Release();
     }
    }
    
    
    int main(int argc, char* argv[])
    {
     IUnknown* pIUnknown=NULL;
     HRESULT hr;
      
     CoInitialize(NULL);
     hr=CoCreateInstance(CLSID_CA,NULL,CLSCTX_SERVER,IID_IUnknown,(void**)&pIUnknown);
     if(SUCCEEDED(hr))
     {
        Foo(pIUnknown,1);
        Foo(pIUnknown,2);
        pIUnknown->Release();
     }
     CoUninitialize();
    
     return 0;
    }
    
    /*
    Called CA::QueryInterface() For IID_IUnknows
    Called CA::AddRef()
    Called CA::AddRef()
    Called CA::Release()
    Called CA::QueryInterface() For IID_IUnknows
    Called CA::AddRef()
    Called CA::Release()
    Called CA::QueryInterface() For IID_I_X
    Called CA::AddRef()
    Called Fx1()  :  iNum = 1
    Called Fx2()  :  iNum = 1
    Called CA::Release()
    Called CA::QueryInterface() For IID_I_Y
    Called CA::AddRef()
    Called Fy1()  :  iNum = 2
    Called Fy2()  :  iNum = 2
    Called CA::Release()
    Called CA::Release()
    */
    And the PowerBASIC version...

    Code:
    #Compile               Exe "CAClient.exe"
    #Dim                   All
    #Include               "Win32Api.inc"
    $CLSID_CA              =Guid$("{20000000-0000-0000-0000-000000000004}")  'Class ID of Class CA, ie., Class A
    $IID_IX                =Guid$("{20000000-0000-0000-0000-000000000005}")  'Interface X
    $IID_IY                =Guid$("{20000000-0000-0000-0000-000000000006}")  'Interface Y
    
    
    Interface I_X $IID_IX : Inherit IUnknown
      Method Fx1(ByVal iNum As Long) As Long
      Method Fx2(ByVal iNum As Long) As Long
    End Interface
    
    
    Interface I_Y $IID_IY : Inherit IUnknown
      Method Fy1(ByVal iNum As Long) As Long
      Method Fy2(ByVal iNum As Long) As Long
    End Interface
    
    
    Sub Foo(unk As IUnknown, ifDesired As Long)
      If ifDesired=1 Then
         Local ix As I_X
         ix=unk
         Call ix.Fx1(1)
         Call ix.Fx2(1)
         Set ix=Nothing
      End If
      If ifDesired=2 Then
         Local iy As I_Y
         iy=unk
         Call iy.Fy1(2)
         Call iy.Fy2(2)
         Set iy=Nothing
      End If
    End Sub
    
    
    Function PBMain() As Long
      Local IUnk As IUnknown
    
      IUnk=NewCom Clsid $CLSID_CA
      Call Foo(IUnk,1)
      Call Foo(IUnk,2)
      Set IUnk=Nothing
      Waitkey$
    
      PBMain=0
    End Function
    
    'Called CA::QueryInterface() For IID_IUnknows
    'Called CA::AddRef()
    'Called CA::AddRef()
    'Called CA::Release()
    'Called CA::QueryInterface() For IID_IUnknows
    'Called CA::AddRef()
    'Called CA::Release()
    'Called CA::QueryInterface() For IID_I_X
    'Called CA::AddRef()
    'Called Fx1()  :  iNum = 1
    'Called Fx2()  :  iNum = 1
    'Called CA::Release()
    'Called CA::QueryInterface() For IID_I_Y
    'Called CA::AddRef()
    'Called Fy1()  :  iNum = 2
    'Called Fy2()  :  iNum = 2
    'Called CA::Release()
    As you can see, Foo() was modified to take a generic IUknown pointer, and an integer arguement to specify which interface was desired. Within the function the IUnknown pointer was cast to the desired specific interface. Pretty neat that the output from both programs is exactly the same.

    Notice in the C++ program that pIUnknown in the CreateInstance() call is an output parameter typed as a (void**)&pIUnknown. That is most definitely just a raw number with no inherent type as far as C++ is concerned, and by typeing it as such it doesn't leave much doubt as to what it is.
    Last edited by Fred Harris; 15 Feb 2009, 09:13 PM.

    Leave a comment:


  • Fred Harris
    replied
    Yes, you have hit the nail on the head Chris. OOP and COM - though closely related, are not exactly the same. Different writers on the topic that I am acquainted with have made the statement that the jump from standard object oriented programming to interface based programming ( COM ) is just about as painful as from procedural programming to OOP.

    Leave a comment:


  • Chris Holbrook
    replied
    Nicely explained Fred.

    Originally posted by Fred Harris View Post
    ...the intent of the design was absolute and total ambiguity.
    I can handle that.

    Originally posted by Fred Harris View Post
    Sub Foo is just a wrapper around a function call which picks out the 3rd zero based function pointer in whatever VTable is passed to it.
    Exactly. So why (does PB) dress it up as something it is not - either a specific (and by implication unique) object reference or a specific interface reference.

    The irritating thing about all this is that when working with Objects in PowerBasic, even if COM is quite irrelevant to the application, it is necessary to take the COM on board and sadly for me this means quite a lot of time in MSDN. No, actually and to be honest, the most irritating thing is that I should have thought that one out a long time ago!

    Thank you for your pioneer work here and elsewhere which is going to save me and others a load of time.

    Leave a comment:


  • Fred Harris
    replied
    I Guess

    Why not make ME a structure with Object and Interface components?
    Having an ambiguous object pointer seems odd to me. Why not have an Object ptr, an Interface Id and a QueryInterface function?
    It does seem odd, but the intent of the design was absolute and total ambiguity.

    Think for a moment about a totally different situation that I expect you've come across, and that is the NMHDR pointer received in the lParam of the Window Procedure during a WM_NOTIFY message from a custim control or one of the Windows Common Controls. To refresh your memory, that type looks like this...

    Code:
    typedef struct tagNMHDR 
    { 
      HWND hwndFrom; 
      UINT idFrom; 
      UINT code; 
    }NMHDR;
    With it one can obtain the hWnd, the ctrl id and the specific message that occurred within the actual window procedure of the custom or common control one is hosting. The interesting part about it though is that the design is such that if this structure/type is prepended at offset zero of a larger structure/type, then it forms the basis for communication between all custom controls which can transmit various types and quantities of information depending on the characteristics of the control. As such, it is an agreed upon contract between clients and servers. If you have a pointer to such an object and you know that that NMHDR structure is there in the 1st twelve bytes, you can determine at run time what type of object you have, and you can intelligently at run time act on that information. For example, I use the siGrid control quite a bit, and here are what a few of the types look like. Note they are all of a different size, but if that NMHDR structure is 1st, one can do a lot here...

    Code:
    Type siSelectedRowCol
        hdr             As NMHDR
        Row             As Long
        Col             As Long
        X               As Long
        Y               As Long
        HwndEdit        As Long
        ChangeMessage   As Long
        ReturnCode      As Long
    End Type
    
    Type siGridNavigation
        hdr             As NMHDR
        Row             As Long
        Col             As Long
        X               As Long
        Y               As Long
        ShiftDown       As Long
        CtrlDown        As Long
        EditMsg         As tagMsg
        ReturnCode      As Long
    End Type
    
    Type siRowNotification
        hdr             As NMHDR
        Row             As Long
        ReturnCode      As Long
    End Type
    So essentially that is what is going on with the three IUnknown functions pre-pended to the beginning of every interface. All interfaces are polymorphs of IUnknown. And I suppose the strange behavior we are seeing (or what seems strange to us) when we see just about anything being accepted in a function parameter when it is typed as an interface reference, must follow from the fact that functions with interface parameters have to really accept anything. Think of the NMHDR situation. I don't see its really anything different from that. And anyway, objects are always passed by reference - never by value, so all we are talking about are pointers anyway.

    And I did think about my situation above where I made that dumb little procedure like this...

    Code:
    Sub Foo(ifx As I_X)
      ifx.Fx1(75)
    End Sub
    ...which in beautiful fashion called I_Y::Fy1(75) when a I_Y pointer was passed to it instead of the I_X pointer it was typed to receive. How could that be any different? The function essentially takes a 32 bit pointer. In the case of my CA class containing two interfaces, i.e., an I_X interface and a I_Y interface, each of which resolves to a virtual function table with pointers to the three IUnknown functions as the 1st three pointers, followed by respectively Fx1()/Fx2() in the I_X VTable and Fy1()/Fy2() in the 2nd VTable - well, Sub Foo is just a wrapper around a function call which picks out the 3rd zero based function pointer in whatever VTable is passed to it. And that's all it did. If you pass a I_X VTable to it, Fx1() gets called. If you pass an I_Y VTable to it, Fy1() gets called. If you pass a null or invalid pointer to it, its going to try to execute a function call 12 bytes advanced from whatever # is in the pointer you gave it. And after all, even C/C++ has its void* parameter type. I guess.
    Last edited by Fred Harris; 15 Feb 2009, 12:25 AM.

    Leave a comment:


  • Chris Holbrook
    replied
    Originally posted by Fred Harris View Post
    me or this has to be a pointer to a class object.
    Having an ambiguous object pointer seems odd to me. Why not have an Object ptr, an Interface Id and a QueryInterface function?

    Leave a comment:


  • Michael Mattias
    replied
    Is it my imagination, or is picking up this 'Object Stuff' easier for those of us (e.g. moi) who have NOT previously used a product featuring same than for those who have?

    Leave a comment:


  • Fred Harris
    replied
    me or this has to be a pointer to a class object.

    Leave a comment:


  • Chris Holbrook
    replied
    Originally posted by José Roca View Post
    If PB didn't allow relaxed type checking then it will have to add a reinterpret_cast operator such in C++.
    Why not make ME a structure with Object and Interface components?

    Leave a comment:


  • José Roca
    replied
    If PB didn't allow relaxed type checking then it will have to add a reinterpret_cast operator such in C++.

    Leave a comment:


  • Fred Harris
    replied
    danger

    Jose sees some useful purpose here, but you had better know what you are doing because it seems rather dangerous to me. Here is what I discovered. I'm using the COM object CA I developed and posted code to over in Jose's forum...

    http://www.jose.it-berater.org/smffo...p?topic=2980.0

    Here is the PowerBASIC program I created to test passing interfaces. The class CA has two interfaces I_X and I_Y, and I created a Sub like so...

    Code:
    Sub Foo(ifx As I_X)
      ifx.Fx1(75)
    End Sub
    ...and I passed an I_Y interface object to the function to see what would happen. But first, in the code below at the top and before the PB part you can see the remarked out C++ code from the COM dll that produces the output from the program - - all the QueryInterface(), AddRef(), Release() stuff...

    Code:
    'HRESULT __stdcall CA::QueryInterface(REFIID riid, void** ppv)
    '{
    ' *ppv=0;
    ' if(riid==IID_IUnknown)
    ' {
    '    *ppv=(I_X*)this;
    '    printf("Called CA::QueryInterface() For IID_IUnknows\n");
    ' }
    ' else if(riid==IID_I_X)
    ' {
    '    *ppv=(I_X*)this;
    '    printf("Called CA::QueryInterface() For IID_I_X\n");
    ' }
    ' else if(riid==IID_I_Y)
    ' {
    '    *ppv=(I_Y*)this;
    '    printf("Called CA::QueryInterface() For IID_I_Y\n");
    ' }
    ' if(*ppv)
    ' {
    '    AddRef();
    '    return S_OK;
    ' }
    ' printf("Called CA::QueryInterface()\n");
    '
    ' return(E_NOINTERFACE);
    '}
    
    'ULONG __stdcall CA::AddRef()
    '{
    ' printf("Called CA::AddRef()\n");
    ' return InterlockedIncrement(&m_lRef);
    '}
    '
    '
    'ULONG __stdcall CA::Release()
    '{
    ' printf("Called CA::Release()\n");
    ' if(InterlockedDecrement(&m_lRef)==0)
    ' {
    '    delete this;
    '    return 0;
    ' }
    '
    ' return m_lRef;
    '}
    '
    '
    'HRESULT __stdcall CA::Fx1(int iNum)
    '{
    ' printf("Called Fx1()  :  iNum = %u\n",iNum);
    ' return S_OK;
    '}
    '
    '
    'HRESULT __stdcall CA::Fx2(int iNum)
    '{
    ' printf("Called Fx2()  :  iNum = %u\n",iNum);
    ' return S_OK;
    '}
    '
    '
    'HRESULT __stdcall CA::Fy1(int iNum)
    '{
    ' printf("Called Fy1()  :  iNum = %u\n",iNum);
    ' return S_OK;
    '}
    '
    '
    'HRESULT __stdcall CA::Fy2(int iNum)
    '{
    ' printf("Called Fy2()  :  iNum = %u\n",iNum);
    
    ' return S_OK;
    '}
    '
    
    
    #Compile               Exe "CAClient.exe"
    #Dim                   All
    #Include               "Win32Api.inc"
    $CLSID_CA              =Guid$("{20000000-0000-0000-0000-000000000004}")  'Class ID of Class CA, ie., Class A
    $IID_IX                =Guid$("{20000000-0000-0000-0000-000000000005}")  'Interface X
    $IID_IY                =Guid$("{20000000-0000-0000-0000-000000000006}")  'Interface Y
    
    
    Interface I_X $IID_IX : Inherit IUnknown
      Method Fx1(ByVal iNum As Long) As Long
      Method Fx2(ByVal iNum As Long) As Long
    End Interface
    
    
    Interface I_Y $IID_IY : Inherit IUnknown
      Method Fy1(ByVal iNum As Long) As Long
      Method Fy2(ByVal iNum As Long) As Long
    End Interface
    
    
    Sub Foo(ifx As I_X)
      Local hResult As Long
      hResult=ifx.Fx1(75)
    End Sub
    
    
    Function PBMain() As Long
      Local hResult As Long
      Local ix As I_X
      Local iy As I_Y
    
      ix=NewCom Clsid $CLSID_CA
      hResult=ix.Fx1(25)
      hResult=ix.Fx2(50)
      iy=ix                '<< this line causes a QueryInterface() call
      Call Foo(iy)         'in the COM object
      Set ix = Nothing
      Set iy = Nothing
      Waitkey$
    
      PBMain=0
    End Function
    
    'Output
    '=================================
    'Called CA::QueryInterface() For IID_I_X
    'Called CA::AddRef()
    'Called CA::AddRef()
    'Called CA::Release()
    'Called CA::QueryInterface() For IID_I_X
    'Called CA::AddRef()
    'Called CA::Release()
    'Called Fx1()  :  iNum = 25
    'Called Fx2()  :  iNum = 50
    'Called CA::QueryInterface() For IID_I_Y
    'Called CA::AddRef()
    'Called Fy1()  :  iNum = 75
    'Called CA::Release()
    'Called CA::Release()
    Notice that the line iy=ix is all that is needed in PowerBASIC to cause a QueryInterface() call in the COM object that causes the COM object to hand out (return) a pointer to the requested jump-table, vtable, interface, struct, virtual base class, array of function pointers - take your pick in terms of the terminology you like best. Visual Basic works similiarly the only difference is the 'Set' keyword is used I believe. But look at that output!!!!! We passed in an I_Y interface object to a function explicitely typed as requiring an I_X interface object - which function explicitely calls the Fx1() Method of the I_X interface, and look at what we got! A call to I_Y::Fy1()!

    Looking at the situation from the C++ side, below is a client C++ program doing the exact same thing and obtaining the exact same results as the PowerBASIC program. The only difference is that when you try to pass an I_Y interface object to a function typed as requiring an I_X interface object, the compiler generates an error message as such...

    Code:
    void Foo(I_X* ifx)
    {
     ifx->Fx1(75);
    }
    
    //called this way...
    
    Foo(pIY);        //Won't Compile!  See Error Compiler Output Below
    /*

    --------------------Configuration: Client - Win32 Release--------------------
    Compiling...
    Main.cpp
    C:\Code\VStudio\VC++6\Projects\CA\Client\Main.cpp(43) : error C2664:
    'Foo' : cannot convert parameter 1 from 'struct I_Y *' to 'struct I_X *'
    Types pointed to are unrelated; conversion requires reinterpret_cast, C-style
    cast or function-style cast
    Error executing cl.exe.

    Main.obj - 1 error(s), 0 warning(s)
    */

    However, taking the hint from the compiler output I put a I_X* cast in the function call like so...

    Foo((I_X*)pIY);

    and that compiled just fine and generated the exact same funky output as the PowerBASIC program. Here is the C++ program and the relavant ioutput...

    Code:
    //CAClient.cpp 
    #include <windows.h>
    #include <stdio.h>
    const CLSID CLSID_CA={0x20000000,0x0000,0x0000,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4}};
    const IID   IID_I_X ={0x20000000,0x0000,0x0000,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x5}};
    const IID   IID_I_Y ={0x20000000,0x0000,0x0000,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6}};
    
    interface I_X : IUnknown
    {
     virtual HRESULT __stdcall Fx1(int)=0;
     virtual HRESULT __stdcall Fx2(int)=0;
    };
    
    
    interface I_Y : IUnknown
    {
     virtual HRESULT __stdcall Fy1(int)=0;
     virtual HRESULT __stdcall Fy2(int)=0;
    };
    
    
    void Foo(I_X* ifx)
    {
     ifx->Fx1(75);
    }
    
    
    int main(int argc, char* argv[])
    {
     HRESULT hr;
     I_X* pIX=NULL;
     I_Y* pIY=NULL;
     
     CoInitialize(NULL);
     hr=CoCreateInstance(CLSID_CA,NULL,CLSCTX_SERVER,IID_I_X,(void**)&pIX);
     if(SUCCEEDED(hr))
     {
        pIX->Fx1(25);
        pIX->Fx2(50);
        pIX->QueryInterface(IID_I_Y,(void**)&pIY);
        if(SUCCEEDED(hr))
        {
           //Foo(pIY);        //Won't Compile!  See Error Compiler Output Below
           Foo((I_X*)pIY); 
           pIX->Release();
           pIY->Release();
        }
        
     }
     CoUninitialize();
    
     return 0;
    }
    
    /*
    --------------------Configuration: Client - Win32 Release--------------------
    Compiling...
    Main.cpp
    C:\Code\VStudio\VC++6\Projects\CA\Client\Main.cpp(43) : error C2664: 
    'Foo' : cannot convert parameter 1 from 'struct I_Y *' to 'struct I_X *'
    Types pointed to are unrelated; conversion requires reinterpret_cast, C-style 
    cast or function-style cast
    Error executing cl.exe.
    
    Main.obj - 1 error(s), 0 warning(s)
    */
    
    /*
    Called CA::QueryInterface() For IID_I_X
    Called CA::AddRef()
    Called CA::AddRef()
    Called CA::Release()
    Called CA::QueryInterface() For IID_I_X
    Called CA::AddRef()
    Called CA::Release()
    Called Fx1()  :  iNum = 25
    Called Fx2()  :  iNum = 50
    Called CA::QueryInterface() For IID_I_Y
    Called CA::AddRef()
    Called Fy1()  :  iNum = 75
    Called CA::Release()
    Called CA::Release()
    Press any key to continue
    */
    I find all this a bit interesting and somewhaqt unexpected. I'll need to think on this some more. But its clearly dangerous.
    Last edited by Fred Harris; 14 Feb 2009, 02:10 PM. Reason: fix terminology

    Leave a comment:

Working...
X