⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 group.cpp

📁 KepWare的OPC Client 示例.面向C
💻 CPP
📖 第 1 页 / 共 5 页
字号:
// **************************************************************************
// group.cpp
//
// Description:
//	Implements the CKGroup class.  An object of this class is associated with
//	each OPC Group we wish to exchange data with.
//
// DISCLAIMER:
//	This programming example is provided "AS IS".  As such Kepware, Inc.
//	makes no claims to the worthiness of the code and does not warranty
//	the code to be error free.  It is provided freely and can be used in
//	your own projects.  If you do find this code useful, place a little
//	marketing plug for Kepware in your code.  While we would love to help
//	every one who is trying to write a great OPC client application, the 
//	uniqueness of every project and the limited number of hours in a day 
//	simply prevents us from doing so.  If you really find yourself in a
//	bind, please contact Kepware's technical support.  We will not be able
//	to assist you with server related problems unless you are using KepServer
//	or KepServerEx.
// **************************************************************************


#include "stdafx.h"
#include "opctestclient.h"
#include "group.h"
#include "server.h"
#include "item.h"
#include "advisesink.h"
#include "datasink20.h"

// Versions for serializing:
#define VERSION_1			1
#define CURRENT_VERSION		VERSION_1


/////////////////////////////////////////////////////////////////////////////
// Data for CSV import/export
/////////////////////////////////////////////////////////////////////////////

#define MAXHEADERLEN				64		// Maximum header length
#define MAXCSVIMPORTFIELDLENGTH		512		// Maximum field length
#define PARSEBUFFERSIZE				2048	// Size of CSV record parse buffer

// Enumerate Field type IDs:
enum CSVFIELD
	{
	CSV_ItemID = 0,	// Item ID
	CSV_AccessPath,	// Item Access Path
	CSV_DataType,	// Item Data Type
	CSV_Active,		// Item Active state
	
	// Place holder used only as a sentinel to determine the number of IDs
	CSV_FieldCount		
	};

// Define a field identifer structre.  There will be one of these for each
// column of the CSV file, as determined from the header record.  These will
// be connected in a linked list, allowing user to place columns in any order
// and omit columns.
typedef struct _tagCSVHEADERFIELD
	{
	TCHAR szText [MAXHEADERLEN];		// Column name Text
	CSVFIELD eID;						// Column field type ID
	struct _tagCSVHEADERFIELD *pNext;	// Link to next structure in list
	} CSVHEADERFIELD;

// Enumerate return values from ReadCSV ():
typedef enum _tagREADCSVRET
	{
	tFieldOK = 0,	// Parse was successfull
	tFieldOverflow,	// Field was too big to fit into buffer
	tEndOfRecord	// End of record encountered
	} READCSVRET;

// Declare an array that will contain a CSVHEADERFIELD structure for each 
// possible field type.  This master field list will be used to validate
// header fields.  It will be initialized in LoadMasterFieldList().
static CSVHEADERFIELD astMasterCSV [CSV_FieldCount];


/////////////////////////////////////////////////////////////////////////////
// Helper functions for CSV import
/////////////////////////////////////////////////////////////////////////////

// **************************************************************************
// LoadMasterFieldList ()
//
// Description:
//	Initialize the master field list array.
//
// Parameters:
//  none
//
// Returns:
//  void
// **************************************************************************
static void LoadMasterFieldList ()
	{
	// Declare a static flag that will prevent us from actually initializing
	// the array more than once, even if this function is called multiple times.
	static BOOL bMasterListLoaded = false;

	// If already loaded then no need to do it again...
	if (bMasterListLoaded)
		return;

	// Declare a scratch CString for loading string resources:
	CString strFieldName;

	// Loop over all possible field types (implied by sizeof astMasterCSV):
	for (int i = 0; i < sizeof (astMasterCSV) / sizeof (astMasterCSV [0]); i++)
		{
		// Load the field name string resources:
		switch (i)
			{
			case CSV_ItemID:
				strFieldName.LoadString (IDS_ITEMID);
				break;

			case CSV_AccessPath:
				strFieldName.LoadString (IDS_ACCESSPATH);
				break;

			case CSV_DataType:
				strFieldName.LoadString (IDS_DATATYPE);
				break;

			case CSV_Active:
				strFieldName.LoadString (IDS_ACTIVE);
				break;

			default:
				// Unexpected field type.  Programmer error.
				ASSERT (FALSE);
				continue;
			}
		
		// Transfer the text we loaded into text field (safely):
		lstrcpyn (astMasterCSV [i].szText, strFieldName, sizeof (astMasterCSV [i].szText) / sizeof (TCHAR));

		// Assign the field type ID:
		astMasterCSV [i].eID = (CSVFIELD)i;

		// Initialize the next pointer to NULL.  It will be set as the header
		// is processed in BuildFieldList():
		astMasterCSV [i].pNext = NULL;
		}

	// Set "loaded" flag so we don't waste time reloading:
	bMasterListLoaded = true;
	}

