Merged PR 2399218: using inbox NSXmlParser instead of xerces on ios/macos.

ios only supports a SAX parser. This change builds simple DOM like functionality on top of NSXmlParser. Got rid of ~2.7MB of xerces from the SDK binary. Current size is ~1.6MB.
Can still use the xerces if needed using the -parser-xerces build flag. Default is to use inbox implementation.

Verified tests pass on macos and ios.

Related work items: #18565171
This commit is contained in:
Adrian Mascarenhas 2018-10-02 21:25:11 +00:00 коммит произвёл msftrubengu
Родитель 08c6b2cc0c
Коммит 5c3284d61c
16 изменённых файлов: 480 добавлений и 33 удалений

Просмотреть файл

@ -4,6 +4,8 @@ build=MinSizeRel
arch=x86_64
dataCompressionLib=libcompression
bundle=off
xmlparserLib=applexml
xmlparser="-DXML_PARSER=applexml"
usage()
{
@ -12,6 +14,7 @@ usage()
echo $'\t' "-arch OSX Architecture. Default x86_64 (simulator)"
echo $'\t' "-xzlib Use MSIX SDK Zlib instead of inbox libCompression api. Default on iOS is libCompression."
echo $'\t' "-sb Skip bundle support."
echo $'\t' "-parser-xerces Use xerces xml parser instead of default apple xml parser."
}
printsetup()
@ -20,6 +23,7 @@ printsetup()
echo "Architecture:" $arch
echo "Data Compression library:" $dataCompressionLib
echo "Skip bundle support:" $bundle
echo "parser:" $xmlparserLib
}
while [ "$1" != "" ]; do
@ -33,6 +37,9 @@ while [ "$1" != "" ]; do
-xzlib )dataCompressionLib=MSIX_SDK_zlib
zlib="-DUSE_MSIX_SDK_ZLIB=on"
;;
-parser-xerces ) xmlparserLib=xerces
xmlparser="-DXML_PARSER=xerces"
;;
-sb ) bundle="on"
;;
-h ) usage
@ -51,5 +58,5 @@ cd .vs
# clean up any old builds of msix modules
find . -name *msix* -d | xargs rm -r
cmake -DCMAKE_BUILD_TYPE=$build $zlib -DIOS=on -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.cmake -DCMAKE_OSX_ARCHITECTURES=$arch -DSKIP_BUNDLES=$bundle ..
cmake -DCMAKE_BUILD_TYPE=$build $zlib -DIOS=on -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.cmake -DCMAKE_OSX_ARCHITECTURES=$arch $xmlparser -DSKIP_BUNDLES=$bundle ..
make

Просмотреть файл

@ -3,13 +3,16 @@
build=MinSizeRel
dataCompressionLib=libcompression
bundle=off
xmlparserLib=applexml
xmlparser="-DXML_PARSER=applexml"
usage()
{
echo "usage: makemac [-b buildType] [-xzlib]"
echo "usage: makemac [-b buildType] [-xzlib] [-parser-xerces]"
echo $'\t' "-b Build type. Default MinSizeRel"
echo $'\t' "-xzlib Use MSIX SDK Zlib instead of inbox libCompression api. Default on MacOS is libCompression."
echo $'\t' "-sb Skip bundle support."
echo $'\t' "-parser-xerces Use xerces xml parser instead of default apple xml parser."
}
printsetup()
@ -17,6 +20,7 @@ printsetup()
echo "Build Type:" $build
echo "Data Compression library:" $dataCompressionLib
echo "Skip bundle support:" $bundle
echo "parser:" $xmlparserLib
}
while [ "$1" != "" ]; do
@ -27,6 +31,9 @@ while [ "$1" != "" ]; do
-xzlib )dataCompressionLib=MSIX_SDK_zlib
zlib="-DUSE_MSIX_SDK_ZLIB=on"
;;
-parser-xerces ) xmlparserLib=xerces
xmlparser="-DXML_PARSER=xerces"
;;
-sb ) bundle="on"
;;
-h ) usage
@ -45,5 +52,5 @@ cd .vs
# clean up any old builds of msix modules
find . -name *msix* -d | xargs rm -r
cmake -DCMAKE_BUILD_TYPE=$build $zlib -DSKIP_BUNDLES=$bundle -DMACOS=on ..
cmake -DCMAKE_BUILD_TYPE=$build $zlib -DSKIP_BUNDLES=$bundle $xmlparser -DMACOS=on ..
make

