Vtable different between C++ and Freebasic? [SOLVED]

General FreeBASIC programming questions.
Post Reply
stephanbrunker
Posts: 62
Joined: Nov 02, 2013 14:57

Vtable different between C++ and Freebasic? [SOLVED]

Post by stephanbrunker »

Hello,

I'm trying to implement some Windows Interfaces in Freebasic, using virtual methods in the same manner as windows does:

Code: Select all

Type JUnknown EXTENDS OBJECT	'IUnknown Interface
        
		Declare Constructor()
		Declare Destructor()
		
		'Methods:
		'IUnknown Interface:	
		Declare Virtual Function QueryInterface (ByVal iid As REFIID, ByVal ppvObject As Any Ptr Ptr) As HRESULT
		Declare Virtual Function AddRef () As ULong
		Declare Virtual Function Release () As ULong
		
		'member variables:
		As Long		 m_lRefCount 				

End Type
and the second one:

Code: Select all

Type JDataObject EXTENDS JUnknown

	Declare Constructor(fmtetc () As FORMATETC , stgmed () As STGMEDIUM)
	Declare Destructor()

	'Methods:
	'IUnknown Interface:		
	Declare Virtual Function QueryInterface ( ByVal iid As IID Ptr, ByVal ppvObject As PVOID Ptr) As HRESULT

	'IDataObject Interface:
	Declare Virtual Function GetData ( ByVal pfmtetc As FORMATETC Ptr, ByVal pstgmed As STGMEDIUM Ptr) As HRESULT
	Declare Virtual Function GetDataHere ( ByVal pfmtetc As FORMATETC Ptr, ByVal pstgmed As STGMEDIUM Ptr) As HRESULT
	Declare Virtual Function QueryGetData (ByVal pfmtetc As FORMATETC Ptr) As HRESULT
	Declare Virtual Function GetCanonicalFormatEtc ( ByVal pfmtetc As FORMATETC Ptr, ByVal pfmtetc2 As FORMATETC Ptr) As HRESULT
	Declare Virtual Function SetData ( ByVal pfmtetc As FORMATETC Ptr, ByVal pstgmed As STGMEDIUM Ptr, ByVal As BOOL) As HRESULT
	Declare Virtual Function EnumFormatEtc ( ByVal dwDirection As DWORD, ByVal ppEnumFmtetc As JEnumFORMATETC Ptr Ptr) As HRESULT
	Declare Virtual Function DAdvise ( ByVal pfmtetc As FORMATETC Ptr, ByVal advf As DWORD, ByVal pAdvSink As IAdviseSink Ptr, ByVal pDwConnection As PDWORD) As HRESULT
	Declare Virtual Function DUnadvise ( ByVal  dwConnection As DWORD) As HRESULT
	Declare Virtual Function EnumDAdvise ( ByVal ppEnumAdvise As IEnumSTATDATA Ptr Ptr) As HRESULT
	
	'member variables:
	As Integer m_nNumFormats	
	As STGMEDIUM Ptr m_pstgmed = Any
	As FORMATETC Ptr m_pfmtetc = Any
	
End Type
but unfortunately, when I send the Pointer to my IDataobject to the DoDragDrop function, it calls correct the IUnknown Members, but as far as I could determinate, Windows then calls GetGata, but this seems to be wrong, because the parameters don't make sense. The only explanation I've at the moment is that the vtable is different in C+ and Freebasic, so that the pointers to the functions are offset and another function should be called. Couriously, if I get a pointer to a Windows-generated dataobject and call GetData with the same TYPE, I get the correct answer.

I've a fallback in form of a UDT without Virtual Functions and derivated Types, it contains static functions and put the pointers to them into the first member of the udt (the IdataObjectVtbl type in the Windows header files):

Code: Select all

