📄 group.cpp
字号:
// **************************************************************************
// 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 + -