Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2014-10

Reference counting interface objects

7 messages · 3 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Adriaan van Os

Section 7.6 in the FreePascal Language Reference discusses automatic reference counting for COM interfaces. It details assignments to variables and temporary variables. But the section doesn't mention parameter passing. What happens when interfaces are passed to functions, procedures and methods ?

From reading Delphi documentation and by experimenting a bit, there seems to be a great difference between value and CONST parameters and between VAR and OUT parameters. I suggest to extend the manual to explain this in detail.

For example, I have an interface IMFMediaEventGenerator (from Windows MediaFoundation) with a method GetEvent

   type
     IMFMediaEventGenerator = interface( IUnknown) ...
	....
          function GetEvent( dwFlags: DWORD; out ppEvent: IMFMediaEvent): HResult; stdcall;

Now, suppose I am calling GetEvent in a loop

       theEvent := nil;
       theEventType := MEUnknown;
       theResult := S_OK;
       while ( theEventType  MESessionClosed) and ( theResult >= S_OK) do
       begin
         theResult := theSession.GetEvent( 0, theEvent);
	...

Now, it seems (correct me if I am wrong) that theEvent doesn't need to be manually released, because it has been declared as an OUT parameter rather than a VAR parameter. So, if this is true, this information is crucial when writing interface bindings.

In general, I am not a proponent of "compiler magic" because it confuses things, puts it under the table. I would certainly welcome a compiler option to NOT auto-refcount.

Regards,

Adriaan van Os

Reply by email

Michael Van Canneyt
to Adriaan van Os
Adriaan van Os wrote 9 lines
Section 7.6 in the FreePascal Language Reference discusses automatic reference counting for COM interfaces. It details assignments to variables and temporary variables. But the section doesn't mention parameter passing. What happens when interfaces are passed to functions, procedures and methods ? From reading Delphi documentation and by experimenting a bit, there seems to be a great difference between value and CONST parameters and between VAR and OUT parameters. I suggest to extend the manual to explain this in detail.

That will require the compiler devs explaining in detail how it works, because I haven't got a clue. AFAIK var, const and out behave the same where ref counting is concerned, with 'value' parameters being the only difference.

Show quoted text 24 lines
For example, I have an interface IMFMediaEventGenerator (from Windows MediaFoundation) with a method GetEvent type IMFMediaEventGenerator = interface( IUnknown) ... .... function GetEvent( dwFlags: DWORD; out ppEvent: IMFMediaEvent): HResult; stdcall; Now, suppose I am calling GetEvent in a loop theEvent := nil; theEventType := MEUnknown; theResult := S_OK; while ( theEventType MESessionClosed) and ( theResult >= S_OK) do begin theResult := theSession.GetEvent( 0, theEvent); ... Now, it seems (correct me if I am wrong) that theEvent doesn't need to be manually released, because it has been declared as an OUT parameter rather than a VAR parameter. So, if this is true, this information is crucial when writing interface bindings.

Indeed.

In general, I am not a proponent of "compiler magic" because it confuses things, puts it under the table. I would certainly welcome a compiler option to NOT auto-refcount.

Talk about going against the spirit of the times :-)

Michael.

Reply by email

Jonas Maebe
to Adriaan van Os: Section 7.6 in the FreePascal Language Reference discusses automatic reference counting…
Adriaan van Os wrote
Now, it seems (correct me if I am wrong) that theEvent doesn't need to be manually released, because it has been declared as an OUT parameter rather than a VAR parameter. So, if this is true, this information is crucial when writing interface bindings.

Nothing ever needs to be manually released, regardless of whether it's a VAR or OUT parameter. That's the whole point of automatic reference counting)