Type CDataObject 'implements Custom IDataObject Interface

	'implements IDataObject@CDataObject
	As IDataObject m_DataObject = Any

	Declare Constructor(fmtetc () As FORMATETC , stgmed () As STGMEDIUM)
	Declare Destructor()
	
	'Methods:
	'IUnknown Interface:		
	Declare Static Function QueryInterface (ByVal pData as IDataObject ptr, byval iid As IID ptr, byval ppvObject As PVOID ptr) as HRESULT
	Declare Static Function AddRef (byval pData As IDataObject ptr) as ULONG
	Declare Static Function Release (ByVal pData as IDataObject ptr) as ULONG
	
	'IDataObject Inteface:
	Declare Static Function GetData (byval pData As IDataObject ptr, byval pfmtetc As FORMATETC ptr, byval pstgmed As STGMEDIUM ptr) as HRESULT
	Declare Static Function GetDataHere (ByVal pData as IDataObject ptr, byval pfmtetc As FORMATETC ptr, ByVal pstgmed as STGMEDIUM ptr) as HRESULT
	Declare Static Function QueryGetData (ByVal pData as IDataObject ptr, ByVal pfmtetc as FORMATETC ptr) as HRESULT
	Declare Static Function GetCanonicalFormatEtc (ByVal pData as IDataObject ptr, ByVal pfmtetc as FORMATETC ptr, byval pfmtetc2 As FORMATETC ptr) as HRESULT
	Declare Static Function SetData (ByVal pData as IDataObject ptr, ByVal pfmtetc as FORMATETC ptr, byval pstgmed As STGMEDIUM ptr, byval as BOOL) as HRESULT
	Declare Static Function EnumFormatEtc (byval pData As IDataObject ptr, ByVal dwDirection as DWORD, ByVal ppEnumFmtetc As IEnumFORMATETC ptr ptr) as HRESULT
	Declare Static Function DAdvise (byval pData As IDataObject ptr, ByVal pfmtetc as FORMATETC ptr, byval advf As DWORD, byval pAdvSink As IAdviseSink ptr, ByVal pDwConnection as PDWORD) as HRESULT
	Declare Static Function DUnadvise (byval pData As IDataObject ptr, byval  dwConnection As DWORD) as HRESULT
	Declare Static Function EnumDAdvise (byval pData As IDataObject ptr, ByVal ppEnumAdvise as IEnumSTATDATA ptr ptr) as HRESULT
	
	'helper Functions:
	Declare Static Function LookupFormatEtc(ByVal pData As IDataObject Ptr, ByVal pfmtetc As FORMATETC ptr) As Integer
	Declare Static Function dupmem(ByVal hMem As HGLOBAL) As HGLOBAL
	
	'member variables:
	As Integer m_lRefCount
	As Integer m_nNumFormats	
	As STGMEDIUM Ptr m_pstgmed = Any
	As FORMATETC Ptr m_pfmtetc = Any
	
End Type

Constructor CDataObject (fmtetc() As FORMATETC, stgmed () As STGMEDIUM)	
	Static As IDataObjectVtbl vtbl = _
		( _
		@CDataObject.QueryInterface, _
		@CDataObject.AddRef, _
		@CDataObject.Release, _
		@CDataObject.GetData, _
		@CDataObject.GetDataHere, _
		@CDataObject.QueryGetData, _
		@CDataObject.GetCanonicalFormatEtc, _
		@CDataObject.SetData, _
		@CDataObject.EnumFormatEtc, _
		@CDataObject.DAdvise, _
		@CDataObject.DUnadvise, _
		@CDataObject.EnumDAdvise _
		)
	m_DataObject.lpVtbl = @vtbl	

	'initalize member variables	
	m_lRefCount  = 1
	
	'get FORMATETC/STGMEDIUM data
	Dim count As ULong = UBound(fmtetc)+1	
	m_nNumFormats = count
	m_pstgmed = New STGMEDIUM[count]
	m_pfmtetc = New FORMATETC[count]
	Dim i As Integer
	For i = 0 To count - 1
		m_pstgmed[i] = stgmed(i)
		m_pfmtetc[i] = fmtetc(i)
	Next i
	Print "CDataObject::Constructor [";m_nNumFormats; " Formats]"	
End Constructor
But this is not really better. It works, but there are a lot of wrong calls to the first three members instead, IDropSource.QueryInterface gets called multiple times but it's obvious not the interface the caller expects. That works obviously better in the first version. Then IDataObject::EnumFormatEtc get called, but this is also dubious because normally IDropTarget::DragEnter should be the next Method to be called. Has anyone an answer what's wrong?
Last edited by stephanbrunker on Mar 12, 2014 17:19, edited 1 time in total.
dkl
Site Admin
Posts: 3235
Joined: Jul 28, 2005 14:45
Location: Germany

