MaKaNu
MaKaNu

Reputation: 1008

Why isn't this object destroyed when it is cleared from the workspace?

I am working on a MATLAB class which stores an interface object created with tcpip and includes a callback function for the interface object to use, as per the following example:

classdef wsg50_mini2 < handle

    properties
        TCPIP
    end

    %PUBLIC METHODS
    methods

        %CONSTRUCTOR
        function obj = wsg50_mini2(varargin)
            fprintf('################# I am created #################\n')

            obj.TCPIP = tcpip('localhost',1000);
            obj.TCPIP.OutputBufferSize = 3000;
            obj.TCPIP.InputBufferSize = 3000;
            obj.TCPIP.ByteOrder = 'littleEndian';
            obj.TCPIP.Timeout = 1;

            %Setting up Callbackfunction
            obj.TCPIP.BytesAvailableFcnMode = 'byte';
            obj.TCPIP.BytesAvailableFcnCount = 1;
            obj.TCPIP.BytesAvailableFcn = {@obj.TCP_Callback, obj};
        end
    end

    %PRIVATE METHODS
    methods (Access = private)

        %DESTRUCTOR
        function delete(obj)
            fprintf('################# Hey I am called! #################\n')
            instrreset
        end
    end

    %STATIC METHODS
    methods (Static)
        %TCP Callback
        %This function will be called if one Byte is available at the TCPIP
        %buffer.
        function TCP_Callback(tcpsocket,event,obj)
            fprintf('Loading 1 Byte Data From Buffer.\n')
        end
    end
end

When I clear my class the variable will be cleaned from the workspace, but the delete destructor function is not called. Why not?

I realized, that my Instruments are still active in the Instrument Control app. If I delete my Instruments from there, my delete destructor is be called.

I think it is some strange behaviour of the tcpip-class.

Upvotes: 0

Views: 245

Answers (2)

MaKaNu
MaKaNu

Reputation: 1008

The problem is the line obj.TCPIP.BytesAvailableFcn = {@obj.TCP_Callback, obj};. The reference @obj.TCP_Callback creates a instance of the class inside the tcpip object, which is a property of the class itself.

the most easiest solution is to clear obj.TCPIP.BytesAvailableFcn = '' inside the destructor and call the destructor manualy. This is okayish if the code is only used by its creator.

To keep the object-oriented code intact, a Wrapper-Class works for the callback-function. The main file is updated with the Wrapper Class and a Listener as shown below. The Setup of the Callback is replaced with a combination of both of them. The Destructor needs then to call the destructors of the Wrapper (Which removes the instance of itself from the Callback) and of the Listener:

classdef wsg50_mini2 < handle

    properties
        TCPIP
        TCPIPWrapper
        LH
    end

    %PUBLIC METHODS
    methods

        %CONSTRUCTOR
        function obj = wsg50_mini2(varargin)
            fprintf('################# I am created #################\n')

            % Create TCPIP Object
            obj.TCPIP = tcpip('localhost',1000);

            % Create TCPIP Wrapper
            obj.TCPIPWrapper = TCPIPWrapper(obj.TCPIP)

            % Add Listener
            obj.LH = listener(obj.TCPIPWrapper,'BytesAvailableFcn',@obj.onBytes);

            obj.TCPIP.OutputBufferSize = 3000;
            obj.TCPIP.InputBufferSize = 3000;
            obj.TCPIP.ByteOrder = 'littleEndian';
            obj.TCPIP.Timeout = 1;

        end
    end

    %PRIVATE METHODS
    methods (Access = private)

        %DESTRUCTOR
        function delete(obj)
            fprintf('################# Hey I am called! #################\n')
            % Destroy Listener
            delete(obj.LH);
            % destroy Wrapper
            delete(obj.TCPIPWrapper);

            instrreset
        end
    end

    %STATIC METHODS
    methods (Private)
        function onBytes(obj,~,~)
            fprintf('Loading 1 Byte Data From Buffer.\n')
        end
    end
end

Additional the Wrapper looks as following:

classdef TCPIPWrapper < handle
    properties(Access=private)
        tcpip
    end
    events
        BytesAvailableFcn
    end
    methods
        % tcpipCallbackWrapper Constructor
        function obj = tcpipCallbackWrapper(tcpip)
            disp 'CONSTRUCTED tcpipCallbackWrapper'
            % Keep a reference to the tcpip object
            obj.tcpip = tcpip;
            % Adding this listener will result in the destructor not being
            % called automatically anymore; but luckily we have myClass
            % which will explicitly call it for us
            obj.tcpip.BytesAvailableFcnMode = 'byte';
            obj.tcpip.BytesAvailableFcnCount = 1;
            obj.tcpip.BytesAvailableFcn = @obj.bytesAvailableFcn;
        end
        % tcpipCallbackWrapper Destructor
        function delete(obj)
            disp 'DESTRUCTED tcpipCallbackWrapper'
            % Overwrite the callback with a new empty one, removing the
            % reference from the callback to our class instance, allowing
            % the instance to truly be destroyed
            obj.tcpip.BytesAvailableFcn = '';
        end
    end
    methods (Access=private)
        % Callback for BytesAvailableFcn
        function bytesAvailableFcn(obj,~,~)
            notify(obj, 'BytesAvailableFcn');
        end
    end
end

Upvotes: 0

matlabgui
matlabgui

Reputation: 5672

It sounds very much like you have the wsg50 variable reference in your Instrument Control App class, in that case when you clear the variable from the workspace because it is still referenced elsewhere it is not deleted.

Lets look at a simple example:

classdef myClass < handle
  properties
    name = '';
  end
  methods
    function delete ( obj )
      fprintf ( '%s being deleted\n', obj.name );
    end
  end
end

Right lets run some code:

var = basicClass;
var.name = '123';

If we clear this variable then you can see the delete is called:

>> clear var
 being deleted

If we re run this code and make a reference elsewhere:

var = basicClass;
var.name = '123';
otherReference.var = var;

Looking at both variables they are the same (as expected):

>> var    
var = 
  myClass with properties:
    name: '123'

>> otherReference.var
ans = 
  myClass with properties:
    name: '123'

So what happens if we clear var and look at the other reference

clear var
otherReference.var.name

>> otherReference.var
ans = 
  myClass with properties:
    name: '123'

We can see that the class variable is alive, as it should be as this is no difference than a class being an input to a function and when that function finishes the local copy of that variable is cleared from scope.

If we really want to delete the variable then you can do the following, where you explicitly run the destructor method:

var = basicClass;
var.name = '123';
otherReference.var = var;
delete(var);
otherReference.var.name

The delete line give us:

123 being deleted

While if we look at the otherReference.var we get:

Invalid or deleted object.

This will actually delete the variable and you will see that the otherReference is now a pointer to an invalid handle.

Upvotes: 2

Related Questions