Link to home
Start Free TrialLog in
Avatar of sklein
sklein

asked on

Why NO memory leak with AllocSysString() in COM object???

I have build a COM wrapper for an underlying C++ DLL in order to access it with VB6 and .NET, but I am puzzled by why I'm NOT having a memory leak for one of the methods I wrote.

I have run the VB snippet below with 100,000 random strings of length 10,000 (a total of 1GB of strings allocated via AllocSysString(). Whether the FreeString() method is called or not, there is NO difference in the memory footprint of the running process (either memory usage or VM size) -- in both cases, over the entire run, the memory usage is 7.2MBytes and the VM size is 2.8MBytes.

As I understand it, it should be necessary for the VB program to free the memory allocated in the wrapper by AllocSysString(). But it looks like (somehow) that memory is being deallocated without my having to do so.

Here's my question -- can someone explain whether either (1) this test is flawed such that there really isn't a bunch of memory allocated, or (2) some part of VB6 or the COM mechanism is deallocating the memory for me, or (3) something else even more mysterious is happening such that I am not seeing 1GB of memory leak.

Thanks in advance.

COM Wrapper code:
 
STDMETHODIMP CETModule::SetInString(LONG VarNum, BSTR Value)
{
   // Get the specified Input variable
   IVariable *pVar = pModule->GetInVariable(VarNum);
 
   USES_CONVERSION;
   if (pVar == NULL)
      // Invalid VarNum
      return E_INVALIDARG;
   else
      pVar->SetString(W2A(OLE2T(Value)));
 
   return S_OK;
}
 
STDMETHODIMP CETModule::GetOutString(LONG VarNum, BSTR* Result)
{
   // Get the specified Output variable
   IVariable *pVar = pModule->GetOutVariable(VarNum)
 
   if (pVar == NULL)
      // Invalid VarNum
      return E_INVALIDARG;
   else
      // MUST BE DEALLOCATED BY CETModule::FreeString()
      *Result = CString(pVar->GetString()).AllocSysString();
 
   return S_OK;
}
 
STDMETHODIMP CETModule::FreeString(BSTR Value)
{
   try
   {
      SysFreeString(Value);
   }
   catch(...)
   {}
 
   return S_OK;
}
 
 
VB6 Code:
 
   ...
   Dim index as Long
   Dim out As String
   For index = 1 To NumLoops
      ' push a random string into the COM object
      obj.SetInString 0, RandomString(StrSize)
      ' just transfer the input var to the output var
      obj.Calculate
      ' retrieve the random input string
      out = obj.GetOutString(0)
      If chkDealloc = 1 Then obj.FreeString (out)
   Next index
 
   MsgBox "Done.", vbOKOnly
   ...

Open in new window

Avatar of F. Dominicus
F. Dominicus
Flag of Germany image

That's wrong COM programming, COM is base on Reference Counting, and it is not allowed that the client can tamper with the Server data structures. You expose an Interface which does exactly that. If you return a BSTR it is abolutly in the responsibility of the client to free that memory,  it "owns" the String. You must prepare in  Release for freeing the Server BSTR. There must not be any link left from Client data structures to Server data structures.

Regards
Friedrich
Avatar of sklein
sklein

ASKER

Friedrich--

What I am looking for is primarily an understanding of why I am NOT seeing a memory leak. As far as I can tell, the code sample I posted should leak. But it doesn't.

In this situation, reference counting is not an option. My example illustrates the sort of thing I need to do. The strings returned are dynamically created and there are potentially an unlimited number of them created during the COM object's lifetime. But they have a very short lifespan. Strict allocate/deallocate works better (I believe) than reference counting.

My question is still why it doesn't look like I need to do the deallocate!
You have not read my mail properly. You should not give the client access to  the intenal structures  of your server. You always generate fresh BSTR as return value.

Get a decent book about COM and check it. I suggest Essential COM....

You always have reference counting in COM that's the base of IUnknown...

Regards
Friedrich
Avatar of sklein

ASKER

Friedrich--

That is not the issue here. I have implemented a COM object only because the underlying DLL is not easily accessible to VB6 or C#. It is strictly for internal consumption. I don't have the option of rewriting the underlying DLL. So, COM it is.

Second, this COM object will create (and return) an UNLIMITED number of dynamically generated strings. I have no control over that. Reference counting only adds overhead in tracking them.

Third, my question is why I'm NOT seeing memory leaks. Allocate/deallocate is a valid C/C++ style whether or not it is recommended for COM. In this case, it *appears* that the deallocate is not necessary. Does VB6 automatically deallocate [out, retval] BSTRs when they go out of scope or is something else happening here?
If you COM server obey the  rules than you do not have to worry about memory handling in Visual Basic. So if you have the typelibrary at hand VB runtime takes care of "getting" rid of the garbage. You however provided a facility to free a Servers allocated String to Client code and this is a bug.

Friedrich
Avatar of sklein

ASKER

Friedrich--

I added the FreeString call because the sources I consulted indicated that every AllocSysString() call must be matched by an explicit SysFreeString() call. If that is not actually necessary in this case, I'd be happy to delete the (unnecessary) FreeString() method.  I am trying to determine if I really don't need to do an explicit deallocation, because none of the documentation I've seen makes that claim.

Normally, the VB runtime deallocates strings that it allocates. I have not seen the claim that it deallocates strings that are passed to it thru a COM interface. Perhaps that is what is happening, because (as I've indicated) I don't see any memory leaks.

So, are you making the claim that the combination of the VB Runtime and the COM interface result in the strings being automatically deallocated? If so, does that also apply to .NET + COM (per my original question)?
Avatar of sklein

ASKER

Friedrich--

As an alternative strategy, if I specify that the string returned by a GetOutString() call must be copied before the next GetOutString() call, then the protocol can be that GetOutString() deallocates the *previous* allocated string before allocating the next string. Then, there is never more than one string allocated. The destructor also knows what to clean up. Ugly, but guaranteed not to leak. Thoughts?
The COM rule are strict and they are known in Visual Basic too. What you have to do is keep track  of the memory in  C/C++ code. So indeed ifyou get back a BSTR as out you know that it's your respnsibility to free it.
The typelibrary informs Visual Basic about that and so you do not have to call the SysFreeString in Visual Basic
If you like you can help the runtime with  setting the returned value to nil.

You still have you memory leak  if you do  not free the allocates memory on the server  side, and this is much more serious.  You generte  COM object and every time you set the string you are  leaking memory, for  that  you have to keep track of the reference count and provide a clean up in the Release Element of  the  IUnknown interface.

That's absolutly necessary  to get  right if you do  use COM...

COM and net have interoperability laysers which should be handled in .NET as in the visual basic runtime. But you  still have to provide the freein of  required resources in the Server. That's what I wrote, Clien and Server must no hamper with the data of the other side. This just can go wrong

For a book  about COM and net check .NET and COM the Complete Interoperability Guide

Regards
Friedrich

Regards
Friedrich
Avatar of sklein

ASKER

Friedrich--

Microsoft doesn't agree with you. Please see the MSDN article: "Allocating and Releasing Memory for a BSTR" (http://msdn.microsoft.com/en-us/library/xda6xzx7(VS.71).aspx)

The intro section says:

When you create BSTRs and pass them between COM objects, you must take care in treating the memory they use in order to avoid memory leaks. When a BSTR stays within an interface, you must free its memory when you are done with it. However, when a BSTR passes out of an interface, the receiving object takes responsibility for its memory management.

The last sentence says that it is the client who is responsible for memory management in this specific case.

So, the model I am following is specifically per Microsoft recommendations. And back to my original question (which is still unanswered), why am I not seeing a memory leak when I don't do the deallocate? Since my code is consistent with Microsoft recommendations, can we stop discussing the need to free the memory on the server side?

Following is the specific Microsoft example from the above MSDN article which looks exactly like my GetOutString().


When you implement a function that returns a BSTR, allocate the string but do not free it. The receiving function releases the memory. For example:
 
// Example shows using MFC's 
// CString::AllocSysString
 
//...
HRESULT CMyClass::get_StatusText( BSTR * pbstr )
{
 
   try
   {
      //m_str is a CString in your class
      *pbstr = m_str.AllocSysString( );
      }
   catch (...)
   {
      return E_OUTOFMEMORY;
   }
 
// The client is now responsible for freeing pbstr.
return( S_OK );
}
//...

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of F. Dominicus
F. Dominicus
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of sklein

ASKER

Friedrich--
I think I agree with your analysis that the VB6 runtime is doing the deallocate call. That would be consistent with behavior for strings allocated in VB itself (it doesn't matter whether the string is allocated within VB or an arbitrary COM object).
This does explain why I see no memory leaks even though allocating more than 1GB of strings. Thanks.