// **************************************************************************
// ReadCSV ()
//
// Description:
//	Parse a field from a CSV file record.  Called once per field in a given
//	record.  pnStart will be reset to end of current field for subsequent call.
//	Use master field list to know how many times per record to call this.
//
// Parameters:
//  LPCTSTR		szInBuff	Input buffer containing whole record string.
//	int			*pnStart	Character position to begin parse.  Will be reset
//							  to start of next field.
//	LPTSTR		szOutBuff	Output buffer for parsed field string.
//	int			nBuffSize	Size of output buffer.
//
// Returns:
//  READCSVRET - Type enumerated above.
// **************************************************************************
static READCSVRET _stdcall ReadCSV (LPCTSTR szInBuff, int *pnStart, LPTSTR szOutBuff, int nBuffSize)
	{
	TCHAR ch = _T('\r');
	BOOL bQuoted = FALSE;
	BOOL bOverflow = FALSE;
	int cnChars = 0;

	// If we are at the end of the record, marked by a NULL termimator,
	// then we parse another field.  Return the "end of record" code.
	if (!szInBuff [*pnStart])
		return (tEndOfRecord);

	// Continue to read characters until we hit the next comma or
	// an EOL (End Of Line - "\n") character:
	while (TRUE)
		{
		// Get the next character, and increment start position for next time:
		ch = szInBuff [(*pnStart)++];

		// If the character is NULL, we are at the end of the record and
		// therefore is nothing left to read.  Break out of the loop.
		if (!ch)
			break;

		// Trim leading whitespace, if any.  If current character is a space,
		// then continue.  This will force us to process next character without
		// saving current one (a space) in output buffer.
		if (!cnChars && _istspace (ch))
			continue;

		// If we make it here, then the current character is "a keeper".
		// Increment the character counter:
		++cnChars;

		// Quotes will bracket strings that may contain what we consider delimiters.
		if (ch == _T('"'))
			{
			// Toggle bQuoted flag.  If set TRUE, this is an opening quote, and
			// there had better be closing quote.  If there is not closing quote,
			// we will continue parsing, skipping over field dilimiters, until 
			// the output buffer is filled.
			bQuoted = !bQuoted;

			// Continue without saving delimiter in output buffer:
			continue;
			}

		// EOL and commas delimit the fields.  We don't count delimiters inside 
		// quotes. This is where we eventually want to break out of the loop.  
		if (!bQuoted)
			{
			if (ch == _T(',') || ch == _T('\n'))
				break;
			}

		// Add the current character to the output buffer as long a there
		// is room:
		if (nBuffSize > 1)
			{
			// There is room, so add the current character to the output 
			// buffer, and move output buffer pointer to next position.
			*szOutBuff++ = ch;

			// Decrement nBuffSize.  When it hits zero we know we have
			// filled the output buffer to capacity.
			--nBuffSize;
			}
		
		else
			{
			// There is no more room in the output buffer.  Set the bOverflow
			// flag, but don't break out of the loop.  We want to keep going
			// until we hit a field deleimiter.  This will allow us to parse
			// the next field, even though this one is too big to deal with.
			bOverflow = TRUE;
			}
		}

	// Make sure the output string is properly NULL terminated:
	*szOutBuff = 0;

	// If overflowed the output buffer, then return tFieldOverflow,
	// otherwise return tFieldOK:
	return (bOverflow ? tFieldOverflow : tFieldOK);
	}

