1

I've a Delphi client/server project, which uses RemObjects for communication between client and server. In remobject the server and public service functions are defined and interfaces are generated for it. At some points, we need to call other services, from withith another service. In order to do this, we created class methods/functions, pass over the current database connection and remobjects session, an call an implementation of the desired function. For example:

class function TMyService.GetFoo(session: TROSession; conn: Connection): AnsiString;
var
    svc: TMyService;
begin
    svc := TMyService.Create(nil);
    try
      IROObjectActivation(svc).OnActivate(session.SessionID, nil);
      try
        Result := svc.GetFooImpl();
      finally
        IROObjectActivation(svc).OnDeactivation(session.SessionID);
      end;
    finally
      FreeAndNil(svc);
    end;
end;

Some times, looks random, svc seems already to be free'd, before the FreeAndNil call, which causes access violations.

At this point, TMyService, also has a generated interface IMyService, but this only contians the public methods, not the implementations. That's why we use the type instead of the interface to devince the svc variable.

As far as I know, interfaced objects should be free'd at the end of a method, and not half way. Are there any compiler optimisations which can affect this behaviour?

[edit] By the way, FastMM4 is also compiled in this project, which may have some effect on this. Project compiled with Delphi 10.3

Michiel van Vaardegem
  • 2,260
  • 20
  • 35
  • Interfaced objects can be freed as soon as their reference count reaches ZERO. And yes this could happen before the end of current procedure. Local variables are the ones that get freed when program leaves the scope of a procedure in which they were initialized. – SilverWarior Feb 18 '21 at 14:32
  • @SilverWarior But in this case, svc is not nil yet, but it still can be freeed? Would it help if I put the cast to IROObjectActivation in a variable which I set to nil after the freeandnil of the svc? Or any other options? – Michiel van Vaardegem Feb 18 '21 at 15:01
  • Why declare `svc` as a class type when you're casting it to an interface? Why not declare `svc` as `IROObjectActivation` and avoid the try/finally altogether. This will force a single variable to hold a real reference count rather than confusing the compiler with a hard class reference that you're treating as an interface. Either that or just use it as a class and forget about the interface. Don't mix and match. – J... Feb 18 '21 at 15:07
  • you definitely combining interfaces and objects here which is never a good idea. I would suggest you switch completely to interfaces in this case and declare svc as IROObjectActivation. Basically you can then get rid of the try finally freeandnil too – mrabat Feb 18 '21 at 15:07
  • @J...Because that interface, is comming from the RemObjects library, and doesn't have the method GetFooImpl() what I'm calling – Michiel van Vaardegem Feb 18 '21 at 15:49
  • @mrabat I agree, but as we've used it like this for years, and only recently are getting problems with it, i'm looking for an explanation – Michiel van Vaardegem Feb 18 '21 at 15:49
  • @MichielvanVaardegem you should declare `svc` as `IROObjectActivation` to maintain the reference count properly. Cast `svc` to `TMyService` when you need to access implementation members that are not exposed in the interface. – Remy Lebeau Feb 18 '21 at 17:38
  • @MichielvanVaardegem Or, alternatively, drop the interface cast and just call `OnActivate` and `OnDeactivation` directly on the class reference. There's no need to cast it to an interface - you know it supports those methods at compile time so there's no need to abstract behind an interface. Interfaces let you generalize your code so that you don't need to care about the type of the underlying object. Except here you do care, and know, so it seems pointless to cast to an interface just to call a method (even notwithstanding the refcount issues this causes). – J... Feb 18 '21 at 19:00
  • @J... well.. there is, as there is also an OnActivate from another interface implemented on that service (well, on the base service) – Michiel van Vaardegem Feb 19 '21 at 06:50
  • @MichielvanVaardegem Then it's either overloaded with a unique signature or both interfaces call the same method in the underlying class. Either way it should work. – J... Feb 19 '21 at 12:13

1 Answers1

2

If TMyService supports interfaces, and you have problems with premature releasing, that means TMyService class is reference counted class (in other words, it supports interfaces, but reference counting is not disabled).

In such cases you need to store such object instance in interface reference to properly initialize reference counting. Also you should not manually release such object because its memory will be automatically managed, and if you need to access some methods that are not exposed through interface you can do that by typecasting.

However, using methods that are not exposed through interface is potential abuse of the class. Usually, interfaces contain all methods that are meant to be used.

class function TMyService.GetFoo(session: TROSession; conn: Connection): AnsiString;
var
    svc: IMyService;
begin
    svc := TMyService.Create(nil);
    IROObjectActivation(svc).OnActivate(session.SessionID, nil);
    try
      Result := svc.GetFooImpl();
      // or
      Result := TMyService(svc).GetFooImpl();
    finally
      IROObjectActivation(svc).OnDeactivation(session.SessionID);
    end;
end;

Whether or not you will need to use typecast for IROObjectActivation depends on the declaration of IMyService. Possibly, you don't need IMyService interface and you can just use IROObjectActivation.

Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • I agree, this is better/nicer. But it's odd that these problems are showing op only in the last few weeks, and there is nothing changed in this part.. – Michiel van Vaardegem Feb 25 '21 at 10:39
  • @MichielvanVaardegem When reference counting is not properly initialized you may observe random behavior depending on the involved code. So changes in your code in seemingly unrelated places, or changes in code you call may behave differently. Even user actions that lead to some code path may leave memory in particular state that can trigger different behavior. – Dalija Prasnikar Feb 25 '21 at 10:56