Просмотреть файл

@ -108,6 +108,11 @@ MIDL_DEFINE_GUID(IID, IID_IXercesElement, 0x07d6ee0e,0x2165,0x4b90,0x80,0x24,0x
MIDL_DEFINE_GUID(IID, IID_IJavaXmlElement, 0x69ab3660,0x398d,0x4cd6,0xa1,0x31,0xe7,0x31,0x6,0x4,0xe,0x3b);
#endif
#ifdef USING_APPLE_XML
// {8FBC0096-E87D-406A-95D9-203ADEFBF9AF}
MIDL_DEFINE_GUID(IID, IID_IAppleXmlElement, 0x8fbc0096, 0xe87d, 0x406a, 0x95, 0xd9, 0x20, 0x3a, 0xde, 0xfb, 0xf9, 0xaf);
#endif
#ifdef USING_MSXML
MIDL_DEFINE_GUID(IID, IID_IMSXMLElement, 0x2730f595,0x0c80,0x4f3e,0x88,0x91,0x75,0x3b,0x2e,0x8c,0x30,0x5d);
MIDL_DEFINE_GUID(IID, IID_IMSXMLDom, 0xb6bca5f0,0xc6c1,0x4409,0x85,0xbe,0xe4,0x76,0xaa,0xbe,0xc1,0x9a);

Просмотреть файл

@ -22,6 +22,24 @@ if(XML_PARSER MATCHES javaxml)
add_definitions(-DUSING_JAVAXML=1)
endif()
if(XML_PARSER MATCHES applexml)
message(STATUS "XML_PARSER defined. Using apple xml parser." )
set(XmlParser)
list(APPEND XmlParser
"PAL/XML/APPLE/XmlObject.cpp"
"PAL/XML/APPLE/NSXmlParserDelegateWrapper.mm"
"PAL/XML/APPLE/NSXmlParserWrapper.mm"
"PAL/XML/APPLE/XmlDocumentReader.cpp"
)
set(XmlParserHpp)
list(APPEND XmlParserHpp
"PAL/XML/APPLE/NSXmlParserDelegateWrapper.h"
"PAL/XML/APPLE/NSXmlParserWrapper.h"
"PAL/XML/APPLE/XmlDocumentReader.hpp"
)
add_definitions(-DUSING_APPLE_XML=1)
endif()
if(XML_PARSER MATCHES msxml6)
message(STATUS "XML_PARSER defined. Using MSXML6 XML parser." )
set(XmlParser PAL/XML/msxml6/XmlObject.cpp)
@ -102,8 +120,13 @@ else()
# used to get the languages of the device
find_library(COREFOUNDATION_LIBRARY CoreFoundation)
if(NOT COREFOUNDATION_LIBRARY)
message(FATAL_ERROR "CoreFoundation not found")
message(FATAL_ERROR "CoreFoundation library not found")
endif()
find_library(FOUNDATION_LIBRARY Foundation)
if(NOT FOUNDATION_LIBRARY)
message(FATAL_ERROR "Foundation library not found")
endif()
find_library (LIBSTDCXX NAMES stdc++)
set(Applicability PAL/Applicability/Apple/Applicability.cpp)
if(IOS)
add_definitions(-DIOS)
@ -226,6 +249,7 @@ set(LIB_PRIVATE_HEADERS
../inc/ZipObject.hpp
${InteropHpp}
${BundleHeaders}
${XmlParserHpp}
)
set(LIB_SOURCES
@ -369,7 +393,7 @@ if(AOSP)
endif()
if((IOS) OR (MACOS))
target_link_libraries(${PROJECT_NAME} PRIVATE ${COREFOUNDATION_LIBRARY})
target_link_libraries(${PROJECT_NAME} PRIVATE ${COREFOUNDATION_LIBRARY} ${FOUNDATION_LIBRARY} ${LIBSTDCXX})
endif()
if(LINUX)

Просмотреть файл

@ -147,7 +147,8 @@ void CheckForJavaXmlParseException(JNIEnv* env)
}
else
{
ThrowError(MSIX::Error::XmlError);
// We treat all other parser errors as fatal.
ThrowError(MSIX::Error::XmlFatal);
}
}
@ -202,8 +203,7 @@ public:
bool ForEachElementIn(const ComPtr<IXmlElement>& root, XmlQueryName query, XmlVisitor& visitor) override
{
ComPtr<IJavaXmlElement> element;
ThrowHrIfFailed(root->QueryInterface(UuidOfImpl<IJavaXmlElement>::iid, reinterpret_cast<void**>(&element)));
ComPtr<IJavaXmlElement> element = root.As<IJavaXmlElement>();
std::unique_ptr<_jstring, JObjectDeleter> jquery(m_env->NewStringUTF(xPaths[static_cast<uint8_t>(query)]));
std::unique_ptr<_jobjectArray, JObjectDeleter> javaElements(reinterpret_cast<jobjectArray>(m_env->CallObjectMethod(m_javaXmlDom.get(), getElementsFunc, element->GetJavaObject(), jquery.get())));

Просмотреть файл

@ -0,0 +1,9 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#import <Foundation/Foundation.h>
@interface NSXmlParserDelegateWrapper : NSObject <NSXMLParserDelegate>
-(id) initWithXmlDocumentReader:(void *) xmlDocumentReader;
@end

Просмотреть файл

@ -0,0 +1,52 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#import "NSXmlParserDelegateWrapper.h"
#import "XmlDocumentReader.hpp"
@implementation NSXmlParserDelegateWrapper{
MSIX::XmlDocumentReader* m_xmlDocumentReader;
}
- (id) initWithXmlDocumentReader:(void *)xmlDocumentReader{
self = [super init];
if (self)
{
m_xmlDocumentReader = static_cast<MSIX::XmlDocumentReader*>(xmlDocumentReader);
}
return self;
}
- (void) parserDidStartDocument:(NSXMLParser *)parser {
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
std::unique_ptr<MSIX::XmlNode> node(new MSIX::XmlNode());
node->NodeName = std::string([elementName UTF8String]);
if (qName)
{
node->QualifiedNodeName = std::string([qName UTF8String]);
}
for(id key in attributeDict)
{
node->Attributes.emplace(std::string([key UTF8String]), std::string([[attributeDict objectForKey:key] UTF8String]));
}
m_xmlDocumentReader->ProcessNodeBegin(std::move(node));
}
-(void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
m_xmlDocumentReader->ProcessCharacters([string UTF8String]);
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
m_xmlDocumentReader->ProcessNodeEnd(std::string([elementName UTF8String]));
}
- (void) parserDidEndDocument:(NSXMLParser *)parser {
}
- (void) parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
}
@end