Regarding the effects of the various qualifiers:
* nothing (value): the reference count of the parameter is increased (I forgot whether it's the caller or callee that does it) on entry and decreased on exit
* OUT: the reference count of the value that's passed in is decreased by 1, and the variable that's passed into the procedure is initialized to "empty" (nil, but that's an implementation detail)
* VAR: nothing happens to the reference count. A reference to the original variable is passed in, and changing it or reading it has exactly the same effect as changing/reading the original variable.
* CONST: this is the only tricky one. Again nothing happens to the reference count, but because you can pass non-lvalues here. In particular, you can pass a class implementing an interface rather than the interface itself, which can cause the class to be freed unexpectedly. See the example at http://docwiki.embarcadero.com/RADStudio/XE7/en/Using_Reference_Counting . The same can happen with functions returning a class instance passed to a function expecting a CONST interface parameter. There is an open bug report about adding a warning for it: http://bugs.freepascal.org/view.php?id=19503

Jonas

Reply by email

Adriaan van Os
to Jonas Maebe
Jonas Maebe wrote 11 lines
On 16 Oct 2014, at 12:35, Adriaan van Os wrote: > Now, it seems (correct me if I am wrong) that theEvent doesn't need to > be manually released, because it has been declared as an OUT parameter > rather than a VAR parameter. So, if this is true, this information is > crucial when writing interface bindings. Nothing ever needs to be manually released, regardless of whether it's a VAR or OUT parameter. That's the whole point of automatic reference counting)

I agree for pure Pascal code, where a parameter can be OUT or VAR and automatic release will work in both cases. However, I disagree for interfacing with external interfaces. It must be clear for the bindings-writer that VAR in the bindings instead of (what should have been) OUT can cause a memory leak. This is tricky enough that it deserves an explanation in the manual. For example, I think (from Windows MediaFoundation)

type
     IMFMediaEventGenerator = interface( IUnknown) ...
     ....
          function GetEvent( dwFlags: DWORD; OUT ppEvent: IMFMediaEvent): HResult; stdcall;

is correct, whereas

          function GetEvent( dwFlags: DWORD; VAR ppEvent: IMFMediaEvent): HResult; stdcall;

will leak memory.

Show quoted text 21 lines
Regarding the effects of the various qualifiers: * nothing (value): the reference count of the parameter is increased (I forgot whether it's the caller or callee that does it) on entry and decreased on exit * OUT: the reference count of the value that's passed in is decreased by 1, and the variable that's passed into the procedure is initialized to "empty" (nil, but that's an implementation detail) * VAR: nothing happens to the reference count. A reference to the original variable is passed in, and changing it or reading it has exactly the same effect as changing/reading the original variable. * CONST: this is the only tricky one. Again nothing happens to the reference count, but because you can pass non-lvalues here. In particular, you can pass a class implementing an interface rather than the interface itself, which can cause the class to be freed unexpectedly. See the example at http://docwiki.embarcadero.com/RADStudio/XE7/en/Using_Reference_Counting . The same can happen with functions returning a class instance passed to a function expecting a CONST interface parameter. There is an open bug report about adding a warning for it: http://bugs.freepascal.org/view.php?id=19503

Thanks for the explanation. This clarifies things.

Regards,

Adriaan van Os

Reply by email

Jonas Maebe
to Adriaan van Os
Adriaan van Os wrote 25 lines
Jonas Maebe wrote: > Nothing ever needs to be manually released, regardless of whether > it's a VAR or OUT parameter. That's the whole point of automatic > reference counting) I agree for pure Pascal code, where a parameter can be OUT or VAR and automatic release will work in both cases. However, I disagree for interfacing with external interfaces. It must be clear for the bindings-writer that VAR in the bindings instead of (what should have been) OUT can cause a memory leak. This is tricky enough that it deserves an explanation in the manual. For example, I think (from Windows MediaFoundation) type IMFMediaEventGenerator = interface( IUnknown) ... .... function GetEvent( dwFlags: DWORD; OUT ppEvent: IMFMediaEvent): HResult; stdcall; is correct, whereas function GetEvent( dwFlags: DWORD; VAR ppEvent: IMFMediaEvent): HResult; stdcall; will leak memory.

No, it won't:
1) if the external code expects it to be "out" and you declare it as "var": the caller will decrease the reference count and the ppEvent that you get will have as initial value "nil". You can just assign to it like normal.
2) if the external code expects it to be "var" and you declare it as "out": the caller will not do anything and hence the ppEvent will not be nil. However, assignments to the "out" parameter still cause its reference count to be decreased. After all, you can assign multiple times to an "out" parameter, so the compiler cannot assume it's already nil. The only problem that could happen here is if you would explicitly write your code in a way that behaves differently depending on whether the initial value is nil (e.g., assigning something to it only in one branch of an if-statement, and then later checking whether it's "still" nil to determine whether you have to assign something else to it).

Jonas

Reply by email

