1230 lines
36 KiB
C++
1230 lines
36 KiB
C++
//=============================================================================
|
|
// File: tmplfile.cpp
|
|
// Author: a-jammar
|
|
// Covers: CTemplateFileFunctions
|
|
//
|
|
// Copyright (c) 1998-1999 Microsoft Corporation
|
|
//
|
|
// This file contains the functions necessary to read in the MSInfo template
|
|
// file. The template file is a text file with an NFT extension. Reading
|
|
// multiple template files is allowed - the contents are merged together into
|
|
// the category tree maintained by the snap-in.
|
|
//=============================================================================
|
|
|
|
#include "stdafx.h"
|
|
#include "gather.h"
|
|
#include "gathint.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This function is the main entry point for reading the template file into
|
|
// the category structure used by the CDataGatherer object. If there is no
|
|
// tree when this function is called, the tree is created from the contents
|
|
// of the file. If there is a tree already in place, then the contents of the
|
|
// file are loaded under the existing root node.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::ReadTemplateFile(CFile *pFile, CDataGatherer *pGatherer)
|
|
{
|
|
ASSERT(pFile && pGatherer);
|
|
|
|
if (!VerifyUNICODEFile(pFile))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadTemplateFile() passed non-UNICODE file\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!ReadHeaderInfo(pFile, pGatherer))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadTemplateFile() failed from ReadHeaderInfo\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (pGatherer->m_dwRootID)
|
|
{
|
|
// There is already a tree present. Insert the contents of the file under
|
|
// the root node, after the last first level node. Walk through the first
|
|
// level of the internal category tree. TBD: add a way to extend a specified node
|
|
|
|
INTERNAL_CATEGORY *pInternal = pGatherer->GetInternalRep(pGatherer->m_dwRootID);
|
|
ASSERT(pInternal);
|
|
|
|
if (pInternal)
|
|
{
|
|
pInternal = pGatherer->GetInternalRep(pInternal->m_dwChildID);
|
|
while (pInternal && pInternal->m_dwNextID)
|
|
pInternal = pGatherer->GetInternalRep(pInternal->m_dwNextID);
|
|
}
|
|
|
|
DWORD dwPrevID = (pInternal) ? pInternal->m_dwID : 0;
|
|
if (ReadNodeRecursive(pFile, pGatherer, pGatherer->m_dwRootID, dwPrevID) == 0)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
pGatherer->m_dwRootID = ReadNodeRecursive(pFile, pGatherer, 0, 0);
|
|
if (pGatherer->m_dwRootID == 0)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This method reads the header information from the file before the recursive
|
|
// category descriptions. Note: since this is the first and only version of
|
|
// the template file, we take the easy way out and just make sure that the
|
|
// identifier and version are there.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::ReadHeaderInfo(CFile *pFile, CDataGatherer * /* pGatherer */)
|
|
{
|
|
return VerifyAndAdvanceFile(pFile, CString(_T(TEMPLATE_FILE_TAG)));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This method verifies that the passed file is a UNICODE file, by reading the
|
|
// value 0xFEFF from the file. It also leaves the file pointer past this word.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::VerifyUNICODEFile(CFile *pFile)
|
|
{
|
|
WORD verify;
|
|
|
|
if (pFile->Read((void *) &verify, sizeof(WORD)) != sizeof(WORD))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::VerifyUNICODEFile() couldn't read WORD\n");
|
|
return FALSE;
|
|
}
|
|
|
|
return (verify == 0xFEFF);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This is the recursive function to read a node. It reads the information
|
|
// from the node parameters, creates the node, and processes the contents of
|
|
// the block following the node (contained within "{}"'s). It's called
|
|
// recursively if there are any nodes in that block.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DWORD CTemplateFileFunctions::ReadNodeRecursive(CFile *pFile, CDataGatherer *pGatherer, DWORD dwParentID, DWORD dwPrevID)
|
|
{
|
|
// Determine if we need to create a new category for this node. Search through the
|
|
// other sibling nodes to see if there is one which matches this category's name.
|
|
// If there is, just use that category, and don't create a new one. Read the
|
|
// information from the file to determine the identifier for the new category.
|
|
|
|
CString strEnumerateClass, strIdentifier;
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString(NODE_KEYWORD) + CString("(")))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() Verify.. failed on node keyword\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!ReadArgument(pFile, strEnumerateClass))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on enumerate class\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!ReadArgument(pFile, strIdentifier))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on identifier\n");
|
|
return 0;
|
|
}
|
|
|
|
// Look for a node among the siblings which has a matching strIdentifier.
|
|
|
|
INTERNAL_CATEGORY * pInternal;
|
|
DWORD dwSearchID = dwPrevID, dwMatchingID = 0;
|
|
|
|
while (dwMatchingID == 0 && dwSearchID != 0)
|
|
{
|
|
pInternal = pGatherer->GetInternalRep(dwSearchID);
|
|
|
|
if (pInternal)
|
|
{
|
|
if (pInternal->m_strIdentifier.CompareNoCase(strIdentifier) == 0)
|
|
dwMatchingID = dwSearchID;
|
|
dwSearchID = pInternal->m_dwPrevID;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(pInternal);
|
|
break;
|
|
}
|
|
}
|
|
|
|
INTERNAL_CATEGORY * pCategory = ((dwMatchingID) ? pInternal : NULL);
|
|
DWORD dwID = dwMatchingID;
|
|
|
|
if (pCategory == NULL)
|
|
{
|
|
// Create the category for the node.
|
|
|
|
dwID = CreateCategory(pGatherer, dwParentID, dwPrevID);
|
|
pCategory = pGatherer->GetInternalRep(dwID);
|
|
if (!pCategory)
|
|
return 0;
|
|
|
|
// Read the contents of the node argument list ("node(enum, identifier, field(source, formatstr, arg...))")
|
|
// We've already read up to and including the identifier.
|
|
|
|
pCategory->m_strEnumerateClass = strEnumerateClass;
|
|
pCategory->m_strIdentifier = strIdentifier;
|
|
|
|
if (!ReadField(pFile, pCategory->m_fieldName))
|
|
return 0;
|
|
|
|
// Copy the field name to the name of the category (they are two different
|
|
// member variables to allow for dynamically refreshed names, which turns
|
|
// out to be unnecessary in this version).
|
|
|
|
pCategory->m_categoryName.m_strText = pCategory->m_fieldName.m_strFormat;
|
|
|
|
if (!ReadArgument(pFile, pCategory->m_strNoInstances))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on no instance message\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This node already existed, and we just want to read past the rest of
|
|
// it's description without changing the existing node.
|
|
|
|
GATH_FIELD fieldTemp;
|
|
CString strTemp;
|
|
|
|
if (!ReadField(pFile, fieldTemp))
|
|
return 0;
|
|
|
|
if (!ReadArgument(pFile, strTemp))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() ReadArgument failed on no instance message\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString("){")))
|
|
{
|
|
TRACE1("-- CTemplateFileFunctions::ReadNodeRecursive() Verify.. failed on node \"){\" (%s)\n", pCategory->m_strIdentifier);
|
|
return 0;
|
|
}
|
|
|
|
// Process the contents of the block (enclosed in "{}") for this node.
|
|
|
|
DWORD dwSubNodePrev = 0, dwNewNode = 0;
|
|
CString strKeyword;
|
|
|
|
// If this new category isn't actually new (i.e. it is being read from a
|
|
// template and overlaps an existing category) see if there are any
|
|
// existing children.
|
|
|
|
if (pCategory->m_dwChildID)
|
|
{
|
|
pInternal = pGatherer->GetInternalRep(pCategory->m_dwChildID); ASSERT(pInternal);
|
|
while (pInternal && pInternal->m_dwNextID)
|
|
pInternal = pGatherer->GetInternalRep(pInternal->m_dwNextID);
|
|
|
|
if (pInternal)
|
|
dwSubNodePrev = pInternal->m_dwID;
|
|
}
|
|
|
|
while (GetKeyword(pFile, strKeyword))
|
|
{
|
|
if (strKeyword.CompareNoCase(CString(NODE_KEYWORD)) == 0)
|
|
{
|
|
dwNewNode = ReadNodeRecursive(pFile, pGatherer, dwID, dwSubNodePrev);
|
|
if (dwNewNode == 0)
|
|
return 0;
|
|
|
|
// If this is the first child node we've read, save its ID.
|
|
|
|
if (pCategory->m_dwChildID == 0)
|
|
pCategory->m_dwChildID = dwNewNode;
|
|
|
|
// If we've read another child node, set its next field appropriately.
|
|
|
|
if (dwSubNodePrev)
|
|
{
|
|
INTERNAL_CATEGORY * pPrevCategory = pGatherer->GetInternalRep(dwSubNodePrev);
|
|
if (pPrevCategory)
|
|
pPrevCategory->m_dwNextID = dwNewNode;
|
|
}
|
|
dwSubNodePrev = dwNewNode;
|
|
}
|
|
else if (strKeyword.CompareNoCase(CString(COLUMN_KEYWORD)) == 0)
|
|
{
|
|
if (!ReadColumnInfo(pFile, pGatherer, dwID))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() failed on ReadColumnInfo\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (strKeyword.CompareNoCase(CString(LINE_KEYWORD)) == 0)
|
|
{
|
|
GATH_LINESPEC * pNewLineSpec = ReadLineInfo(pFile, pGatherer);
|
|
|
|
if (pNewLineSpec == NULL)
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() failed on ReadLineInfo\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Add the line we just read in to the end of the list of line specs for this
|
|
// internal category.
|
|
|
|
if (pCategory->m_pLineSpec == NULL)
|
|
pCategory->m_pLineSpec = pNewLineSpec;
|
|
else
|
|
{
|
|
GATH_LINESPEC * pLineSpec = pCategory->m_pLineSpec;
|
|
while (pLineSpec->m_pNext)
|
|
pLineSpec = pLineSpec->m_pNext;
|
|
pLineSpec->m_pNext = pNewLineSpec;
|
|
}
|
|
}
|
|
else if (strKeyword.CompareNoCase(CString(ENUMLINE_KEYWORD)) == 0)
|
|
{
|
|
GATH_LINESPEC * pNewLineSpec = ReadLineEnumRecursive(pFile, pGatherer);
|
|
|
|
if (pNewLineSpec == NULL)
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::ReadNodeRecursive() failed on ReadLineEnumRecursive\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Add the line we just read in to the end of the list of line specs for this
|
|
// internal category.
|
|
|
|
if (pCategory->m_pLineSpec == NULL)
|
|
pCategory->m_pLineSpec = pNewLineSpec;
|
|
else
|
|
{
|
|
GATH_LINESPEC * pLineSpec = pCategory->m_pLineSpec;
|
|
while (pLineSpec->m_pNext)
|
|
pLineSpec = pLineSpec->m_pNext;
|
|
pLineSpec->m_pNext = pNewLineSpec;
|
|
}
|
|
|
|
//if (!ReadLineEnumRecursive(pFile, pGatherer, dwID))
|
|
//{
|
|
// TRACE0("CTemplateFileFunctions::ReadNodeRecursive() failed on ReadLineEnumRecursive\n");
|
|
// return FALSE;
|
|
//}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(FALSE);
|
|
VerifyAndAdvanceFile(pFile, strKeyword);
|
|
}
|
|
}
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString("}")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadNodeRecursive() Verify.. failed on \"}\"\n");
|
|
return 0;
|
|
}
|
|
|
|
return dwID;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This method verifies that the text in strVerify comes next in the file (not
|
|
// including case or whitespace differences) and advances the file past that
|
|
// text. If the text was the next content in the file, TRUE is returned,
|
|
// otherwise FALSE. If FALSE is returned, the file is backed up to where it
|
|
// was when this method was called.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::VerifyAndAdvanceFile(CFile * pFile, const CString &strVerify)
|
|
{
|
|
DWORD dwPosition = pFile->GetPosition();
|
|
TCHAR cLastChar, cCurrentChar = _T('\0');
|
|
BOOL fInComment = FALSE;
|
|
int iCharIndex = 0, iStringLen = strVerify.GetLength();
|
|
|
|
while (iCharIndex < iStringLen)
|
|
{
|
|
// Save the last character read, since the comment token ("//") is
|
|
// two characters long.
|
|
|
|
cLastChar = cCurrentChar;
|
|
|
|
// Read the next character in the file.
|
|
|
|
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
|
|
{
|
|
TRACE0("-- CTemplateFileFunctions::VerifyAndAdvanceFile() couldn't read character\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// If we're in a comment, and the character we just read isn't a new line,
|
|
// we want to ignore it.
|
|
|
|
if (fInComment)
|
|
{
|
|
if (cCurrentChar == _T('\n'))
|
|
fInComment = FALSE;
|
|
continue;
|
|
}
|
|
|
|
// Check to see if we've started into a comment. Note that we ignore
|
|
// the first '/' also by continuing.
|
|
|
|
if (cCurrentChar == _T('/'))
|
|
{
|
|
if (cLastChar == _T('/'))
|
|
fInComment = TRUE;
|
|
continue;
|
|
}
|
|
|
|
// Skip whitespace, and also leading commas.
|
|
|
|
if (_istspace(cCurrentChar) || (cCurrentChar == _T(',') && iCharIndex == 0))
|
|
continue;
|
|
|
|
if (cCurrentChar != strVerify[iCharIndex])
|
|
{
|
|
pFile->Seek((LONG)dwPosition, CFile::begin);
|
|
return FALSE;
|
|
}
|
|
|
|
iCharIndex++;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Create a new category, and return the ID for the category.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DWORD CTemplateFileFunctions::CreateCategory(CDataGatherer * pGatherer, DWORD dwParentID, DWORD dwPrevID)
|
|
{
|
|
DWORD dwID = pGatherer->m_dwNextFreeID;
|
|
INTERNAL_CATEGORY * pInternalCat;
|
|
INTERNAL_CATEGORY * pPreviousCat;
|
|
CString strName;
|
|
|
|
pInternalCat = new INTERNAL_CATEGORY;
|
|
if (!pInternalCat)
|
|
return 0;
|
|
|
|
pInternalCat->m_fListView = TRUE;
|
|
pInternalCat->m_dwID = dwID;
|
|
pInternalCat->m_dwParentID = dwParentID;
|
|
pInternalCat->m_dwPrevID = dwPrevID;
|
|
|
|
if (dwPrevID)
|
|
{
|
|
pPreviousCat = pGatherer->GetInternalRep(dwPrevID);
|
|
if (pPreviousCat)
|
|
pPreviousCat->m_dwNextID = dwID;
|
|
}
|
|
|
|
pGatherer->m_mapCategories.SetAt((WORD)dwID, (void *) pInternalCat);
|
|
pGatherer->m_dwNextFreeID++;
|
|
|
|
return dwID;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This method returns the next keyword in the file. Any whitespace or
|
|
// punctuation is skipped until an alphanumeric character is read. The keyword
|
|
// returned is the string starting with this character until whitespace or
|
|
// punctuation is encountered. Note: it is very important that this function
|
|
// returns the file to the state it was in when the function started, with
|
|
// the current position restored.
|
|
//
|
|
// TBD: inefficient
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::GetKeyword(CFile * pFile, CString & strKeyword)
|
|
{
|
|
CString strTemp = CString("");
|
|
DWORD dwPosition = pFile->GetPosition();
|
|
TCHAR cLastChar, cCurrentChar = _T('\0');
|
|
BOOL fInComment = FALSE;
|
|
|
|
// Skip over whitespace characters until we reach an alphanumeric char.
|
|
|
|
do
|
|
{
|
|
// Save the last character read, since the comment token ("//") is
|
|
// two characters long.
|
|
|
|
cLastChar = cCurrentChar;
|
|
|
|
// Read the next character in the file.
|
|
|
|
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
|
|
return FALSE;
|
|
|
|
// If we're in a comment, and the character we just read isn't a new line,
|
|
// we want to ignore it.
|
|
|
|
if (fInComment)
|
|
{
|
|
if (cCurrentChar == _T('\n'))
|
|
fInComment = FALSE;
|
|
continue;
|
|
}
|
|
|
|
// Check to see if we've started into a comment.
|
|
|
|
if (cCurrentChar == _T('/'))
|
|
{
|
|
if (cLastChar == _T('/'))
|
|
fInComment = TRUE;
|
|
continue;
|
|
}
|
|
} while (_istspace(cCurrentChar) || cCurrentChar == _T('/') || fInComment);
|
|
|
|
// Read the keyword while it's alphanumeric.
|
|
|
|
if (_istalnum(cCurrentChar))
|
|
do
|
|
{
|
|
strTemp += CString(cCurrentChar);
|
|
|
|
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
|
|
return FALSE;
|
|
} while (_istalnum(cCurrentChar));
|
|
|
|
// Reset the file, set the keyword and return.
|
|
|
|
pFile->Seek((LONG)dwPosition, CFile::begin);
|
|
strKeyword = strTemp;
|
|
return !strTemp.IsEmpty();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This method reads in a "column" line from the file, adding the appropriate
|
|
// entries for the columns into the category referenced by dwID. The column
|
|
// line contains a bunch of fields in a list.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::ReadColumnInfo(CFile * pFile, CDataGatherer * pGatherer, DWORD dwID)
|
|
{
|
|
CString strTemp;
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString(COLUMN_KEYWORD) + CString("(")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadColumnInfo() Verify.. failed on column keyword\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Get the internal category referenced by dwID.
|
|
|
|
INTERNAL_CATEGORY * pCategory = pGatherer->GetInternalRep(dwID);
|
|
if (!pCategory)
|
|
return FALSE;
|
|
|
|
// We only allow one column specifier list per node.
|
|
|
|
if (pCategory->m_pColSpec)
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadColumnInfo() already column information present\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// While we are still reading fields from the file, keep adding to the column list.
|
|
|
|
GATH_FIELD * pNewField = new GATH_FIELD;
|
|
if (pNewField == NULL)
|
|
return FALSE;
|
|
|
|
while (ReadField(pFile, *pNewField))
|
|
{
|
|
if (pCategory->m_pColSpec == NULL)
|
|
pCategory->m_pColSpec = pNewField;
|
|
else
|
|
{
|
|
// Scan to the last field in the linespec.m_pFields list, and insert the new field.
|
|
|
|
GATH_FIELD * pFieldScan = pCategory->m_pColSpec;
|
|
while (pFieldScan->m_pNext)
|
|
pFieldScan = pFieldScan->m_pNext;
|
|
pFieldScan->m_pNext = pNewField;
|
|
}
|
|
|
|
// Parse the width out of the column caption.
|
|
|
|
if (pNewField->m_strFormat.ReverseFind(_T(',')) != -1)
|
|
{
|
|
strTemp = pNewField->m_strFormat.Right(pNewField->m_strFormat.GetLength() - pNewField->m_strFormat.ReverseFind(_T(',')) - 1);
|
|
pNewField->m_usWidth = (unsigned short) atoi(strTemp);
|
|
pNewField->m_strFormat = pNewField->m_strFormat.Left(pNewField->m_strFormat.GetLength() - strTemp.GetLength() - 1);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(FALSE);
|
|
pNewField->m_usWidth = (unsigned short) 80;
|
|
}
|
|
|
|
// Parse off any remaining information in the column label (the label ends
|
|
// with [name, n], when n is the width, and name is the ID for the column
|
|
// which should not be displayed).
|
|
|
|
if (pNewField->m_strFormat.ReverseFind(_T('[')) != -1)
|
|
pNewField->m_strFormat = pNewField->m_strFormat.Left(pNewField->m_strFormat.ReverseFind(_T('[')) - 1);
|
|
|
|
// Read the sorting type from the file.
|
|
|
|
if (ReadArgument(pFile, strTemp))
|
|
{
|
|
if (strTemp.CompareNoCase(CString(_T(SORT_LEXICAL))) == 0)
|
|
pNewField->m_sort = LEXICAL;
|
|
else if (strTemp.CompareNoCase(CString(_T(SORT_VALUE))) == 0)
|
|
pNewField->m_sort = BYVALUE;
|
|
else
|
|
pNewField->m_sort = NOSORT;
|
|
}
|
|
else
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadColumnInfo() couldn't read column sorting\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Read the complexity (BASIC or ADVANCED) from the file.
|
|
|
|
if (ReadArgument(pFile, strTemp))
|
|
{
|
|
if (strTemp.CompareNoCase(CString(_T(COMPLEXITY_ADVANCED))) == 0)
|
|
pNewField->m_datacomplexity = ADVANCED;
|
|
else
|
|
pNewField->m_datacomplexity = BASIC;
|
|
}
|
|
else
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadColumnInfo() couldn't read data complexity\n");
|
|
return FALSE;
|
|
}
|
|
|
|
pNewField = new GATH_FIELD;
|
|
if (pNewField == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
delete pNewField;
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString(")")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadColumnInfo() Verify.. failed on \")\"\n");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Read in the information for a single line. Add the line to the internal
|
|
// representation of the category. TBD: inefficient, since this will be
|
|
// called multiple times and the line list will need to be scanned to the
|
|
// end each time.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
GATH_LINESPEC * CTemplateFileFunctions::ReadLineInfo(CFile * pFile, CDataGatherer * /* pGatherer */)
|
|
{
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString(LINE_KEYWORD) + CString("(")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadLineInfo() Verify.. failed on line keyword\n");
|
|
return NULL;
|
|
}
|
|
|
|
// Declare a line specification variable to store the line info.
|
|
|
|
GATH_LINESPEC * pNewLineSpec = new GATH_LINESPEC;
|
|
if (pNewLineSpec == NULL)
|
|
return NULL;
|
|
|
|
// While we are still reading fields from the file, keep adding to the column list.
|
|
// TBD: inefficient, repeated scans through linespec.m_pFields list.
|
|
|
|
GATH_FIELD * pNewField = new GATH_FIELD;
|
|
if (pNewField == NULL)
|
|
{
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
|
|
// Read in the complexity (BASIC or ADVANCED) for this line.
|
|
|
|
CString strTemp;
|
|
if (ReadArgument(pFile, strTemp))
|
|
{
|
|
if (strTemp.CompareNoCase(CString(_T(COMPLEXITY_ADVANCED))) == 0)
|
|
pNewLineSpec->m_datacomplexity = ADVANCED;
|
|
else
|
|
pNewLineSpec->m_datacomplexity = BASIC;
|
|
}
|
|
else
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadLineInfo() couldn't read complexity\n");
|
|
return FALSE;
|
|
}
|
|
|
|
while (ReadField(pFile, *pNewField))
|
|
{
|
|
if (pNewLineSpec->m_pFields == NULL)
|
|
pNewLineSpec->m_pFields = pNewField;
|
|
else
|
|
{
|
|
// Scan to the last field in the linespec.m_pFields list, and insert the new field.
|
|
|
|
GATH_FIELD * pFieldScan = pNewLineSpec->m_pFields;
|
|
while (pFieldScan->m_pNext)
|
|
pFieldScan = pFieldScan->m_pNext;
|
|
pFieldScan->m_pNext = pNewField;
|
|
}
|
|
|
|
pNewField = new GATH_FIELD;
|
|
if (pNewField == NULL)
|
|
{
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
delete pNewField;
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString(")")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadLineInfo() Verify.. failed on \")\"\n");
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
|
|
return pNewLineSpec;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This method simply reads an argument (as string) from the file, until a
|
|
// punctuation or whitespace character is found. If a quote mark is found,
|
|
// all characters are included in the string until another quote is found.
|
|
// TBD: currently no way to have a quote mark in the string.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::ReadArgument(CFile * pFile, CString & strSource)
|
|
{
|
|
BOOL fInQuote = FALSE, fInComment = FALSE;
|
|
CString strTemp;
|
|
TCHAR cLastChar, cCurrentChar = _T('\0');
|
|
|
|
// Skip over characters until we reach an alphanumeric char. If we find
|
|
// a close paren, then we've reached the end of the argument list and
|
|
// should return FALSE.
|
|
|
|
do
|
|
{
|
|
// Save the last character read, since the comment token ("//") is
|
|
// two characters long.
|
|
|
|
cLastChar = cCurrentChar;
|
|
|
|
// Read the next character in the file.
|
|
|
|
if (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) != sizeof(TCHAR))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadArgument() couldn't read character\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// If we're in a comment, and the character we just read isn't a new line,
|
|
// we want to ignore it.
|
|
|
|
if (fInComment)
|
|
{
|
|
if (cCurrentChar == _T('\n'))
|
|
fInComment = FALSE;
|
|
continue;
|
|
}
|
|
|
|
// Check to see if we've started into a comment.
|
|
|
|
if (cCurrentChar == _T('/'))
|
|
{
|
|
if (cLastChar == _T('/'))
|
|
fInComment = TRUE;
|
|
continue;
|
|
}
|
|
|
|
if (cCurrentChar == _T(')'))
|
|
return FALSE;
|
|
} while (!_istalnum(cCurrentChar) && cCurrentChar != _T('"'));
|
|
|
|
// Read characters into the string until we find whitespace or punctuation.
|
|
|
|
do
|
|
{
|
|
if (cCurrentChar == _T('"'))
|
|
{
|
|
fInQuote = !fInQuote;
|
|
continue;
|
|
}
|
|
|
|
if (_istalnum(cCurrentChar) || fInQuote)
|
|
strTemp += CString(cCurrentChar);
|
|
else
|
|
break;
|
|
} while (pFile->Read((void *) &cCurrentChar, sizeof(TCHAR)) == sizeof(TCHAR));
|
|
|
|
// If the last character read (the one which terminated this argument) was
|
|
// not a comma, then back the file up so that the character can be re-read
|
|
// and interpreted.
|
|
|
|
if (cCurrentChar != _T(','))
|
|
pFile->Seek(-(LONG)sizeof(TCHAR), CFile::current);
|
|
|
|
strSource = strTemp;
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// A field consists of a source string, followed by a format string, followed
|
|
// by a list of zero or more arguments.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::ReadField(CFile * pFile, GATH_FIELD & field)
|
|
{
|
|
// Advance past the field keyword and read the two source and format strings.
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString(FIELD_KEYWORD) + CString("(")))
|
|
return FALSE;
|
|
|
|
if (!ReadArgument(pFile, field.m_strSource))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadField() ReadArgument failed on source\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!ReadArgument(pFile, field.m_strFormat))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadField() ReadArgument failed on format\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Read arguments until there are no more, building them into a list of
|
|
// arguments stored by the FIELD struct.
|
|
|
|
GATH_VALUE arg;
|
|
GATH_VALUE * pArg = NULL;
|
|
|
|
while (ReadArgument(pFile, arg.m_strText))
|
|
{
|
|
if (pArg == NULL)
|
|
{
|
|
field.m_pArgs = new GATH_VALUE;
|
|
if (field.m_pArgs == NULL)
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadField() field.m_pArgs allocation failed\n");
|
|
return FALSE;
|
|
}
|
|
*field.m_pArgs = arg;
|
|
pArg = field.m_pArgs;
|
|
}
|
|
else
|
|
{
|
|
pArg->m_pNext = new GATH_VALUE;
|
|
if (pArg->m_pNext == NULL)
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadField() pArg->m_pNext allocation failed\n");
|
|
return FALSE;
|
|
}
|
|
*pArg->m_pNext = arg;
|
|
pArg = pArg->m_pNext;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Read an enumline(){} block. This construct is used to group lines together
|
|
// which are enumerated for each instance of a class. A line is added to
|
|
// the parent node's list of lines with a m_strEnumerateClass equal to the
|
|
// class to be enumerated. The added line structure will have children lines
|
|
// (the lines to be enumerated) referenced by m_pEnumeratedGroup.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
GATH_LINESPEC * CTemplateFileFunctions::ReadLineEnumRecursive(CFile * pFile, CDataGatherer * pGatherer)
|
|
{
|
|
if (!VerifyAndAdvanceFile(pFile, CString(ENUMLINE_KEYWORD) + CString("(")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() Verify.. failed on enum line keyword\n");
|
|
return NULL;
|
|
}
|
|
|
|
// Declare a line specification variable to store the line info.
|
|
|
|
GATH_LINESPEC * pNewLineSpec = new GATH_LINESPEC;
|
|
if (pNewLineSpec == NULL)
|
|
return NULL;
|
|
|
|
// Read in the enumerated class variable.
|
|
|
|
if (!ReadArgument(pFile, pNewLineSpec->m_strEnumerateClass))
|
|
{
|
|
delete pNewLineSpec;
|
|
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() ReadArgument failed on enumerate class\n");
|
|
return NULL;
|
|
}
|
|
|
|
// Read in the variable (zero or more) number of fields for the constraints.
|
|
|
|
GATH_FIELD * pNewField = new GATH_FIELD;
|
|
if (pNewField == NULL)
|
|
return NULL;
|
|
|
|
while (ReadField(pFile, *pNewField))
|
|
{
|
|
if (pNewLineSpec->m_pConstraintFields == NULL)
|
|
pNewLineSpec->m_pConstraintFields = pNewField;
|
|
else
|
|
{
|
|
// Add the newly read field to the end of the field list. Note,
|
|
// this is inefficient, and should be fixed. (TBD)
|
|
|
|
GATH_FIELD * pFieldScan = pNewLineSpec->m_pConstraintFields;
|
|
while (pFieldScan->m_pNext)
|
|
pFieldScan = pFieldScan->m_pNext;
|
|
pFieldScan->m_pNext = pNewField;
|
|
}
|
|
|
|
pNewField = new GATH_FIELD;
|
|
if (pNewField == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
delete pNewField;
|
|
|
|
// Advance past the close paren and the (necessary) open bracket.
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString("){")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() Verify.. failed on \"){\"\n");
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
|
|
// Read the contents of the block (should be all lines or enumlines).
|
|
|
|
CString strKeyword;
|
|
while (GetKeyword(pFile, strKeyword))
|
|
{
|
|
if (strKeyword.CompareNoCase(CString(LINE_KEYWORD)) == 0)
|
|
{
|
|
GATH_LINESPEC * pNewSubLine = ReadLineInfo(pFile, pGatherer);
|
|
if (pNewSubLine == NULL)
|
|
{
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
|
|
if (pNewLineSpec->m_pEnumeratedGroup == NULL)
|
|
pNewLineSpec->m_pEnumeratedGroup = pNewSubLine;
|
|
else
|
|
{
|
|
GATH_LINESPEC * pLineSpec = pNewLineSpec->m_pEnumeratedGroup;
|
|
while (pLineSpec->m_pNext)
|
|
pLineSpec = pLineSpec->m_pNext;
|
|
pLineSpec->m_pNext = pNewSubLine;
|
|
}
|
|
}
|
|
else if (strKeyword.CompareNoCase(CString(ENUMLINE_KEYWORD)) == 0)
|
|
{
|
|
GATH_LINESPEC * pNewSubLine = ReadLineEnumRecursive(pFile, pGatherer);
|
|
if (pNewSubLine == NULL)
|
|
{
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
|
|
if (pNewLineSpec->m_pEnumeratedGroup == NULL)
|
|
pNewLineSpec->m_pEnumeratedGroup = pNewSubLine;
|
|
else
|
|
{
|
|
GATH_LINESPEC * pLineSpec = pNewLineSpec->m_pEnumeratedGroup;
|
|
while (pLineSpec->m_pNext)
|
|
pLineSpec = pLineSpec->m_pNext;
|
|
pLineSpec->m_pNext = pNewSubLine;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive(), bad keyword in enumlines block\n");
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!VerifyAndAdvanceFile(pFile, CString("}")))
|
|
{
|
|
TRACE0("CTemplateFileFunctions::ReadLineEnumRecursive() Verify.. failed on \"}\"\n");
|
|
delete pNewLineSpec;
|
|
return NULL;
|
|
}
|
|
|
|
return pNewLineSpec;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This function is used to adjust the tree of loaded categories based on
|
|
// a string (which indicates what categories should be included). The
|
|
// following rules are applied:
|
|
//
|
|
// 1. By default, no categories are included.
|
|
// 2. If "+all" is in the string, all categories are included.
|
|
// 3. If "+cat" is in the string, the cat, all its children and ancestors
|
|
// are included.
|
|
// 4. If "-cat" is in the string, the cat and all its children are excluded.
|
|
//
|
|
// First this function must recurse through the tree, marking each node
|
|
// with whether it should be deleted or not. Then the nodes are actually
|
|
// removed from the tree. Yippee skip.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::ApplyCategories(const CString & strCategories, CDataGatherer * pGatherer)
|
|
{
|
|
CString strLoweredCats(strCategories);
|
|
strLoweredCats.MakeLower();
|
|
|
|
BOOL fDefaultAdd = (strLoweredCats.Find(CString(_T("+all"))) > -1);
|
|
RecurseTreeCategories(fDefaultAdd, pGatherer->m_dwRootID, strLoweredCats, pGatherer);
|
|
RemoveExtraCategories(pGatherer->m_dwRootID, pGatherer);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Remove all the categories from the tree which aren't marked as "include".
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CTemplateFileFunctions::RemoveExtraCategories(DWORD dwID, CDataGatherer * pGatherer)
|
|
{
|
|
if (dwID == 0)
|
|
return;
|
|
|
|
INTERNAL_CATEGORY *pInternal = pGatherer->GetInternalRep(dwID);
|
|
if (pInternal == NULL)
|
|
return;
|
|
|
|
// If this category is not marked as included, delete it and
|
|
// all the children.
|
|
|
|
if (!pInternal->m_fIncluded)
|
|
{
|
|
DWORD dwChildID = pInternal->m_dwChildID;
|
|
DWORD dwNextChild = 0;
|
|
while (dwChildID)
|
|
{
|
|
INTERNAL_CATEGORY *pChild = pGatherer->GetInternalRep(dwChildID);
|
|
if (pChild)
|
|
dwNextChild = pChild->m_dwNextID;
|
|
else
|
|
dwNextChild = 0;
|
|
RemoveExtraCategories(dwChildID, pGatherer);
|
|
dwChildID = dwNextChild;
|
|
}
|
|
pGatherer->m_mapCategories.SetAt((WORD)pInternal->m_dwID, (void *) NULL);
|
|
delete pInternal;
|
|
|
|
return;
|
|
}
|
|
|
|
// Otherwise, if we are to save this category, scan through all the
|
|
// children, recursively calling this function on each one, and
|
|
// constructing a new list of children which are included.
|
|
|
|
INTERNAL_CATEGORY * pLastGood = NULL;
|
|
DWORD dwChildID = pInternal->m_dwChildID;
|
|
DWORD dwNextChild = 0;
|
|
while (dwChildID)
|
|
{
|
|
INTERNAL_CATEGORY *pChild = pGatherer->GetInternalRep(dwChildID);
|
|
if (pChild)
|
|
{
|
|
dwNextChild = pChild->m_dwNextID;
|
|
if (!pChild->m_fIncluded)
|
|
{
|
|
// We're removing this child. If this is the first child,
|
|
// set the pInternal field, otherwise, remove it from
|
|
// the list of children.
|
|
|
|
if (dwChildID == pInternal->m_dwChildID)
|
|
pInternal->m_dwChildID = dwNextChild;
|
|
else if (pLastGood) // this better be true
|
|
pLastGood->m_dwNextID = dwNextChild;
|
|
}
|
|
else
|
|
pLastGood = pChild;
|
|
RemoveExtraCategories(dwChildID, pGatherer);
|
|
}
|
|
else
|
|
dwNextChild = 0;
|
|
dwChildID = dwNextChild;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This function recursively processes the categories to determine which
|
|
// ones should be included.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CTemplateFileFunctions::RecurseTreeCategories(BOOL fParentOK, DWORD dwID, const CString & strCategories, CDataGatherer * pGatherer)
|
|
{
|
|
if (dwID == 0)
|
|
return FALSE;
|
|
|
|
INTERNAL_CATEGORY *pInternal = pGatherer->GetInternalRep(dwID);
|
|
if (pInternal == NULL)
|
|
return FALSE;
|
|
|
|
// Default to using the same status as the parent category.
|
|
|
|
pInternal->m_fIncluded = fParentOK;
|
|
|
|
// If we are added or removed by the category string, change our status.
|
|
|
|
CString strCategoryID(pInternal->m_strIdentifier);
|
|
strCategoryID.MakeLower();
|
|
|
|
int iIndex = strCategories.Find(strCategoryID);
|
|
if (iIndex > 0)
|
|
{
|
|
// Make sure that we aren't matching part of a longer string,
|
|
// by making sure this is either the last string, or a + or -
|
|
// immediately follows.
|
|
|
|
if ((iIndex + strCategoryID.GetLength()) >= strCategories.GetLength() ||
|
|
strCategories[iIndex + strCategoryID.GetLength()] == _T('+') ||
|
|
strCategories[iIndex + strCategoryID.GetLength()] == _T('-'))
|
|
{
|
|
if (strCategories[iIndex - 1] == _T('+'))
|
|
pInternal->m_fIncluded = TRUE;
|
|
else if (strCategories[iIndex - 1] == _T('-'))
|
|
pInternal->m_fIncluded = FALSE;
|
|
}
|
|
}
|
|
|
|
// Now, for each child of this node, recurse using this node's status.
|
|
// If any of the children return TRUE for an included status, we must
|
|
// modify this node to TRUE.
|
|
|
|
DWORD dwChildID = pInternal->m_dwChildID;
|
|
BOOL fChildIncluded = FALSE;
|
|
while (dwChildID)
|
|
{
|
|
fChildIncluded |= RecurseTreeCategories(pInternal->m_fIncluded, dwChildID, strCategories, pGatherer);
|
|
INTERNAL_CATEGORY *pChild = pGatherer->GetInternalRep(dwChildID);
|
|
if (pChild)
|
|
dwChildID = pChild->m_dwNextID;
|
|
else
|
|
dwChildID = 0;
|
|
}
|
|
|
|
pInternal->m_fIncluded |= fChildIncluded;
|
|
return pInternal->m_fIncluded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This function is used to load template information from DLLs (the new
|
|
// method, to allow resources to be selected on the fly). The HKEY passed in
|
|
// is the base key for the entries which describe the DLLs containing template
|
|
// information. It's enumerated for subkeys, each of which is used to load a
|
|
// DLL. A standard entry point for the DLL is used, and the template
|
|
// information retrieved and passed into the file parsing functions.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
typedef DWORD (__cdecl *pfuncGetTemplate)(void ** ppBuffer);
|
|
extern "C" DWORD __cdecl GetTemplate(void ** ppBuffer);
|
|
|
|
BOOL CTemplateFileFunctions::LoadTemplateDLLs(HKEY hkeyBase, CDataGatherer * pGatherer)
|
|
{
|
|
CStringList strlistTemplates;
|
|
|
|
// Add a keyword to the list of DLLs which indicates that we should add
|
|
// information from ourselves (we don't want to just add ourselves normally,
|
|
// since we would do a LoadLibrary on ourselves, which opens up can of
|
|
// unnecessary initializion worms). So, we'll just add "this" to the string list.
|
|
|
|
strlistTemplates.AddTail(_T("this"));
|
|
|
|
// Enumerate the registry key, adding each subkey to a list of DLL names to
|
|
// process (the DLL path is in the default value of the subkey).
|
|
|
|
if (hkeyBase)
|
|
{
|
|
TCHAR szName[64], szValue[MAX_PATH];
|
|
DWORD dwIndex = 0;
|
|
DWORD dwLength = sizeof(szName) / sizeof(TCHAR);
|
|
|
|
while (ERROR_SUCCESS == RegEnumKeyEx(hkeyBase, dwIndex++, szName, &dwLength, NULL, NULL, NULL, NULL))
|
|
{
|
|
dwLength = sizeof(szValue) / sizeof(TCHAR);
|
|
if (ERROR_SUCCESS == RegQueryValue(hkeyBase, szName, szValue, (long *)&dwLength))
|
|
if (*szValue)
|
|
strlistTemplates.AddTail(szValue);
|
|
|
|
dwLength = sizeof(szName) / sizeof(TCHAR);
|
|
}
|
|
}
|
|
|
|
// For each DLL in the list of templates, we'll attempt to get the template info.
|
|
|
|
CString strFileName;
|
|
HINSTANCE hinst;
|
|
DWORD dwBufferSize;
|
|
pfuncGetTemplate pfunc;
|
|
unsigned char * pBuffer;
|
|
CMemFile memfile;
|
|
|
|
while (!strlistTemplates.IsEmpty())
|
|
{
|
|
strFileName = strlistTemplates.RemoveHead();
|
|
|
|
// Try to load the library, and get a pointer to the entry point.
|
|
|
|
if (strFileName.Compare(_T("this")) == 0)
|
|
{
|
|
hinst = NULL;
|
|
pfunc = &GetTemplate;
|
|
}
|
|
else
|
|
{
|
|
hinst = LoadLibrary(strFileName);
|
|
if (hinst == NULL)
|
|
continue;
|
|
|
|
pfunc = (pfuncGetTemplate) GetProcAddress(hinst, "GetTemplate");
|
|
if (pfunc == NULL)
|
|
{
|
|
FreeLibrary(hinst);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Call the DLL function with a NULL parameter to get the size of the buffer.
|
|
|
|
dwBufferSize = (*pfunc)((void **)&pBuffer);
|
|
if (dwBufferSize && pBuffer)
|
|
{
|
|
memfile.Attach((BYTE *)pBuffer, dwBufferSize, 0);
|
|
CTemplateFileFunctions::ReadTemplateFile(&memfile, pGatherer);
|
|
memfile.Detach();
|
|
(void)(*pfunc)(NULL); // calling the exported DLL function with NULL frees its buffers
|
|
}
|
|
|
|
if (hinst != NULL)
|
|
{
|
|
FreeLibrary(hinst);
|
|
hinst = NULL;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|