// **************************************************************************
// BuildFieldList ()
//
// Description:
//	Users are free to omit certain fields and to order included fields any 
//	way they want.  To do this, this function must be called to interpret
//	the field identification record (header).  Here, we will build a linked 
//	list of CSVHEADERFIELD structures based on header by setting the "pNext"
//	members of the master field list elements.  This list defines what fields
//	we expect to see and their order.
//
//	This function will throw an exception if the header can not be parsed.
//  The caller must be prepared to handle these exceptions.
//
// Parameters:
//  LPCTSTR		lpszHeader	Pointer to header string.
//
// Returns:
//  CSVHEADERFIELD* - Head of linked list of CSVHEADERFIELD structures.
// **************************************************************************
static CSVHEADERFIELD *BuildFieldList (LPCTSTR lpszHeader)
	{
	CString str;
	TCHAR szBuff [MAXHEADERLEN];
	int nIndex = 0;
	int i;
	int nMaxFields;
	CSVHEADERFIELD *pHeader = NULL;
	CSVHEADERFIELD *pLast = NULL;
	CSVHEADERFIELD *pField;

	// Make sure the master field list is initialized.  It will do the actual
	// loading on the first call only, so don't worry about the apparent redundancy,
	// or the "pNext" members getting reset.
	LoadMasterFieldList ();

	// The maximum number of fields we should expected is implied by the
	// sizeof astMasterCSV:
	nMaxFields = sizeof (astMasterCSV) / sizeof (astMasterCSV [0]);

	// Clear any existing pointer references from the master list:
	for (i = 0; i < nMaxFields; i++)
		astMasterCSV [i].pNext = NULL;

	// Initialize our "done" flag:
	bool bDone = false;

	// Process fields in header record until we decide we are done:
	while (!bDone)
		{
		// Parse out a field:
		READCSVRET eRC = ReadCSV (lpszHeader, &nIndex, szBuff, _countof (szBuff));

		// Check return code of our field parse function.  If there was a problem,
		// throw an exception.  If not, add a link to out field list.
		switch (eRC)
			{
			case tFieldOverflow:
				// The supplied buffer was not big enough to hold the field contents.
				// The header must be considered invalid - issue an error message and
				// throw an exception.

				// "Error importing CSV item data.\n\nField buffer overflow reading identification record."
				AfxMessageBox (IDS_IDFIELDBUFFEROVERFLOW);
				throw (-1);

			case tFieldOK:
				// We got something.  See if we can match a field type name.

				// Clear the field pointer:
				pField = NULL;

				// Search the master field list for a match of the field name:
				for (i = 0; i < nMaxFields; i++)
					{
					// Compare name in master filed list element with parsed string:
					if (!lstrcmpi (szBuff, astMasterCSV [i].szText))
						{
						// A match was found.  Point this field to its match in
						// the master field list:
						pField = &astMasterCSV [i];
						break;
						}
					}

				// If pField is still NULL, the we must not have found a matching
				// name in master field list.  Thus, the header must be considered
				// invalid - issue an error message and throw an exception.
				if (!pField)
					{
					// "Error importing CSV item data.\n\nUnrecognized field name: '%s'"
					str.Format (IDS_UNRECOGNIZEDFIELDNAME, szBuff);
					AfxMessageBox (str);
					throw (-1);
					}

				// If pNext has been set, then we know this field has already been 
				// added.  If the pField is the same as pLast then we also know the 
				// field has aleady been added.  If fields are duplicated, we must
				// consider the header invalid - issue an error message and throw
				// an exception.
				if (pField->pNext || pField == pLast)
					{
					// "Error importing item database.\n\nDuplicate field name: '%s'"
					str.Format (IDS_DUPLICATEFIELDNAME, szBuff);
					AfxMessageBox (str);
					throw (-1);
					}

				// If we make it here, we know the field is "a keeper".  Add it
				// to the end of the linked list.
				if (!pHeader)
					pHeader = pField;
				else
					pLast->pNext = pField;

				pLast = pField;
				break;

			case tEndOfRecord:
				// End of record was encountered, so we're done.
				bDone = true;
				break;

			default:
				// Unexpected return code.  Programmer error.
				ASSERT (FALSE);
				break;
			}
		}

	// If we processed the whole header record, and didn't add anyting to the
	// linked list (i.e. pHeader is still NULL), then we must consider the
	// header invalid - issue an error message and throw an exception.
	if (!pHeader)
		{
		// "Error importing item database.\n\nMissing item field identification record."
		AfxMessageBox (IDS_MISSINGIDRECORD);
		throw (-1);
		}

	// If we make it here, then everything looks good.  Return a pointer
	// to the head of the field linked list.
	return (pHeader);
	}

// **************************************************************************
// CSVToItem ()
//
// Description:
//	Set item property according to a field parsed from CSV record.
//
// Parameters:
//  CSVFIELD	eID			Field type ID.
//	LPCTSTR		szFieldText	Property value parsed from record.
//	CKItem		*pItem		Item to modify.
//
// Returns:
//  void
// **************************************************************************
static void _stdcall CSVToItem (CSVFIELD eID, LPCTSTR szFieldText, CKItem *pItem)
	{
	// If the field is empty (i.e. it starts with a NULL terminator), then
	// there is nothing to do.
	if (!szFieldText [0])
		return;

	// Selecte the item "Set" function based on given field type ID:
	switch (eID)
		{
		case CSV_ItemID:
			// Item ID:

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -