views:

140

answers:

1

There's a bug in delphi 6 which you can find some reference online for when you import a tlb the order of the parameters in an event invocation is reversed. It is reversed once in the imported header and once in TServerEventDIspatch.Invoke.

you can find more information about it here: http://cc.embarcadero.com/Item/16496

somewhat related to this issue there appears to be a memory leak in TServerEventDispatch.Invoke with a parameter of a Variant of type Var_Array (maybe others, but this is the more obvious one i could see). The invoke code copies the args into a VarArray to be passed to the event handler and then copies the VarArray back to the args after the call, relevant code pasted below:

  // Set our array to appropriate length
  SetLength(VarArray, ParamCount);

  // Copy over data
  for I := Low(VarArray) to High(VarArray) do
    VarArray[I] := OleVariant(TDispParams(Params).rgvarg^[I]);

  // Invoke Server proxy class
  if FServer <> nil then FServer.InvokeEvent(DispID, VarArray);

  // Copy data back
  for I := Low(VarArray) to High(VarArray) do
    OleVariant(TDispParams(Params).rgvarg^[I]) := VarArray[I];

  // Clean array
  SetLength(VarArray, 0);

There are some obvious work-arounds in my case: if i skip the copying back in case of a VarArray parameter it fixes the leak. to not change the functionality i thought i should copy the data in the array instead of the variant back to the params but that can get complicated since it can hold other variants and seems to me that would need to be done recursively.

Since a change in OleServer will have a ripple effect i want to make sure my change here is strictly correct.

can anyone shed some light on exactly why memory is being leaked here? I can't seem to look up the callstack any lower than TServerEventDIspatch.Invoke (why is that?)

I imagine that in the process of copying the Variant holding the VarArray back to the param list it added a reference to the array thus not allowing it to be release as normal but that's just a rough guess and i can't track down the code to back it up.

Maybe someone with a better understanding of all this could shed some light?

+1  A: 

Interestingly enough, i think the solution was in the link i provided in the question, but i didn't understand the implication until digging into it a bit more.

A few things to clarify:

  1. When a variant containing an array is assigned from the VarArray back to the Params, a copy is made. This is explained within the delphi help pages.
  2. Assigning over an existing variant will definitely free the memory associated with the previous value of the Variant, so the array contained by the variant prior to the assignment would have been freed on assignment.
  3. VarClear will free the memory associated with the variant and tests show that a VarClear on the variant hold in the Params after the assignment will in fact remove the memory leak.

It appears that the issue has to do with the indiscriminate write back of param values. The particular event i'm dealing with does not have any parameters marked as var, so the COM object is not expecting changes to invocation param to free new memory that's been allocated.

Roughly the COM object allocated an array, invokes the event and then frees it's own memory after the event. The Oleserver however allocates some new memory when it copied the array param back to the param list which the COM object wouldn't even know about since it didn't pass anything by reference and is not expecting changes to its params. There must be some additional marshalling magic there that i'm neglecting, if anyone knows the details i'd definitely be curious.

The TVariantArg's vt field has a flag to indicate whether it is passed by value or reference. As far as i can discern, we should only be copying the value back if the param is marked as being passed by reference.

Furthermore it may be necessary to do more than just assign the variant if this is in fact pass by reference, although that could be taken care of by the marshalling, still not sure about this part.

The solution for now is to change the code to:

if ((TDispParams(Params).rgvarg^[I].vt and VT_BYREF) <> 0) then begin
  OleVariant(TDispParams(Params).rgvarg^[I]) := VarArray[I];
end;
Mike Davis