Releasing/Freeing Objects Received In Event Procedures

Started by Frederick J. Harris, August 17, 2015, 08:03:58 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

Jose:

     Would you look at the NavigateComplete2 member of the DWebBrowserEvents2 dispinterface?  The IDL looks like so...


[id(0x000000fc), helpstring("Fired when the document being navigated to becomes visible and enters the navigation stack.")] void NavigateComplete2
(
  [in] IDispatch* pDisp,
  [in] VARIANT* URL
);


Note that the 2nd parameter is a pointer (address of) to a VARIANT, which, it just so happens, contains  a BSTR.  Using your TypeLib Browser one comes up with code such as this...


' =====================================================================================
   METHOD NavigateComplete2 <252> _
   ( _
     BYVAL prm_pDisp AS IDispatch, _                    ' [in] *pDisp VT_DISPATCH <IDispatch>
     BYREF prm_URL AS VARIANT _                         ' [in] *URL VT_VARIANT <Variant>
   )                                                    ' void
     ' *** Insert your code here ***
     #If %Def(%Debug)
         Print #fp, "Entering NavigateComplete2()"
     #Else
         MsgBox("Called NavigateComplete2()")
     #EndIf
     #If %Def(%Debug)
         Print #fp, "  Variant$(prm_URL) = " Variant$(prm_URL)
         Print #fp, "Leaving NavigateComplete2()"
     #EndIf
   END METHOD
   ' =====================================================================================


Now the part that has me confused and wondering is whether a SysFreeString() needs to be called on that BSTR contained in the Variant.  In my C++ coding when I'm doing IDispatch work with dispinterfaces I always call VariantClear() on BSTRs I receive in a Variant as a parameter to the IDispatch::Invoke() call.  I'm pretty sure this is all correct and necessary to not create memory leaks.  In the case above the code works whether I insert a VariantClear(prm_URL) before exiting the function or not.  I'm not detecting any crashes either way, its just that I fear I may be leaking memory if I don't manually release that BSTR contained in the Variant. 

Now I have to admit this question arises out of C++ work I'm doing, but I think the question I have is a general one pertaining to OLE/COM regardless of what programming language one is using. 

I know that if one is using PowerBASIC and parameters to functions are PowerBASIC Strings (Wstrings), we don't have to worry about allocating and deallocating BSTRs, as Bob is taking care of this.  But what about a case such as above where a BSTR is 'hidden' in a Variant?  Now I suspect Variant$() might involve the creation of a BSTR (a PowerBASIC Wstring) and the release of that Wstring at function termination.  But what about the original BSTR passed in through that Variant?  Does that need to be freed?  What about just your empty stub function right out of your TypeLib Browser's output...


' =====================================================================================
   METHOD NavigateComplete2 <252> _
   ( _
     BYVAL prm_pDisp AS IDispatch, _                    ' [in] *pDisp VT_DISPATCH <IDispatch>
     BYREF prm_URL AS VARIANT _                         ' [in] *URL VT_VARIANT <Variant>
   )                                                    ' void
     ' *** Insert your code here ***
     
   END METHOD
' =====================================================================================


I'm guessing you are going to tell me that the empty stub is correct and nothing needs to be done with either parameter, that is, the IDispatch parameter, or the Variant containing the BSTR.  What are your thoughts on this Jose?  Could you elaborate a little bit?

Can a general rule come out of this such as if the parameter has an [in] IDL attribute nothing needs to be done in the receiving events interface to free it?

Frederick J. Harris

I just tested the VariantClear() function call from within the above NavigateComplete() event procedure, and I'm getting all S_OK returns on trhe calls.  But I have reservations on whether these VariantClear() calls are necessary.

Frederick J. Harris

#2
I think I have my answer figurred out.  Recall that article I provided a link to here ...

"Strings The OLE Way"

https://social.msdn.microsoft.com/Forums/en-US/88f6f6ce-46cb-4d19-8b7b-c92f5f34775c/bstrings

some time back?  It was kind of humorous as I recall?  Anyway, the writer's Rule #4 is as follows...

Quote
Rule 4: You do not own any BSTR passed to you by value.

The only thing you can do with such a string is copy it or pass it on to other functions that won't modify it. The caller owns the string and will dispose of it according to its whims. A BSTR passed by value looks like this in C++:

void DLLAPI TakeThisStringAndCopyIt(BCSTR bsIn);

The BCSTR is a typedef that should have been defined by OLE, but wasn't. I define it like this in OleType.H:

typedef const wchar_t * const BCSTR;

If you declare input parameters for your functions this way, the C++ compiler will enforce the law by failing on most attempts to change either the contents or the pointer.

The Object Description Language (ODL) statement for the same function looks like this:

void WINAPI TakeThisStringAndCopyIt([in] BCSTR bsIn);

The BCSTR type is simply an alias for BSTR because MKTYPLIB doesn't recognize const. The [in] attribute allows MKTYPLIB to compile type information indicating the unchangeable nature of the BSTR. OLE clients such as Visual Basic will see this type information and assume you aren't going to change the string. If you violate this trust, the results are unpredictable.

So that's basically the case I think with that pointer to a Variant parameter.  I have a pointer to some entity allocated within the ieframe.dll, but that entity is a Variant - not a BSTR.  However, the Variant contains a pointer to the 1st character of a string.  The underlying BSTR however is owned by ieframe.dll - not be me.  I can't reallocate or free it.  The [in] IDL attribute reflects this situation.  Only [out] or perhaps [in, out] specified parameters need handling by my code - if I'm right about this.  Confusing stuff!

José Roca

PB will call VariantClear if the variant is passed by value. If it is passed by reference, then it is the responsability of the caller to do it.

If the caller wants you to free something, it must tell you explicitily in the documentation.

José Roca


' =====================================================================================
   METHOD NavigateComplete2 <252> _
   ( _
     BYVAL prm_pDisp AS IDispatch, _                    ' [in] *pDisp VT_DISPATCH <IDispatch>
     BYREF prm_URL AS VARIANT _                         ' [in] *URL VT_VARIANT <Variant>
   )                                                    ' void
     ' *** Insert your code here ***
     
   END METHOD
' =====================================================================================


Quote
I'm guessing you are going to tell me that the empty stub is correct and nothing needs to be done with either parameter, that is, the IDispatch parameter, or the Variant containing the BSTR.  What are your thoughts on this Jose?  Could you elaborate a little bit?

Because the IDispatch pointer is being passed by value, PB will call AddRef at the beginning of the method and Release at the end. If it was passed by reference, it will do nothing.

Because the Variant is being passed by reference, PB will do nothing with it.

In fact, if you don't need to use this method, you can remove the empty stub. This can only be done with IDispatch interfaces, because what it counts is the DISPID (<252>), not the name or the position. With low-level IUnknown interfaces you need to implement all the event methods and in the correct order, no matter if you are going to do something in them or not.

This is because, before calling the method, Automation calls GetIDsOfNames and if it does not receive the wanted answer then it does not call the method. With IUnknown interfaces, the method is called directly according its address in the VTable for the class.



Frederick J. Harris

Thanks a lot Jose.  I'm really trying to understand it but I believe I've got it.  I'll probably have to sleep on it though.  I think I'm making it more difficult than it is.  Looks like some free/release calls that I was suspecting I needed to make aren't necessary.  In the case of this C++ output log code...


void CEventSink::NavigateComplete2(IDispatch* pDisp, VARIANT* pVariantURL)
{
#ifdef MyDebug
fprintf(fp,"  Entering CEventSink::NavigateComplete2()\n");
fwprintf(fp,L"    %s\n",pVariantURL->bstrVal);   //VARIANT* pvarVal;  // VT_BYREF|VT_VARIANT. 
fprintf(fp,"  Leaving CEventSink::NavigateComplete2()\n\n");
#endif
}


...that has to be correct if this PowerBASIC code is correct...


' =====================================================================================
   METHOD NavigateComplete2 <252> _
   ( _
     BYVAL prm_pDisp AS IDispatch, _                    ' [in] *pDisp VT_DISPATCH <IDispatch>
     BYREF prm_URL AS VARIANT _                         ' [in] *URL VT_VARIANT <Variant>
   )                                                    ' void
     ' *** Insert your code here ***
     
   END METHOD
' =====================================================================================


Quote
Because the IDispatch pointer is being passed by value, PB will call AddRef at the beginning
of the method and Release at the end. If it was passed by reference, it will do nothing.

That's exactly the effect I've noted many times and caught in output logs where I wrote both
the server and client code with low level COM and was using a PB client.  I hadn't associated it
with my question until you mentioned it.  Thanks for bringing it up.

Yep, I've got to digest this! :)