0

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.

MaKaNu
  • 762
  • 8
  • 25
  • You haven't described in what sense your destructor "stopped working". But debugging help is unlikely to be an on-topic question here, so you probably can't just rephrase as "why doesn't my code work this way?". If you can frame the problem in a "how do I write a `handle`-class destructor that behaves in a particular way?" then there could be a salvageable question here. – Will Jan 21 '20 at 14:41
  • Thats my bad. The destructor isn't called anymore if I clear the workspace. I will edit the question. – MaKaNu Jan 21 '20 at 14:46
  • Please read [mre]. Code snippets are mostly not useful to find out what is wrong with your code. – Cris Luengo Jan 21 '20 at 14:49
  • 1
    Sounds like essentially a duplicate of my first ever question on SO [Find where handle is stored in scope](https://stackoverflow.com/questions/38432941/find-where-handle-is-stored-in-scope) then. It's *not* an easy problem to debug. – Will Jan 21 '20 at 14:51
  • I see that a minimal Example is necessary. While building the minimal reproducable example I discovered, that the problem comes from the callbackfunction from the tcpip-object. I edited my initial Question and replaced the snippet with the example. – MaKaNu Jan 22 '20 at 10:01
  • Hope you don't mind the rather extensive proposed edit to make your example more minimal and the question more focused on what is unknown. I think the question would be viable in this form. – Will Jan 22 '20 at 12:18
  • Thank you for your edit. I tried to figure out what maybe causes the problems. Debugging in matlab these callback functions is very shady. I think with calling the callback function. A reference of my class is set into the tcpip object. If I clear my workspace the reference still exist, so my destructor is not called. I cannot/don't want change the tcpip class because it is part of matlab and not my code. – MaKaNu Jan 22 '20 at 13:59

2 Answers2

2

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.

matlabgui
  • 5,642
  • 1
  • 12
  • 15
  • This indeed helped me to better understand what the problem is, but did not really awnser my question. I posted an Awnser which was created with the help of a Mathworks Support Employee. – MaKaNu May 27 '20 at 12:21
0

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
MaKaNu
  • 762
  • 8
  • 25