Re: Vtable different between C++ and Freebasic?

Post by dkl »

It looks correct to me:

Code: Select all

__ZTVN8JUNKNOWNE:
.long 0 (null pointer)
.int __ZTSN8JUNKNOWNE (JUnknown RTTI table)
.int __ZN8JUNKNOWN14QUERYINTERFACEEP4GUIDPPv@12 (JUnknown.QueryInterface)
.int __ZN8JUNKNOWN6ADDREFEv@4 (JUnknown.AddRef)
.int __ZN8JUNKNOWN7RELEASEEv@4 (JUnknown.Release)

...

__ZTVN11JDATAOBJECTE:
.long 0 (null pointer)
.int __ZTSN11JDATAOBJECTE (JDataObject RTTI table)
.int __ZN11JDATAOBJECT14QUERYINTERFACEEP4GUIDPPv@12 (JDataObject.QueryInterface)
.int __ZN8JUNKNOWN6ADDREFEv@4 (JUnknown.AddRef)
.int __ZN8JUNKNOWN7RELEASEEv@4 (JUnknown.Release)
.int __ZN11JDATAOBJECT7GETDATAEP9FORMATETCP9STGMEDIUM@12 (JDataObject.GetData)
.int __ZN11JDATAOBJECT11GETDATAHEREEP9FORMATETCP9STGMEDIUM@12 (JDataObject.GetDataHere)
.int __ZN11JDATAOBJECT12QUERYGETDATAEP9FORMATETC@8 (JDataObject.QueryGetData)
.int __ZN11JDATAOBJECT21GETCANONICALFORMATETCEP9FORMATETCS1_@12 (JDataObject.GetCanonicalFormatEtc)
.int __ZN11JDATAOBJECT7SETDATAEP9FORMATETCP9STGMEDIUMi@16 (JDataObject.SetData)
.int __ZN11JDATAOBJECT13ENUMFORMATETCEjPPv@12 (JDataObject.EnumFormatEtc)
.int __ZN11JDATAOBJECT7DADVISEEP9FORMATETCjP11IADVISESINKPj@20 (JDataObject.DAdvise)
.int __ZN11JDATAOBJECT9DUNADVISEEj@8 (JDataObject.DUnadvise)
.int __ZN11JDATAOBJECT11ENUMDADVISEEPP13IENUMSTATDATA@8 (JDataObject.EnumDAdvise)
Since the object's vptr will be initialized to point to the 3rd element (@vtable + 2) it looks to me like the layout matches the IDataObject vtable declaration in the MinGW-w64 headers. The methods are in the correct order, they use stdcall, the parameters/results look correct, there are no other unrelated entries in between.
stephanbrunker
Posts: 62
Joined: Nov 02, 2013 14:57

Re: Vtable different between C++ and Freebasic?

Post by stephanbrunker »

Thank you for looking - the error has to be somewhere else, because the IDropTarget Interface works correctly and get it's methods called in the right order. My next guess is a maybe incorrect handling of the QueryInterface calls. Before DoDragDrop calls one of the interfaces' methods, it confirms with QueryInterface and maybe the ppvobject return value isn't correct.

Edit:
Small steps: If I get a Pointer to an IDataObject Interface, the type works correct and i can call every method. But other way round, afert calling JDataobject:QueryInterface, Windows calls next JDataobject:GetData but meant also QueryInterface, because the Argument is a REFIID. The Question is why, because I answer the previous call to QueryInterface with @this:

Code: Select all

Function JDataObject.QueryInterface ( ByVal iid As IID Ptr,ByVal ppvObject As Any Ptr Ptr) As HRESULT
	Print "IDataObject::QueryInterface" 
	'if it's the right format return a pointer to the interface 
	If IsEqualIID ( iid, @IID_IUnknown) Or IsEqualIID( iid, @IID_IDataObject) Then 
		AddRef()
		*ppvObject = @This
		Print "S_OK"
		Return S_OK
	Else
		*ppvObject = NULL
		Print "E_NOINTERFACE"
		Return E_NOINTERFACE
	End If
	
