/// Load a db file multiple times substituting a specified macro according to a number range.
///
/// The \a dbFile and \a macros arguments are like the normal dbLoadRecords() however
/// it is possible to embed a macro within these whose value follows the range \a start to \a stop.
/// You can either load the same \a dbFile multiple times with different macros, or even load
/// different database files by using \a loopVar as part of the filename. If you want to use a list
/// of (non-numeric) substitutions rather than an integer range see dbLoadRecordsList()
///
/// The name of the macro to be used for substitution is contained in \a loopVar and needs to be
/// reference in an \\ escaped way to make sure EPICS does not try to substitute it too soon.
/// as well as the \a macros the \a dbFile is also passed the \a loopVar macro value
/// @code
///     dbLoadRecordsLoop("file\$(I).db", "P=1,Q=Hello\$(I)", "I", 1, 4)
/// @endcode 
///
/// @param[in] dbFile @copydoc dbLoadRecordsLoopInitArg0
/// @param[in] macros @copydoc dbLoadRecordsLoopInitArg1
/// @param[in] loopVar @copydoc dbLoadRecordsLoopInitArg2
/// @param[in] start @copydoc dbLoadRecordsLoopInitArg3
/// @param[in] stop @copydoc dbLoadRecordsLoopInitArg4
/// @param[in] step @copydoc dbLoadRecordsLoopInitArg5
epicsShareFunc void dbLoadRecordsLoop(const char* dbFile, const char* macros, const char* loopVar, int start, int stop, int step)
{
    char loopVal[32];
    if (loopVar == NULL)
    {
        dbLoadRecords(dbFile, macros);
        return;
    }
    if (step <= 0)
    {
        step = 1;
    }
    std::string macros_s, dbFile_s;
    subMacros(macros_s, macros, loopVar);
    subMacros(dbFile_s, dbFile, loopVar);
    MAC_HANDLE* mh = NULL;
	char macros_exp[1024], dbFile_exp[1024];
    macCreateHandle(&mh, NULL);
	loadMacEnviron(mh);
    for(int i = start; i <= stop; i += step)
    {
		macPushScope(mh);
        epicsSnprintf(loopVal, sizeof(loopVal), "%d", i);
        macPutValue(mh, loopVar, loopVal);   
        macExpandString(mh, macros_s.c_str(), macros_exp, sizeof(macros_exp));
        macExpandString(mh, dbFile_s.c_str(), dbFile_exp, sizeof(dbFile_exp));
        std::ostringstream new_macros;
        new_macros << macros_exp << (strlen(macros_exp) > 0 ? "," : "") << loopVar << "=" << i;
        std::cout << "--> (" << i << ") dbLoadRecords(\"" << dbFile_exp << "\",\"" << new_macros.str() << "\")" << std::endl;
        dbLoadRecords(dbFile_exp, new_macros.str().c_str());
		macPopScope(mh);
    }
	macDeleteHandle(mh);		
}
/// Load a db file multiple times according to a list of items separated by known separator(s).
///
/// The \a dbFile and \a macros arguments are like the normal dbLoadRecords() however
/// it is possible to embed a macro within these whose value takes a value from the \a list.
/// You can either load the same \a dbFile multiple times with different macros, or even load
/// different database files by using \a loopVar as part of the filename. If you want to use a 
/// pure numeric range see dbLoadRecordsLoop()
///
/// The name of the macro to be used for substitution is contained in \a loopVar and needs to be
/// reference in an \\ escaped way to make sure EPICS does not try to substitute it too soon.
/// as well as the \a macros the \a dbFile is also passed the \a loopVar macro value
/// @code
///     dbLoadRecordsList("file\$(S).db", "P=1,Q=Hello\$(S)", "S", "A;B;C", ";")
/// @endcode 
///
/// @param[in] dbFile @copydoc dbLoadRecordsListInitArg0
/// @param[in] macros @copydoc dbLoadRecordsListInitArg1
/// @param[in] loopVar @copydoc dbLoadRecordsListInitArg2
/// @param[in] list @copydoc dbLoadRecordsListInitArg3
/// @param[in] sep @copydoc dbLoadRecordsListInitArg4
epicsShareFunc void dbLoadRecordsList(const char* dbFile, const char* macros, const char* loopVar, const char* list, const char* sep)
{
    static const char* default_sep = ";";
    if (loopVar == NULL || list == NULL)
    {
        dbLoadRecords(dbFile, macros);
        return;
    }
	if (sep == NULL)
	{
		sep = default_sep;
	}
    std::string macros_s, dbFile_s;
    subMacros(macros_s, macros, loopVar);
    subMacros(dbFile_s, dbFile, loopVar);
    MAC_HANDLE* mh = NULL;
	char macros_exp[1024], dbFile_exp[1024];
    macCreateHandle(&mh, NULL);
	loadMacEnviron(mh);
    char* saveptr = NULL;
    char* list_tmp = strdup(list);
    char* list_item = epicsStrtok_r(list_tmp, sep, &saveptr);
    while(list_item != NULL)
    {
		macPushScope(mh);
        macPutValue(mh, loopVar, list_item);   
        macExpandString(mh, macros_s.c_str(), macros_exp, sizeof(macros_exp));
        macExpandString(mh, dbFile_s.c_str(), dbFile_exp, sizeof(dbFile_exp));
        std::ostringstream new_macros;
        new_macros << macros_exp << (strlen(macros_exp) > 0 ? "," : "") << loopVar << "=" << list_item;
        std::cout << "--> (" << list_item << ") dbLoadRecords(\"" << dbFile_exp << "\",\"" << new_macros.str() << "\")" << std::endl;
        dbLoadRecords(dbFile_exp, new_macros.str().c_str());
        list_item = epicsStrtok_r(NULL, sep, &saveptr);
		macPopScope(mh);
    }
    free(list_tmp);
	macDeleteHandle(mh);		
}
int main(int argc,char **argv)
{
    void *inputPvt;
    MAC_HANDLE *macPvt;
    char *pval;
    int  narg;
    char *substitutionName=0;
    char *templateName=0;
    int  i;

    inputConstruct(&inputPvt);
    macCreateHandle(&macPvt,0);
    macSuppressWarning(macPvt,1);
    while((argc>1) && (argv[1][0] == '-')) {
	narg = (strlen(argv[1])==2) ? 2 : 1;
	pval = (narg==1) ? (argv[1]+2) : argv[2];
	if(strncmp(argv[1],"-I",2)==0) {
	    inputAddPath(inputPvt,pval);
	} else if(strncmp(argv[1],"-o",2)==0) {
	    if(freopen(pval,"w",stdout)==NULL) {
            fprintf(stderr,"Can't open %s for writing: %s\n", pval, strerror(errno));
            exit(1);
        }
	} else if(strncmp(argv[1],"-M",2)==0) {
	    addMacroReplacements(macPvt,pval);
	} else if(strncmp(argv[1],"-S",2)==0) {
	    substitutionName = calloc(strlen(pval)+1,sizeof(char));
	    strcpy(substitutionName,pval);
	} else if(strncmp(argv[1],"-V",2)==0) {
	    macSuppressWarning(macPvt,0);
	    narg = 1; /* no argument for this option */
	} else {
	    usageExit();
	}
	argc -= narg;
	for(i=1; i<argc; i++) argv[i] = argv[i + narg];
    }
    if(argc>2) {
	fprintf(stderr,"too many filename arguments\n");
	usageExit();
    }
    if(argc==2) {
        templateName = calloc(strlen(argv[1])+1,sizeof(char));
        strcpy(templateName,argv[1]);
    }
    if(!substitutionName) {
	makeSubstitutions(inputPvt,macPvt,templateName);
    } else {
	void *substitutePvt;
        char *filename = 0;

	substituteOpen(&substitutePvt,substitutionName);
        while(substituteGetNextSet(substitutePvt,&filename)) {
            if(templateName) filename = templateName;
            if(!filename) {
	        fprintf(stderr,"no template file\n");
	        usageExit();
            }
            macPushScope(macPvt);
	    while((pval = substituteGetReplacements(substitutePvt))){
	        addMacroReplacements(macPvt,pval);
	        makeSubstitutions(inputPvt,macPvt,filename);
	    }
            macPopScope(macPvt);
        }
        substituteDestruct(substitutePvt);
    }
    inputDestruct(inputPvt);
    free((void *)templateName);
    free((void *)substitutionName);
    return(exitStatus);
}
Exemple #4
0
static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
	const char *path,const char *substitutions)
{
    long	status;
    inputFile	*pinputFile = NULL;
    char	*penv;
    char	**macPairs;
    
    if(*ppdbbase == 0) *ppdbbase = dbAllocBase();
    pdbbase = *ppdbbase;
    if(path && strlen(path)>0) {
	dbPath(pdbbase,path);
    } else {
	penv = getenv("EPICS_DB_INCLUDE_PATH");
	if(penv) {
	    dbPath(pdbbase,penv);
	} else {
	    dbPath(pdbbase,".");
	}
    }
    my_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char));
    freeListInitPvt(&freeListPvt,sizeof(tempListNode),100);
    if(substitutions) {
	if(macCreateHandle(&macHandle,NULL)) {
	    epicsPrintf("macCreateHandle error\n");
            status = -1;
	    goto cleanup;
	}
	macParseDefns(macHandle,(char *)substitutions,&macPairs);
	if(macPairs ==NULL) {
	    macDeleteHandle(macHandle);
	    macHandle = NULL;
	} else {
	    macInstallMacros(macHandle,macPairs);
	    free((void *)macPairs);
	    mac_input_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char));
	}
    }
    pinputFile = dbCalloc(1,sizeof(inputFile));
    if(filename) {
	pinputFile->filename = macEnvExpand(filename);
    }
    if(!fp) {
	FILE	*fp1;

	if(pinputFile->filename) pinputFile->path = dbOpenFile(pdbbase,pinputFile->filename,&fp1);
	if(!pinputFile->filename || !fp1) {
	    errPrintf(0,__FILE__, __LINE__,
		"dbRead opening file %s",pinputFile->filename);
	    free((void *)pinputFile->filename);
	    free((void *)pinputFile);
            status = -1;
            goto cleanup;
	}
	pinputFile->fp = fp1;
    } else {
	pinputFile->fp = fp;
    }
    pinputFile->line_num = 0;
    pinputFileNow = pinputFile;
    my_buffer[0] = '\0';
    my_buffer_ptr = my_buffer;
    ellAdd(&inputFileList,&pinputFile->node);
    status = pvt_yy_parse();
    dbFreePath(pdbbase);
    if(!status) { /*add RTYP and VERS as an attribute */
	DBENTRY	dbEntry;
	DBENTRY	*pdbEntry = &dbEntry;
	long	localStatus;

	dbInitEntry(pdbbase,pdbEntry);
	localStatus = dbFirstRecordType(pdbEntry);
	while(!localStatus) {
	    localStatus = dbPutRecordAttribute(pdbEntry,"RTYP",
		dbGetRecordTypeName(pdbEntry));
	    if(!localStatus)  {
		localStatus = dbPutRecordAttribute(pdbEntry,"VERS",
		    "none specified");
	    }
	    if(localStatus) {
		fprintf(stderr,"dbPutRecordAttribute status %ld\n",status);
	    } else {
	        localStatus = dbNextRecordType(pdbEntry);
	    }
	}
	dbFinishEntry(pdbEntry);
    }
