Announcement

Collapse
No announcement yet.

passing interfaces - no type checking?

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

  • passing interfaces - no type checking?

    Blundering through a bit of coding this morning I noticed that if a parameter is passed AS <interfacename>, then the wrong interface can be supplied without any problems arising until the wrong interface method is called. My guess is that methods are accessed via a jump table rather than being called at explicit addresses like SUBs and FUNCTIONS. So all the "AS myinterface" does it to tell which jump table to use, and this "typing" is not enforced. I've only tried interfaces in the same CLASS so far. Can anyone confirm thsi?

  • #2
    Passed UDT parameters aren't checked, either.. except for the SIZEOF(same). May be related.
    Michael Mattias
    Tal Systems (retired)
    Port Washington WI USA
    [email protected]
    http://www.talsystems.com

    Comment


    • #3
      jump table

      The 'jump table' is the vtable Chris. Maybe this will help...

      http://www.jose.it-berater.org/smffo...p?topic=2980.0
      Fred
      "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

      Comment


      • #4
        Originally posted by Fred Harris View Post
        The 'jump table' is the vtable Chris.
        Oh, I see. Gosh you have been busy over there haven't you?

        Comment


        • #5
          So wouldn't represent better management of our expectations to pass all interfaces as type Interface ? - or would this not be right for COM?

          Comment


          • #6
            Imo it is invoking the desired interface under water.
            If objptr differs from in and use i am right.
            If the same.. we have a problem

            Check out my items question.
            property items As Interface2
            property = Me
            End property

            PB invokes (queryinterface) the desired interface from the class.
            Excellent stuff!!
            hellobasic

            Comment


            • #7
              Originally posted by Edwin Knoppert View Post
              Imo it is invoking the desired interface under water.
              If objptr differs from in and use i am right.
              Edwin if only I understood what you are saying!

              Comment


              • #8
                If a class supports multiple interfaces, it has the capacity to hand out these interfaces in the form of virtual function table pointers to interested clients. Lets say a class contains an ix, iy, and iz interface, and you obtain an ix interface from the class in the usual manner. In either Visual Basic or PowerBASIC you'll get any of the other interfaces by doing something as simple as this...

                iy = ix

                What happens (under water) is that VB or PB performs a QueryInterface() call where one of the parameters of the call is the requested interface. If that interface is implemented within the class you'll get a valid interface pointer from the call that you can use.

                Also, in PowerBASIC anyway, you can pass any interface to a routine because all interfaces ultimately have IUnknown as their 1st methods; i.e., all interfaces are polymorphs of IUnknown.

                It seems you may have also stumbled onto the fact that passing an interface pointer causes a QueryInterface call if the interface passed is different from the type specified in the call.
                Fred
                "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                Comment


                • #9
                  Fred, I don't quite follow. Do you mean that even if the "wrong" interface pointer is supplied, the specified method will be retrieved if it exists?

                  Comment


                  • #10
                    I was wrong

                    No, I was wrong on that Chris, and its good you questioned it. I should have corrected that statement; right after your post I checked it and found the same thing you did; PowerBASIC doesn't seem to do much in the way of type checking on interface parameters.

                    Give me a second and I'll post the little piece of code I tried, and show some bizarre results.
                    Fred
                    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                    Comment


                    • #11
                      PowerBASIC doesn't seem to do much in the way of type checking on interface parameters.
                      Which is useful in some cases, such when it references an interface that you have to implement later.
                      Forum: http://www.jose.it-berater.org/smfforum/index.php

                      Comment


                      • #12
                        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, 01:10 PM. Reason: fix terminology
                        Fred
                        "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                        Comment


                        • #13
                          If PB didn't allow relaxed type checking then it will have to add a reinterpret_cast operator such in C++.
                          Forum: http://www.jose.it-berater.org/smfforum/index.php

                          Comment


                          • #14
                            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?

                            Comment


                            • #15
                              me or this has to be a pointer to a class object.
                              Fred
                              "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                              Comment


                              • #16
                                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?
                                Michael Mattias
                                Tal Systems (retired)
                                Port Washington WI USA
                                [email protected]
                                http://www.talsystems.com

                                Comment


                                • #17
                                  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?

                                  Comment


                                  • #18
                                    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; 14 Feb 2009, 11:25 PM.
                                    Fred
                                    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                                    Comment


                                    • #19
                                      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.

                                      Comment


                                      • #20
                                        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.
                                        Fred
                                        "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                                        Comment

                                        Working...
                                        X