Просмотреть файл

@ -0,0 +1,20 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#pragma once
#include <stdint.h>
#include <stddef.h>
namespace MSIX
{
class NSXmlParserWrapper
{
void* wrapped;
public:
NSXmlParserWrapper() = default;
~NSXmlParserWrapper() = default;
bool Parse(uint8_t * data, size_t length, void* xmlDocumentReader);
};
}

Просмотреть файл

@ -0,0 +1,21 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#import "NSXmlParserWrapper.h"
#import "NSXmlParserDelegateWrapper.h"
namespace MSIX
{
bool NSXmlParserWrapper::Parse(uint8_t* data, size_t dataLength, void* xmlDocumentReader)
{
NSData *xmldata = [NSData dataWithBytes:data length:dataLength];
wrapped = [[NSXMLParser alloc] initWithData:xmldata];
// Create an instance of our parser delegate and assign it to the parser
NSXmlParserDelegateWrapper *parserDelegate = [[NSXmlParserDelegateWrapper alloc] initWithXmlDocumentReader:xmlDocumentReader];
[(NSXMLParser*)wrapped setDelegate:parserDelegate];
return [(NSXMLParser*)wrapped parse];
}
}

Просмотреть файл

@ -0,0 +1,89 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#include "XmlDocumentReader.hpp"
#include "Exceptions.hpp"
namespace MSIX {
void XmlDocumentReader::Init()
{
m_wrapper = new NSXmlParserWrapper();
}
bool XmlDocumentReader::Parse(uint8_t* data, size_t size)
{
return m_wrapper->Parse(data, size, this);
}
void XmlDocumentReader::ProcessNodeBegin(std::unique_ptr<XmlNode> node)
{
if (m_currentNodeStack.empty())
{
m_currentNodeStack.push(node.get());
m_root.swap(node);
}
else
{
auto currentNode = m_currentNodeStack.top();
m_currentNodeStack.push(node.get());
currentNode->Children.emplace_back(std::move(node));
}
}
void XmlDocumentReader::ProcessNodeEnd(std::string nodeName)
{
if (!m_currentNodeStack.empty())
{
auto currentNode = m_currentNodeStack.top();
ThrowErrorIf(Error::XmlFatal, nodeName.compare(currentNode->NodeName) != 0, "Node end does not match current node opened.");
m_currentNodeStack.pop();
}
}
void XmlDocumentReader::ProcessCharacters(std::string string)
{
auto currentNode = m_currentNodeStack.top();
currentNode->Text.append(string);
}
void XmlNode::FindElementsRecursive(std::string xpath, std::list<XmlNode*>& list)
{
size_t nextPathSeparatorIndex = xpath.find_first_of("//");
std::string currentXpathSegment = xpath.substr(0, nextPathSeparatorIndex);
if (NodeName.compare(currentXpathSegment) == 0)
{
if (nextPathSeparatorIndex == std::string::npos)
{
list.emplace_back(this);
}
for(auto it = Children.begin(); it != Children.end(); ++it)
{
(*it)->FindElementsRecursive(xpath.substr(nextPathSeparatorIndex + 1), list);
}
}
}
std::list<XmlNode*> XmlNode::FindElements(std::string xpath)
{
std::list<XmlNode*> list;
if (xpath.size() >= 2 && xpath[0] == '.' && xpath[1] == '/')
{
std::string newXPath = xpath.substr(2);
for(auto it = Children.begin(); it != Children.end(); ++it)
{
auto child = (*it).get();
child->FindElementsRecursive(newXPath, list);
}
}
else if (xpath.size() > 1 && xpath[0] == '/')
{
std::string newXPath = xpath.substr(1);
FindElementsRecursive(newXPath, list);
}
return list;
}
}