cleanup:
    if(macHandle) macDeleteHandle(macHandle);
    macHandle = NULL;
    if(mac_input_buffer) free((void *)mac_input_buffer);
    mac_input_buffer = NULL;
    if(freeListPvt) freeListCleanup(freeListPvt);
    freeListPvt = NULL;
    if(my_buffer) free((void *)my_buffer);
    my_buffer = NULL;
    freeInputFileList();
    return(status);
}
/// \param[in] configSection @copydoc initArg1
/// \param[in] configFile @copydoc initArg2
/// \param[in] host @copydoc initArg3
/// \param[in] options @copydoc initArg4
/// \param[in] progid @copydoc initArg5
/// \param[in] username @copydoc initArg6
/// \param[in] password @copydoc initArg7
lvDCOMInterface::lvDCOMInterface(const char *configSection, const char* configFile, const char* host, int options, const char* progid, const char* username, const char* password) : 
m_configSection(configSection), m_pidentity(NULL), m_pxmldom(NULL), m_options(options), 
	m_progid(progid != NULL? progid : ""), m_username(username != NULL? username : ""), m_password(password != NULL ? password : ""),
	m_mac_env(NULL)
	
{
	epicsThreadOnce(&onceId, initCOM, NULL);
	if (host != NULL && host[0] != '\0') 
	{
		m_host = host;
	}
	else
	{
		//		char name_buffer[MAX_COMPUTERNAME_LENGTH + 1];
		//		DWORD name_size = MAX_COMPUTERNAME_LENGTH + 1;
		//		if ( GetComputerNameEx(ComputerNameNetBIOS, name_buffer, &name_size) != 0 )
		//		{
		//			m_host = name_buffer;
		//		}
		//		else
		//		{
		//			m_host = "localhost";
		//		}			
		m_host = "localhost";
	}
	if (macCreateHandle(&m_mac_env, NULL) != 0)
	{
		throw std::runtime_error("Cannot create mac handle");
	}
	// load current environment into m_mac_env, this is so we can create a macEnvExpand() equivalent 
	// but tied to the environment at a specific time. It is useful if we want to load the same 
	// XML file twice but with a macro defined differently in each case 
	for(char** cp = environ; *cp != NULL; ++cp)
	{
		char* str_tmp = strdup(*cp);
		char* equals_loc = strchr(str_tmp, '='); // split   name=value   string
		if (equals_loc != NULL)
		{
		    *equals_loc = '\0';
		    macPutValue(m_mac_env, str_tmp, equals_loc + 1);
		}
		free(str_tmp);
	}
	//	m_doc = new TiXmlDocument;
	//	if ( !m_doc->LoadFile(configFile) )
	//	{
	//		delete m_doc;
	//		m_doc = NULL;
	//		throw std::runtime_error("Cannot load " + std::string(configFile) + ": load failure");
	//	}
	//	m_root = m_doc->RootElement();
	DomFromCOM();
	short sResult = FALSE;
	char* configFile_expanded = envExpand(configFile);
	m_configFile = configFile_expanded;
	HRESULT hr = m_pxmldom->load(_variant_t(configFile_expanded), &sResult);
	free(configFile_expanded);
	if(FAILED(hr))
	{
		throw std::runtime_error("Cannot load XML \"" + m_configFile + "\" (expanded from \"" + std::string(configFile) + "\"): load failure");
	}
	if (sResult != VARIANT_TRUE)
	{
		throw std::runtime_error("Cannot load XML \"" + m_configFile + "\" (expanded from \"" + std::string(configFile) + "\"): load failure");
	}
	std::cerr << "Loaded XML config file \"" << m_configFile << "\" (expanded from \"" << configFile << "\")" << std::endl;
	m_extint = doPath("/lvinput/extint/@path").c_str();
	epicsAtExit(epicsExitFunc, this);
	if (m_progid.size() > 0)
	{
		if ( CLSIDFromProgID(CT2W(m_progid.c_str()), &m_clsid) != S_OK )
		{
			throw std::runtime_error("Cannot find progId " + m_progid);
		}
	}
	else
	{
		m_clsid = LabVIEW::CLSID_Application;
		wchar_t* progid_str = NULL;
		if ( ProgIDFromCLSID(m_clsid, &progid_str) == S_OK )
		{
			m_progid = CW2CT(progid_str);
			CoTaskMemFree(progid_str);
		}
		else
		{
			m_progid = "LabVIEW.Application";
		}
	}
	wchar_t* clsid_str = NULL;
	if ( StringFromCLSID(m_clsid, &clsid_str) == S_OK )
	{
		std::cerr << "Using ProgID \"" << m_progid << "\" CLSID " << CW2CT(clsid_str) << std::endl;
		CoTaskMemFree(clsid_str);
	}
	else
	{
		std::cerr << "Using ProgID \"" << m_progid << "\" but StringFromCLSID() failed" << std::endl;
	}
}
/** Create this driver's NDAttributeList (pAttributeList) by reading an XML file
  * This clears any existing attributes from this drivers' NDAttributeList and then creates a new list
  * based on the XML file.  These attributes can then be associated with an NDArray by calling asynNDArrayDriver::getAttributes()
  * passing it pNDArray->pAttributeList.
  * 
  * The following simple example XML file illustrates the way that both PVAttribute and paramAttribute attributes are defined.
  * <pre>
  * <?xml version="1.0" standalone="no" ?>
  * \<Attributes>
  * \<Attribute name="AcquireTime"         type="EPICS_PV" source="13SIM1:cam1:AcquireTime"      dbrtype="DBR_NATIVE"  description="Camera acquire time"/>
  * \<Attribute name="CameraManufacturer"  type="PARAM"    source="MANUFACTURER"                 datatype="STRING"     description="Camera manufacturer"/>
  * \</Attributes>
  * </pre>
  * Each NDAttribute (currently either an PVAttribute or paramAttribute, but other types may be added in the future) 
  * is defined with an XML <b>Attribute</b> tag.  For each attribute there are a number of XML attributes
  * (unfortunately there are 2 meanings of attribute here: the NDAttribute and the XML attribute).  
  * XML attributes have the syntax name="value".  The XML attribute names are case-sensitive and must be lower case, i.e. name="xxx", not NAME="xxx".  
  * The XML attribute values are specified by the XML Schema and are always uppercase for <b>datatype</b> and <b>dbrtype</b> attributes.
  * The XML attribute names are listed here:
  *
  * <b>name</b> determines the name of the NDAttribute.  It is required, must be unique, is case-insensitive, 
  * and must start with a letter.  It can include only letters, numbers and underscore. (No whitespace or other punctuation.)
  *
  * <b>type</b> determines the type of the NDAttribute.  "EPICS_PV" creates a PVAttribute, while "PARAM" creates a paramAttribute.
  * The default is EPICS_PV if this XML attribute is absent.
  *
  * <b>source</b> determines the source of the NDAttribute.  It is required. If type="EPICS_PV" then this is the name of the EPICS PV, which is
  * case-sensitive. If type="PARAM" then this is the drvInfo string that is used in EPICS database files (e.g. ADBase.template) to identify
  * this parameter.
  *
  * <b>dbrtype</b> determines the data type that will be used to read an EPICS_PV value with channel access.  It can be one of the standard EPICS
  * DBR types (e.g. "DBR_DOUBLE", "DBR_STRING", ...) or it can be the special type "DBR_NATIVE" which means to use the native channel access
  * data type for this PV.  The default is DBR_NATIVE if this XML attribute is absent.  Always use uppercase.
  *
  * <b>datatype</b> determines the parameter data type for type="PARAM".  It must match the actual data type in the driver or plugin
  * parameter library, and must be "INT", "DOUBLE", or "STRING".  The default is "INT" if this XML attribute is absent.   Always use uppercase.
  * 
  * <b>addr</b> determines the asyn addr (address) for type="PARAM".  The default is 0 if the XML attribute is absent.
  * 
  * <b>description</b> determines the description for this attribute.  It is not required, and the default is a NULL string.
  *
  */
