Professional OPC
Development Tools

logos

Can Develop OPC XML DA C++ client in Visual Studio 2015?

More
22 Dec 2016 12:48 #4695 by Vasanth
Hi,

Debugger Type Mixed and Auto doesn't generate Managed threads in thread window. So I changed it to Managed and generated thread window with call stack in attached file.

Please check it, let me know if you need more info.

Thanks

File Attachment:

File Name: threadinfo...2-22.txt
File Size:17 KB
Attachments:

Please Log in or Create an account to join the conversation.

More
22 Dec 2016 10:31 #4687 by support
Unfortunately it looks like that no managed threads have been captured. Can you please enforce the Mixed mode debugging as described here: msdn.microsoft.com/en-us/library/kbaht4dh.aspx , and repeat the steps?

Also note that the call stack will be possibly needed from more threads - not just one - as in the instructions.

Thank you

Please Log in or Create an account to join the conversation.

More
22 Dec 2016 06:34 #4681 by Vasanth
Hi,

Attached thread and callstack info, let me know if you need more info.

Thanks.

File Attachment:

File Name: threadinfo.txt
File Size:3 KB


File Attachment:

File Name: callstack.txt
File Size:0 KB
Attachments:

Please Log in or Create an account to join the conversation.

More
21 Dec 2016 19:34 #4680 by support
Can you please break into the program at the moment it appears hung, and obtain (and send to us) the list of running threads, and a call stack of at least the thread that is calling the ReadMultipleItems method?

The steps are similar to those described here: kb.opclabs.com/Obtaining_.NET_call_stacks_in_a_COM_application

Except that in your case, as you are already running from Visual Studio, you might be able to just start the process under the debugger, instead of attaching to it.
The following user(s) said Thank You: Vasanth

Please Log in or Create an account to join the conversation.

More
21 Dec 2016 11:49 #4678 by Vasanth
Hi,

I have an issue, When my OpcXmlDa server is not stable that time the below line hangs for long time and it not return backs at all.

ResultArray.Attach(ClientPtr->ReadMultipleItems(&pArgumentsArray));

It gives the below error and works for some time then hangs. If OpcXmlDa server is not stable this function should return some error and come out right? Is there any api to address this issue or any help?


2016-12-14T0:14:42Z COPCXMLDAClient: readOpcXmlDaTagData: Exception occured OpcLabs.EasyOpc.Implementations.NetApi.NetApiException: An exception occurred during processing in a NET API OPC Data Access client. The inner exception contains details about the problem. ---> System.Web.Services.Protocols.SoapException: Relogin attempted too soon

at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)

at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)

at OpcXml.Da10.Service.Read(RequestOptions Options, ReadRequestItemList ItemList, ReplyItemList& RItemList, OPCError[]& Errors)

at OpcXml.Da.Server.Read(Item[] items)

at Opc.Da.Server.Read(Item[] items)

at OpcLabs.EasyOpc.Implementations.NetApi.DataAccess.NetApiOpcDaServer.Read(Item[] items, Exception& exception)

--- End of inner exception stack trace ---

Please Log in or Create an account to join the conversation.

More
05 Dec 2016 20:06 #4627 by support
You have caused the memory leak by the ResultArray.Detach(); and ArgumentsArray.Detach(); calls near the end of the loop body. Remove them.

More observations:

I have written that you have two alternative solutions: Either the one with CoInitializeEx, or the one with message pumping. You should not combine both. That is an overkill, and not a good one.

In a simple loop like this, it does not really matter that much where you put the message pump.

But with your level of experience, if your app allows it, you should definitely stay with just the CoInitializeEx approach, and forget about message pumping. It has many more intricacies to it. And, we are not here to give advise with them - it's a Microsoft stuff.

Our revised example code will be available together with version 2016.2, which is due in several days. But we do not show reads in a loop, and we do not use the message pumping either, so really the significant change there is the proper CoInitializeEx, which is trivial.

Regards
The following user(s) said Thank You: Vasanth

Please Log in or Create an account to join the conversation.

More
05 Dec 2016 07:02 - 05 Dec 2016 16:47 #4626 by Vasanth
Hi,

I have tested with options you have provided but still memory leak issue is not solved, memory leak is growing in same pattern only.

Option A: I replaced CoInitialize with CoInitializeEx and problem is not solved.

Option B: I included message loop inside the thread and still issue is not solved. Please let me know where I can add this message loop.

Please share your sample code where you fixed memory leak issue.