Adriaan van Os
to Jonas Maebe
Jonas Maebe wrote 15 lines
No, it won't: 1) if the external code expects it to be "out" and you declare it as "var": the caller will decrease the reference count and the ppEvent that you get will have as initial value "nil". You can just assign to it like normal. 2) if the external code expects it to be "var" and you declare it as "out": the caller will not do anything and hence the ppEvent will not be nil. However, assignments to the "out" parameter still cause its reference count to be decreased. After all, you can assign multiple times to an "out" parameter, so the compiler cannot assume it's already nil. The only problem that could happen here is if you would explicitly write your code in a way that behaves differently depending on whether the initial value is nil (e.g., assigning something to it only in one branch of an if-statement, and then later checking whether it's "still" nil to determine whether you have to assign something else to it).

Sorry, but the above explanation flabberghasts me.

case 1.

MF binding declaration

          function GetEvent
             (     dwFlags                   : DWORD;
               out ppEvent                   : IMFMediaEvent): HResult; stdcall;

Pascal code is the caller and Windows MF is the callee.

         theResult                     := theSession.GetEvent
           ( 0, theEvent);
  The compiler sees the out parameter and (as you explained)
* OUT: the reference count of the value that's passed in is decreased by 1, and the variable that's passed into the procedure is initialized to "empty" (nil, but that's an implementation detail)

This produces the following assembly

# [195] theResult := theSession.GetEvent leal -24(%ebp),%eax
call FPC_INTF_DECR_REF
leal -24(%ebp),%eax
pushl %eax
pushl $0
pushl -8(%ebp)
movl -8(%ebp),%edx
testl %edx,%edx
jne .Lj260
movl $210,%eax
call FPC_HANDLEERROR
.Lj260:
movl (%edx),%eax
call *12(%eax)
movl %eax,-16(%ebp)

where FPC_INTF_DECR_REF is found in rtl/inc/objpas.inc

     procedure fpc_intf_decr_ref(var i: pointer);[public,alias: 'FPC_INTF_DECR_REF']; compilerproc;
       begin
         if assigned(i) then
           begin
             IUnknown(i)._Release;
             i:=nil;
           end;
       end;

So, before Windows returns a new value for ppEvent, the compiler emits a call to _Release, preventing a memory leak for our Pascal theEvent parameter.

case 2.

MF binding declaration

          function GetEvent
             (     dwFlags                   : DWORD;
               var ppEvent                   : IMFMediaEvent): HResult; stdcall;

Pascal code is the caller and Windows MF is the callee.

         theResult                     := theSession.GetEvent
           ( 0, theEvent);

The compiler sees the out parameter and (as you explained)

* VAR: nothing happens to the reference count. A reference to the original variable is passed in, and changing it or reading it has exactly the same effect as changing/reading the original variable.

This produces the following assembly

# [195] theResult := theSession.GetEvent leal -24(%ebp),%eax
pushl %eax
pushl $0
pushl -8(%ebp)
movl -8(%ebp),%edx
testl %edx,%edx
jne .Lj260
movl $210,%eax
call FPC_HANDLEERROR

So, Windows returns a new value for ppEvent, the compiler does not emit a call to _Release, and our Pascal theEvent parameter leaks (except if you assume that Windows MF behaves different for both cases, i.c. if NIL is passed or not. This is highly unlikely, because the declaration in Mfobjects.idl is

     HRESULT GetEvent(
         [in] DWORD dwFlags,
         [out] IMFMediaEvent** ppEvent
         );

which ignores the input value <http://msdn.microsoft.com/en-us/library/hh916383.aspx>.

Regards,

Adriaan van Os

Reply by email

Jonas Maebe
to Adriaan van Os
Adriaan van Os wrote 20 lines
Jonas Maebe wrote: > No, it won't: > 1) if the external code expects it to be "out" and you declare it > as "var": the caller will decrease the reference count and the > ppEvent that you get will have as initial value "nil". You can just > assign to it like normal. > 2) if the external code expects it to be "var" and you declare it > as "out": the caller will not do anything and hence the ppEvent > will not be nil. However, assignments to the "out" parameter still > cause its reference count to be decreased. After all, you can > assign multiple times to an "out" parameter, so the compiler cannot > assume it's already nil. The only problem that could happen here is > if you would explicitly write your code in a way that behaves > differently depending on whether the initial value is nil (e.g., > assigning something to it only in one branch of an if-statement, > and then later checking whether it's "still" nil to determine > whether you have to assign something else to it). Sorry, but the above explanation flabberghasts me.

I explained the opposite scenario as you: calling a method implemented in Pascal from external Windows code (with mismatched var/out modifiers). Rereading your message, that's indeed not what you were talking about.

In general, using wrong/mismatched modifiers when importing routines virtually always causes problems though (and often much worse than just a memory leak), there's nothing special about reference counted types here.

Jonas

Reply by email