asynStatus asynNDArrayDriver::readNDAttributesFile()
{
    const char *pName, *pSource, *pAttrType, *pDescription;
    xmlDocPtr doc;
    xmlNode *Attr, *Attrs;
    std::ostringstream buff;
    std::string buffer;
    std::ifstream infile;
    std::string attributesMacros;
    std::string fileName;
    MAC_HANDLE *macHandle;
    char **macPairs;
    int bufferSize;
    char *tmpBuffer = 0;
    int status;
    static const char *functionName = "readNDAttributesFile";
    
    getStringParam(NDAttributesFile, fileName);
    getStringParam(NDAttributesMacros, attributesMacros);

    /* Clear any existing attributes */
    this->pAttributeList->clear();
    if (fileName.length() == 0) return asynSuccess;

    infile.open(fileName.c_str());
    if (infile.fail()) {
        asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
            "%s::%s error opening file %s\n", 
            driverName, functionName, fileName.c_str());
        setIntegerParam(NDAttributesStatus, NDAttributesFileNotFound);
        return asynError;
    }
    buff << infile.rdbuf();
    buffer = buff.str();

    // We now have file in memory.  Do macro substitution if required
    if (attributesMacros.length() > 0) {
        macCreateHandle(&macHandle, 0);
        status = macParseDefns(macHandle, attributesMacros.c_str(), &macPairs);
        if (status < 0) { 
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s::%s, error parsing macros\n", driverName, functionName);
            goto done_macros;
        }
        status = macInstallMacros(macHandle, macPairs);
        if (status < 0) {
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s::%s, error installed macros\n", driverName, functionName);
            goto done_macros;
        }
        // Create a temporary buffer 10 times larger than input buffer
        bufferSize = (int)(buffer.length() * 10);
        tmpBuffer = (char *)malloc(bufferSize);
        status = macExpandString(macHandle, buffer.c_str(), tmpBuffer, bufferSize);
        // NOTE: There is a bug in macExpandString up to 3.14.12.6 and 3.15.5 so that it does not return <0
        // if there is an undefined macro which is not the last macro in the string.
        // We work around this by testing also if the returned string contains ",undefined)".  This is
        // unlikely to occur otherwise.  Eventually we can remove this test.
        if ((status < 0)  || strstr(tmpBuffer, ",undefined)")) {
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s::%s, error expanding macros\n", driverName, functionName);
            goto done_macros;
        }
        if (status >= bufferSize) {
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s::%s, error macro buffer too small\n", driverName, functionName);
            goto done_macros;
        }
        buffer = tmpBuffer;
done_macros:
        macDeleteHandle(macHandle);
        free(tmpBuffer);
        if (status < 0) {
            setIntegerParam(NDAttributesStatus, NDAttributesMacroError);
            return asynError;
        } 
    }
    // Assume failure
    setIntegerParam(NDAttributesStatus, NDAttributesXMLSyntaxError);
    doc = xmlReadMemory(buffer.c_str(), (int)buffer.length(), "noname.xml", NULL, 0);
    if (doc == NULL) {
        asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
            "%s:%s: error creating doc\n", driverName, functionName);
        return asynError;
    }
    Attrs = xmlDocGetRootElement(doc);
    if ((!xmlStrEqual(Attrs->name, (const xmlChar *)"Attributes"))) {
        asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
            "%s:%s: cannot find Attributes element\n", driverName, functionName);
        return asynError;
    }
    for (Attr = xmlFirstElementChild(Attrs); Attr; Attr = xmlNextElementSibling(Attr)) {
        pName = (const char *)xmlGetProp(Attr, (const xmlChar *)"name");
        if (!pName) {
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s:%s: name attribute not found\n", driverName, functionName);
            return asynError;
        }
        pDescription = (const char *)xmlGetProp(Attr, (const xmlChar *)"description");
        if (!pDescription) pDescription = "";
        pSource = (const char *)xmlGetProp(Attr, (const xmlChar *)"source");
        if (!pSource) {
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s:%s: source attribute not found for attribute %s\n", driverName, functionName, pName);
            return asynError;
        }
        pAttrType = (const char *)xmlGetProp(Attr, (const xmlChar *)"type");
        if (!pAttrType) pAttrType = NDAttribute::attrSourceString(NDAttrSourceEPICSPV);
        if (strcmp(pAttrType, NDAttribute::attrSourceString(NDAttrSourceEPICSPV)) == 0) {
            const char *pDBRType = (const char *)xmlGetProp(Attr, (const xmlChar *)"dbrtype");
            int dbrType = DBR_NATIVE;
            if (pDBRType) {
                if      (!strcmp(pDBRType, "DBR_CHAR"))   dbrType = DBR_CHAR;
                else if (!strcmp(pDBRType, "DBR_SHORT"))  dbrType = DBR_SHORT;
                else if (!strcmp(pDBRType, "DBR_ENUM"))   dbrType = DBR_ENUM;
                else if (!strcmp(pDBRType, "DBR_INT"))    dbrType = DBR_INT;
                else if (!strcmp(pDBRType, "DBR_LONG"))   dbrType = DBR_LONG;
                else if (!strcmp(pDBRType, "DBR_FLOAT"))  dbrType = DBR_FLOAT;
                else if (!strcmp(pDBRType, "DBR_DOUBLE")) dbrType = DBR_DOUBLE;
                else if (!strcmp(pDBRType, "DBR_STRING")) dbrType = DBR_STRING;
                else if (!strcmp(pDBRType, "DBR_NATIVE")) dbrType = DBR_NATIVE;
                else {
                    asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                        "%s:%s: unknown dbrType = %s for attribute %s\n", driverName, functionName, pDBRType, pName);
                   return asynError;
                }
            }
            asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER,
                "%s:%s: Name=%s, PVName=%s, pDBRType=%s, dbrType=%d, pDescription=%s\n",
                driverName, functionName, pName, pSource, pDBRType, dbrType, pDescription);
#ifndef EPICS_LIBCOM_ONLY
            PVAttribute *pPVAttribute = new PVAttribute(pName, pDescription, pSource, dbrType);
            this->pAttributeList->add(pPVAttribute);
#endif
        } else if (strcmp(pAttrType, NDAttribute::attrSourceString(NDAttrSourceParam)) == 0) {
            const char *pDataType = (const char *)xmlGetProp(Attr, (const xmlChar *)"datatype");
            if (!pDataType) pDataType = "int";
            const char *pAddr = (const char *)xmlGetProp(Attr, (const xmlChar *)"addr");
            int addr=0;
            if (pAddr) addr = strtol(pAddr, NULL, 0);
            asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER,
                "%s:%s: Name=%s, drvInfo=%s, dataType=%s,pDescription=%s\n",
                driverName, functionName, pName, pSource, pDataType, pDescription); 
            paramAttribute *pParamAttribute = new paramAttribute(pName, pDescription, pSource, addr, this, pDataType);
            this->pAttributeList->add(pParamAttribute);
        } else if (strcmp(pAttrType, NDAttribute::attrSourceString(NDAttrSourceFunct)) == 0) {
            const char *pParam = (const char *)xmlGetProp(Attr, (const xmlChar *)"param");
            if (!pParam) pParam = epicsStrDup("");
            asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER,
                "%s:%s: Name=%s, function=%s, pParam=%s, pDescription=%s\n",
                driverName, functionName, pName, pSource, pParam, pDescription); 
#ifndef EPICS_LIBCOM_ONLY
            functAttribute *pFunctAttribute = new functAttribute(pName, pDescription, pSource, pParam);
            this->pAttributeList->add(pFunctAttribute);
#endif
        } else {
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s:%s: unknown attribute type = %s for attribute %s\n", driverName, functionName, pAttrType, pName);
            return asynError;
        }
    }
    setIntegerParam(NDAttributesStatus, NDAttributesOK);
    // Wait a short while for channel access callbacks on EPICS PVs
    epicsThreadSleep(0.5);
    // Get the initial values
    this->pAttributeList->updateValues();
    return asynSuccess;
}