Reference counting in Delphi
When using QuickOPC components from Delphi, you are effectively making use of COM (sometimes also referred to ActiveX, OLE, or Automation) objects. The lifetime (including memory allocation) of COM objects is controlled by reference counting. In short, anybody who uses COM component needs to properly call the AddRef() and Release() methods on any COM object it works with. As long as the reference count maintained by these methods is non-zero, the COM object stays as alive. When the reference count goes to zero, the object is disposed of.
Failure to do the reference counting correctly results in bugs that are sometimes very hard to diagnose. Symptoms of such bugs may be objects "disappearing" for unknown reason, or memory leaks.
QuickOPC is not special in respect to COM reference counting, and the rules that apply to QuickOPC usage in Delphi are the general rules that apply to any COM component. We have, however, put together a short set of recommendations that should help you to write bug-free code more easily, in this respect. Here they are:
- Declare your variables as interfaces, and not as components, if possible.
- If you have imported the QuickOPC type libraries with the "Generate component wrappers" box checked (as recommended), you will end up with two declarations created for each class in the type library: One is the interface (_XXXX), and one is the component (TXXXX). For example, for the EasyUAClient class, you will get _EasyUAClient and TEasyUAClient.
- If the imported class does not have events, or if it does have events but you do not plan to use them, declare your variables with the interface type (e.g. _EasyUAClient) and not the component type (e.g. TEasyUAClient). The reason for that is that Delphi's automatic reference counting only works with interfaces. With variables declared as interfaces, automatic reference counting will be handed by the compiler, and you will not have to put in any deallocation calls in order to prevent memory leaks.
- The following example shows this preferred method, with the important parts highlighted:
var Client: OpcLabs_EasyOpcUA_TLB._EasyUAClient; Value: OleVariant; begin // Instantiate the client object Client := CoEasyUAClient.Create; Value := Client.ReadValue( 'http://opcua.demo-this.com:51211/UA/SampleServer', 'nsu=http://test.org/UA/Data/;i=10853'); WriteLn('value: ', Value); end;
- If you need to use the component class (for events), deallocate the component explicitly.
- The interface types (_XXXX, e.g. _EasyUAClient) do not allow hooking events easily. Events are, however, nicely exposed on the component types (TXXXX, e.g. TEasyUAClient). If you need to use events, declare your variable with the component type. In such case, however, you need to explicitly deallocate the components when you are done with, e.g. with the use of the FreeAndNil() method.
- The following example shows the code when events must be used, with the important parts highlighted:
type TClientEventHandlers = class procedure OnDataChangeNotification( ASender: TObject; sender: OleVariant; const eventArgs: _EasyUADataChangeNotificationEventArgs); end; procedure TClientEventHandlers.OnDataChangeNotification( ASender: TObject; sender: OleVariant; const eventArgs: _EasyUADataChangeNotificationEventArgs); begin // Display the data // Remark: Production code would check eventArgs.Exception before accessing // eventArgs.AttributeData. WriteLn(eventArgs.Arguments.NodeDescriptor.ToString, ': ', eventArgs.AttributeData.ToString); end; class procedure SubscribeDataChange.Main; var Client: TEasyUAClient; ClientEventHandlers: TClientEventHandlers; begin // Instantiate the client object and hook events Client := TEasyUAClient.Create(nil); ClientEventHandlers := TClientEventHandlers.Create; Client.OnDataChangeNotification := ClientEventHandlers.OnDataChangeNotification; WriteLn('Subscribing...'); Client.SubscribeDataChange( 'http://opcua.demo-this.com:51211/UA/SampleServer', 'nsu=http://test.org/UA/Data/;i=10853', 1000); WriteLn('Processing data change events for 1 minute...'); PumpSleep(60*1000); WriteLn('Unsubscribing...'); Client.UnsubscribeAllMonitoredItems; WriteLn('Waiting for 5 seconds...'); PumpSleep(5*1000); WriteLn('Finished.'); FreeAndNil(Client); FreeAndNil(ClientEventHandlers); end;
- If you have a class with event handlers and create an instance of it, do not forget to deallocate it, too.
- There is nothing surprising about it, this is just normal Delphi. You will probably use the FreeAndNil() method again. The example above shows this as well.
Hint: In order to diagnose memory leaks, you can put the following line at the beginning of your program:
ReportMemoryLeaksOnShutdown := True;
Note that when the component classes are used (TXXXX, e.g. TEasyUAClient), you may end up with memory leak of TServerEventDispatch object. As far as we can tell, this is a bug in Delphi that we cannot influence or fix. Since there is just one such small object leaked per component, and you are not normally creating many such components in the application, the leak should be benign under such circumstances. More information: