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?
Announcement
Collapse
No announcement yet.
passing interfaces - no type checking?
Collapse
X
-
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
-
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!!
Comment
-
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
-
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
-
PowerBASIC doesn't seem to do much in the way of type checking on interface parameters.
Comment
-
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...
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
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()
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 */
Fred
"fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"
Comment
-
If PB didn't allow relaxed type checking then it will have to add a reinterpret_cast operator such in C++.
Comment
-
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
-
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?
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;
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
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
Last edited by Fred Harris; 15 Feb 2009, 12:25 AM.Fred
"fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"
Comment
-
Nicely explained Fred.
Originally posted by Fred Harris View Post...the intent of the design was absolute and total ambiguity.
Originally posted by Fred Harris View PostSub 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.
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
-
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
Comment