Thanks.
// Initialize the COM library
    //CoInitialize(NULL);
	[b]CoInitializeEx(NULL, COINIT_MULTITHREADED);[/b]
 
    // Instatiate the EasyOPC-DA client object
    _EasyDAClientPtr ClientPtr(__uuidof(EasyDAClient));
 
	while(1){
 
		_DAReadItemArgumentsPtr ReadItemArguments1Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments1Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments1Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Double";
 
		_DAReadItemArgumentsPtr ReadItemArguments2Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments2Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments2Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Double[]";
 
		_DAReadItemArgumentsPtr ReadItemArguments3Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments3Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments3Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Int";
 
		_DAReadItemArgumentsPtr ReadItemArguments4Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments4Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments4Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Int[]";
 
		CComSafeArray<VARIANT> ArgumentsArray(4);
		ArgumentsArray.SetAt(0, _variant_t((IDispatch*)ReadItemArguments1Ptr));
		ArgumentsArray.SetAt(1, _variant_t((IDispatch*)ReadItemArguments2Ptr));
		ArgumentsArray.SetAt(2, _variant_t((IDispatch*)ReadItemArguments3Ptr));
		ArgumentsArray.SetAt(3, _variant_t((IDispatch*)ReadItemArguments4Ptr));
 
		LPSAFEARRAY pArgumentsArray = ArgumentsArray.Detach();
		//CComSafeArray<VARIANT> ResultArray(ClientPtr->ReadMultipleItems(&pArgumentsArray));
		CComSafeArray<VARIANT> ResultArray;
		ResultArray.Attach(ClientPtr->ReadMultipleItems(&pArgumentsArray));
		ArgumentsArray.Attach(pArgumentsArray);
 
		for (int i = ResultArray.GetLowerBound(0); i <= ResultArray.GetUpperBound(0); i++)
		{
			_DAVtqResultPtr DAVtqResultPtr(ResultArray[i]);
			_DAVtqPtr DAVtqPtr(DAVtqResultPtr->Vtq);
			_variant_t vtqAsString(DAVtqPtr->ToString);
			_tprintf(_T("results(%d).Vtq.ToString(): %s\n"), i, vtqAsString.bstrVal);
		}
		Sleep(50);
 
		[b]MSG msg;
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		}[/b]
		// Release all interface pointers BEFORE calling CoUninitialize()
		ResultArray.Detach();
		ResultArray.Destroy();
		ArgumentsArray.Detach();
		ArgumentsArray.Destroy();
 
	}
    ClientPtr = NULL;
 
    CoUninitialize();
Last edit: 05 Dec 2016 16:47 by support.

Please Log in or Create an account to join the conversation.

More
01 Dec 2016 12:57 - 12 Dec 2016 19:58 #4615 by Vasanth
Hi,

Intentionally I ran OPCDA (COM Based) code to show the memory growth in basic OPCDA itself. Otherwise I have modified ReadMulipleItems for OPCXMLDA and introduced delay (Sleep(50)) as mentioned by you.
But, Still memory growth is same.

It has grown 89,752K in 40 minutes, Check attached Performance Counter. And also copying ReadMultipleItems for OPCXMLDA code here.

Thanks.

Code:
// Initialize the COM library
    CoInitialize(NULL);
 
    // Instatiate the EasyOPC-DA client object
    _EasyDAClientPtr ClientPtr(__uuidof(EasyDAClient));
 
	while(1){
 
		_DAReadItemArgumentsPtr ReadItemArguments1Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments1Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments1Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Double";
 
		_DAReadItemArgumentsPtr ReadItemArguments2Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments2Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments2Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Double[]";
 
		_DAReadItemArgumentsPtr ReadItemArguments3Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments3Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments3Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Int";
 
		_DAReadItemArgumentsPtr ReadItemArguments4Ptr(_uuidof(DAReadItemArguments));
		ReadItemArguments4Ptr->ServerDescriptor->UrlString = L"http://opcxml.demo-this.com/XmlDaSampleServer/Service.asmx";
		ReadItemArguments4Ptr->ItemDescriptor->ItemId = L"Dynamic/Analog Types/Int[]";
 
		CComSafeArray<VARIANT> ArgumentsArray(4);
		ArgumentsArray.SetAt(0, _variant_t((IDispatch*)ReadItemArguments1Ptr));
		ArgumentsArray.SetAt(1, _variant_t((IDispatch*)ReadItemArguments2Ptr));
		ArgumentsArray.SetAt(2, _variant_t((IDispatch*)ReadItemArguments3Ptr));
		ArgumentsArray.SetAt(3, _variant_t((IDispatch*)ReadItemArguments4Ptr));
 
		LPSAFEARRAY pArgumentsArray = ArgumentsArray.Detach();
		//CComSafeArray<VARIANT> ResultArray(ClientPtr->ReadMultipleItems(&pArgumentsArray));
		CComSafeArray<VARIANT> ResultArray;
		ResultArray.Attach(ClientPtr->ReadMultipleItems(&pArgumentsArray));
		ArgumentsArray.Attach(pArgumentsArray);
 
		for (int i = ResultArray.GetLowerBound(0); i <= ResultArray.GetUpperBound(0); i++)
		{
			_DAVtqResultPtr DAVtqResultPtr(ResultArray[i]);
			_DAVtqPtr DAVtqPtr(DAVtqResultPtr->Vtq);
			_variant_t vtqAsString(DAVtqPtr->ToString);
			_tprintf(_T("results(%d).Vtq.ToString(): %s\n"), i, vtqAsString.bstrVal);
		}
		Sleep(50);
		// Release all interface pointers BEFORE calling CoUninitialize()
		ResultArray.Detach();
		ResultArray.Destroy();
		ArgumentsArray.Detach();
		ArgumentsArray.Destroy();
	}
    ClientPtr = NULL;
 
    CoUninitialize();

File Attachment:

File Name: Performanc...nter.zip
File Size:24 KB
Attachments:
Last edit: 12 Dec 2016 19:58 by support.

Please Log in or Create an account to join the conversation.

More
01 Dec 2016 12:51 - 12 Dec 2016 19:57 #4614 by support
OK, this mystery is resolved now. In short: A COM single-apartment thread (STA model) must pump messages.

In the example we are discussing, you can either:

A ) Switch to the multithreaded object concurrency (MTA threads). This can be done by replacing the CoInitialize(...) call with:
CoInitializeEx(NULL, COINIT_MULTITHREADED);

B ) Alternatively, keep using STA, but add pump messaging to the thread loop - e.g.:
MSG msg;
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
	TranslateMessage(&msg); 
	DispatchMessage(&msg); 
}

In the end, the problem was really in the C/C++ code, and there is nothing wrong with the component. It has worked well from other tools (such as VBScript) because they are not so low-level, and already contain code that handles the thread-model intricacies well. Also, calling some parts of the code (such as OPC XML-DA) could have invoked parts that internally perform message pumping, effectively "healing" the issue as I have described in my earlier post.

The garbage collector was involved indirectly in this; probably what happens is that when the message are not pumped, the COM wrappers around .NET objects do not get a "chance" to actually release their references to .NET objects, making it impossible for the GC to collect them.

Regards

See also:
- support.microsoft.com/en-us/kb/136885
- blogs.msdn.microsoft.com/timng/2006/09/07/com-re-entrancy-and-message-pumping/
Last edit: 12 Dec 2016 19:57 by support.
The following user(s) said Thank You: Vasanth

Please Log in or Create an account to join the conversation.

More
30 Nov 2016 14:00 #4610 by support
OK, this is complicated, with bits of mystery to it. QuickOPC is written in .NET, and the COM interfaces are exposed on top of that. .NET has automated memory management, with non-deterministic disposal of the unused memory space, using a garbage collector (GC). One cannot expect that a final release of a COM object would immediately deallocate the memory. In the long term, however, a memory consumed by a program that is not supposed to allocate more and more should *not* grow.

Microsoft provides general information about how the GC works, but not every detail of it.

So far I have been able to determine the following facts:

1. It seems to be necessary to give GC some time to actually perform the collection. Having a program that consists of a tight loop (with no idle time) appears to block the GC from performing its task. The code you have sent is an example of that - no wonder it consumes so much CPU, but what's worse, it has this bad effect on the GC. In a loop like this, you definitely should have some idle period, e.g. Sleep(50).

2. Resolving the issue above is necessary, but not always sufficient, to address the memory growth problem. I have found that if you use such code with OPC XML-DA, the memory does *not* grow. In the code you posted, you have switched to OPC DA (COM-based), and for some reason, the memory *does* grow with it. So if, in reality, you need to use OPC XML-DA and the code you have sent was just an experiment, you should be able to switch back to OPC XML-DA, add some idle time to the loop, and it should work.

The reason why the memory grows with OPC DA is unclear to me at the time. It is a mysterious matter, for several reasons:

a) The problem does not appear in .NET programs (C#).
b) The problem does not appear in COM programs written in other tools (e.g. VBScript).
a+b) Therefore it looks like that there is no problem on the component side - it is just the C++ code that has this problem.
c) The problem actually appears already when just the initial objects (DAReadItemArguments) are created (and then released) in a loop, with the rest of the code commented out. But there appears to be no bug in the remaining trivial code. Adding the actual call to EasyDAClient.ReadMultipleItems actually "heals" the problem - but ONLY if all the arguments refer to OPC XML-DA servers.

I will continue investigating this.

Regards

Please Log in or Create an account to join the conversation.

Moderators: support
Time to create page: 0.318 seconds

      

 Recommend this on Google