End Function
or is simply the row *ppvObject = @This wrong? The MSDN says:
ppvObject [out]
The address of a pointer variable that receives the interface pointer requested in the riid parameter. Upon successful return, *ppvObject contains the requested interface pointer to the object. If the object does not support the interface, *ppvObject is set to NULL.
fxm
Moderator
Posts: 12106
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Vtable different between C++ and Freebasic?

Post by fxm »

Just a small remark from your different posts (I don't know if there is an impact on the bug):
JDataObject.QueryInterface(...) does not override JUnknown.QueryInterface(...) (no polymorphism despite of virtual declaration) because the parameter signature is different between the two declarations!
dkl
Site Admin
Posts: 3235
Joined: Jul 28, 2005 14:45
Location: Germany

Re: Vtable different between C++ and Freebasic?

Post by dkl »

I think after expanding the type aliases (typedefs) they're the same, that's why fbc inserts the overrides into the vtable as expected.

That QueryInterface() looks good to me too (though I have no experience with COM).
fxm
Moderator
Posts: 12106
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Vtable different between C++ and Freebasic?

Post by fxm »

Yes.

But perhaps the descriptor should it be virtual with interfaces?
(I apologize in proposing ideas can be ridiculous!)
stephanbrunker
Posts: 62
Joined: Nov 02, 2013 14:57

Re: Vtable different between C++ and Freebasic?

Post by stephanbrunker »

Thank you all, but I've got the problem fixed! (and it was a hard one to find!):

The problem was the IsEqualIID - Function in QueryInterface. Following the description, it should be returning TRUE or FALSE. But that isn't correct. After printing every IID's in the communication and comparing manually and also printing the result of that function, I found out that the function returns without any obvious reason -2 or 0 for false and -1 for true. Because of this behaviour, Freebasic didn't recogize any value as true (def'ed to 1) in

Code: Select all

	If (IsEqualIID ( iid, @IID_IUnknown) = TRUE) Or (IsEqualIID( iid, @IID_IDropSource) = TRUE) Then 
and every -2 return as false true value in

Code: Select all

	If IsEqualIID ( iid, @IID_IUnknown) Or IsEqualIID( iid, @IID_IDropSource) Then 
it only gives the correct return value when written in actual numbers:

Code: Select all

	If (IsEqualIID ( iid, @IID_IUnknown) = -1) Or (IsEqualIID( iid, @IID_IDropSource) = -1) Then 
I fairly don't have a clue what DoDragDrop wants to call when every QueryInterface Call gets answered with S_OK in one case and I understand that the operation is aborted when every call gets negated, but it was as simple as that. (And mostly I'm angering myself because I had a working version with memcmp instead of IsEqualIID in the previous, non-inherited version of the interfaces and it was one more part which I changed when rewriting.

But I appreciate the help and if I don't bump against unsolvable problems, i'm going to post and describe the code in a Tutorial at http://www.freebasic-portal.de , also including a version with IStream and CF_FILEDESCRIPTOR for virtual Files.
dkl
Site Admin
Posts: 3235
Joined: Jul 28, 2005 14:45
Location: Germany

Re: Vtable different between C++ and Freebasic? [SOLVED]

Post by dkl »

Hmm, that makes sense. I think FB's IsEqualIID()/IsEqualGUID() declaration is buggy:

inc/win/objbase.bi

Code: Select all

'' old, wrong:
#define IsEqualGUID(rguid1, rguid2) (not memcmp(rguid1, rguid2, sizeof(GUID)))
'' it should be:
#define IsEqualGUID(rguid1, rguid2) (-(memcmp(rguid1, rguid2, sizeof(GUID)) = 0))
TJF
Posts: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Re: Vtable different between C++ and Freebasic? [SOLVED]

Post by TJF »

Better use

Code: Select all

#DEFINE IsEqualGUID(rguid1, rguid2) (IIF(memcmp(rguid1, rguid2, sizeof(GUID)), 0, 1))
aloberoger
Posts: 507
Joined: Jan 13, 2009 19:23

Re: Vtable different between C++ and Freebasic? [SOLVED]

Post by aloberoger »

JEnumFORMATETC definition is Missing
In the other hand you try to mix virtual methods with vtbl C++ don't do that, C++ use only Virtual methods
take a look on my works INSIDE ACTIVEX WITH FREEBASIC
Post Reply