Просмотреть файл

@ -0,0 +1,53 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#pragma once
#include "NSXmlParserWrapper.h"
#include <string>
#include <list>
#include <stack>
#include <map>
namespace MSIX {
class XmlAttribute
{
public:
std::string Name;
std::string Value;
};
class XmlNode
{
public:
std::map<std::string, std::string> Attributes;
std::list<std::unique_ptr<XmlNode>> Children;
std::string Text;
std::string NodeName;
std::string QualifiedNodeName;
std::list<XmlNode*> FindElements(std::string xpath);
private:
void FindElementsRecursive(std::string xpath, std::list<XmlNode*>& list);
};
class XmlDocumentReader
{
public:
void Init();
bool Parse(uint8_t* data, size_t size);
void ProcessNodeBegin(std::unique_ptr<XmlNode> node);
void ProcessNodeEnd(std::string nodeName);
void ProcessCharacters(std::string string);
XmlNode* GetRoot(){return m_root.get();};
private:
NSXmlParserWrapper* m_wrapper;
std::unique_ptr<XmlNode> m_root;
std::stack<XmlNode*> m_currentNodeStack;
};
}

Просмотреть файл

@ -0,0 +1,173 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#include <memory>
#include <string>
#include <vector>
#include <map>
#include "Exceptions.hpp"
#include "StreamBase.hpp"
#include "IXml.hpp"
#include "Encoding.hpp"
#include "StreamHelper.hpp"
#include "MSIXResource.hpp"
#include "UnicodeConversion.hpp"
#include "Enumerators.hpp"
#include "XmlDocumentReader.hpp"
EXTERN_C const IID IID_IAppleXmlElement;
// An internal interface for apple XML document object model
// {8FBC0096-E87D-406A-95D9-203ADEFBF9AF}
interface IAppleXmlElement : public IUnknown
{
public:
virtual MSIX::XmlNode* GetXmlNode() = 0;
};
SpecializeUuidOfImpl(IAppleXmlElement);
namespace MSIX {
class XmlElement final : public ComClass<XmlElement, IXmlElement, IAppleXmlElement, IMsixElement>
{
public:
XmlElement(IMsixFactory* factory, XmlNode* xmlNode) :
m_factory(factory), m_xmlNode(xmlNode)
{
}
// IXmlElement
std::string GetAttributeValue(XmlAttributeName attribute) override
{
auto intermediate = utf16_to_utf8(attributeNames[static_cast<uint8_t>(attribute)]);
return GetAttributeValue(intermediate);
}
std::vector<std::uint8_t> GetBase64DecodedAttributeValue(XmlAttributeName attribute) override
{
auto intermediate = GetAttributeValue(attribute);
return GetBase64DecodedValue(intermediate);
}
std::string GetText() override
{
return m_xmlNode->Text;
}
// IAppleXmlElement
XmlNode* GetXmlNode() override { return m_xmlNode; }
// IMsixElement
HRESULT STDMETHODCALLTYPE GetAttributeValue(LPCWSTR name, LPWSTR* value) noexcept override try
{
ThrowErrorIf(Error::InvalidParameter, (value == nullptr), "bad pointer.");
auto intermediate = utf16_to_utf8(name);
auto attributeValue = GetAttributeValue(intermediate);
return m_factory->MarshalOutString(attributeValue, value);;
} CATCH_RETURN();
HRESULT STDMETHODCALLTYPE GetText(LPWSTR* value) noexcept override try
{
ThrowErrorIf(Error::InvalidParameter, (value == nullptr), "bad pointer.");
auto text = GetText();
return m_factory->MarshalOutString(text, value);
} CATCH_RETURN();
HRESULT STDMETHODCALLTYPE GetElements(LPCWSTR name, IMsixElementEnumerator** elements) noexcept override try
{
ThrowErrorIf(Error::InvalidParameter, (elements == nullptr || *elements != nullptr), "bad pointer.");
auto intermediate = utf16_to_utf8(name);
auto elementsFound = m_xmlNode->FindElements(intermediate);
std::vector<ComPtr<IMsixElement>> elementsEnum;
for(auto element : elementsFound)
{
auto item = ComPtr<IMsixElement>::Make<XmlElement>(m_factory, element);
elementsEnum.push_back(std::move(item));
}
*elements = ComPtr<IMsixElementEnumerator>::
Make<EnumeratorCom<IMsixElementEnumerator,IMsixElement>>(elementsEnum).Detach();
return static_cast<HRESULT>(Error::OK);
} CATCH_RETURN();
private:
IMsixFactory* m_factory = nullptr;
XmlNode* m_xmlNode = nullptr;
std::string GetAttributeValue(std::string& attributeName)
{
return m_xmlNode->Attributes[attributeName];
}
};
class XmlDom final : public ComClass<XmlDom, IXmlDom>
{
public:
XmlDom(IMsixFactory* factory, const ComPtr<IStream>& stream) :
m_factory(factory), m_stream(stream)
{
auto buffer = Helper::CreateBufferFromStream(stream);
m_xmlDocumentReader.reset(new XmlDocumentReader());
m_xmlDocumentReader->Init();
ThrowErrorIfNot(MSIX::Error::XmlFatal, m_xmlDocumentReader->Parse(buffer.data(), buffer.size()), "Xml Parse failed.");
// Apple currently only supports SAX parser.
// If schema validation is required, then use xerces as the xml parser.
}
// IXmlDom
MSIX::ComPtr<IXmlElement> GetDocument() override
{
return ComPtr<IXmlElement>::Make<XmlElement>(m_factory, m_xmlDocumentReader->GetRoot());
}
bool ForEachElementIn(const ComPtr<IXmlElement>& root, XmlQueryName query, XmlVisitor& visitor) override
{
ComPtr<IAppleXmlElement> element = root.As<IAppleXmlElement>();
XmlNode* xmlNode = element->GetXmlNode();
auto elements = xmlNode->FindElements(xPaths[static_cast<uint8_t>(query)]);
for(auto element : elements)
{
auto item = ComPtr<IXmlElement>::Make<XmlElement>(m_factory, element);
if (!visitor.Callback(visitor.context, item))
{
return false;
}
}
return true;
}
protected:
IMsixFactory* m_factory;
ComPtr<IStream> m_stream;
std::unique_ptr<XmlDocumentReader> m_xmlDocumentReader;
};
class AppleXmlFactory final : public ComClass<AppleXmlFactory, IXmlFactory>
{
public:
AppleXmlFactory(IMsixFactory* factory) : m_factory(factory)
{
}
ComPtr<IXmlDom> CreateDomFromStream(XmlContentType footPrintType, const ComPtr<IStream>& stream) override
{
return ComPtr<IXmlDom>::Make<XmlDom>(m_factory, stream);
}
protected:
IMsixFactory* m_factory;
};
ComPtr<IXmlFactory> CreateXmlFactory(IMsixFactory* factory) { return ComPtr<IXmlFactory>::Make<AppleXmlFactory>(factory); }
} // namespace MSIX

Просмотреть файл

@ -363,8 +363,7 @@ public:
bool ForEachElementIn(const ComPtr<IXmlElement>& root, XmlQueryName query, XmlVisitor& visitor) override
{
ComPtr<IXercesElement> element;
ThrowHrIfFailed(root->QueryInterface(UuidOfImpl<IXercesElement>::iid, reinterpret_cast<void**>(&element)));
ComPtr<IXercesElement> element = root.As<IXercesElement>();
XercesXMLChPtr xPath(XMLString::transcode(xPaths[static_cast<uint8_t>(query)]));
XercesPtr<DOMXPathResult> result(m_parser->getDocument()->evaluate(

Просмотреть файл

@ -7,14 +7,6 @@ project(apitest)
# Define two variables in order not to repeat ourselves.
set(BINARY_NAME apitest)
IF (XML_PARSER MATCHES xerces)
add_definitions(-DUSING_XERCES=1)
ENDIF()
if(XML_PARSER MATCHES javaxml)
add_definitions(-DUSING_JAVAXML=1)
endif()
IF (XML_PARSER MATCHES msxml6)
add_definitions(-DUSING_MSXML=1)
ENDIF()

Просмотреть файл

@ -12,10 +12,10 @@ static const char* packageToTest = "..\\test\\appx\\TestAppxPackage_Win32.appx";
static const char* packageToTest = "../test/appx/TestAppxPackage_Win32.appx";
#endif
#if defined(USING_XERCES) || defined (USING_JAVAXML)
static const wchar_t* ApplicationXpath = L"/Package/Applications/Application";
#else
#if defined(USING_MSXML)
static const wchar_t* ApplicationXpath = L"/*[local-name()='Package']/*[local-name()='Applications']/*[local-name()='Application']";
#else
static const wchar_t* ApplicationXpath = L"/Package/Applications/Application";
#endif

Просмотреть файл

@ -84,10 +84,6 @@ static HRESULT RunTest(std::string packageName, std::string unpackFolder, MSIX_V
if(expectedResult == result)
{ std::cout << "Succeeded" << std::endl;
}
else if ((expectedResult == 4099) && (result == 4098))
{
std::cout << "Succeeded for AOSP JavaXml since it always returns 4098 for xml errors" << std::endl;
}
else
{ std::cout << "Failed" << std::endl;
g_TestFailed = true;