본문 바로가기

Application Programming Interface/Windows API

Visual C++로 MSXML 사용하기 #2

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

 아래의 간단한 XML 코드를 MSXML을 통해 생성하고 파일로 저장해보겠습니다.

<?xml version="1.0"?>
<document>
</document>

 먼저 XML 문서를 다루기 위한 XMLDOMDocument 인스턴스를 생성합니다. COM 클래스는 이름 끝에 Ptr이라 붙는 자료형을 제공하는데 이는 일종의 스마트포인터(smart pointer)로서 자신이 선언된 스코프가 끝날 때 메모리를 자동으로 해제하도록 만들어진 것입니다. 아래와 같이 IXMLDOMDocumentPtr형의 변수를 선언하고 CreateInstance 메서드를 호출합니다. 매개변수로는 XMLDocument의 CLSID를 전달합니다. HRESULT를 반환하며 SUCCEEDED 매크로를 통해 성공 또는 실패를 판별할 수 있습니다.

/* ex03.cpp */
#include <windows.h>
#import <msxml4.dll>

int main(int argc, char * argv[])
{
	if (SUCCEEDED(::CoInitialize(NULL)))
	{
		IXMLDOMDocumentPtr xmlDocument;
		if(!SUCCEEDED(xmlDocument.CreateInstance(CLSID_XMLDocument))) return -1;

		::CoUninitialize();
	}
	rerurn 0;
}

 인스턴스 생성에 성공하였다면 xmlDocument 인스턴스를 통해 XML 구성요소를 생성하고 추가해나가면 됩니다. document라고 이름 붙인 루트 엘리먼트를 추가하기에 앞서 맨 처음 등장하는 <?xml version="1.0"?> 이라는 부분을 추가합니다. 이는 IXMLDOMProcessingInstruction 클래스로 제공됩니다.

/* ex04.cpp */
#include <windows.h>
#import <msxml4.dll>

int main(int argc, char * argv[])
{
	if (SUCCEEDED(::CoInitialize(NULL)))
	{
		{
			BSTR bstr1, bstr2, bstr3;
			IXMLDOMDocumentPtr xmlDocument;
			IXMLDOMProcessingInstructionPtr xmlProcessingInstruction;

			if (SUCCEEDED(xmlDocument.CreateInstance(CLSID_XMLDocument))) return -1;

			/* <?xml version="1.0"?> 부분을 만들기 위한 소스 */
			/* version 항목에는 1.0의 값을 부여 */
			bstr1 = ::SysAllocString(TEXT("xml"));
			bstr2 = ::SysAllocString(TEXT("version=\"1.0\""));
			/* 지정한 항목과 값으로 ProcessingInstruction을 생성 마지막 매개변수를 통해 생성된 인스턴스가 반환됨. */
			xmlDocument->createProcessingInstruction(bstr1, bstr2, &xmlProcessingInstruction);
			/* 할당한 문자열 메모리는 사용이 끝났으므로 해제 */
			::SysFreeString(bstr2);
			::SysFreeString(bstr1);

			/* XMLDocument에 추가 */
			xmlDocument->appendChild(xmlProcessingInstruction, NULL);
			
			/* 현재까지 만들어진 문서를 문자열로 얻기 */
			xmlDocument->get_xml(&bstr3);
			/* 문서의 내용을 출력 */
			::wprintf(L"%s", (const wchar_t *)bstr3);
		}
		::CoUninitialize();
	}
	rerurn 0;
}

 COM은 문자열을 전달할 때 BSTR이라는 방식으로 전달합니다. 문자열에는 크게 C-Style 문자열과 BSTR(Basic String)이 있는데, C-Style은 문자열 끝에 NULL이 삽입되는 문자열이고 BSTR은 NULL을 사용하지 않는 대신 문자열의 길이를 명시하는 방식을 사용합니다. C-Style 문자열에서 BSTR로 변환하기 위해 SysAllocString이 사용된 것입니다.

~Ptr로 끝나는 클래스들은 스코프를 벗어날 때 소멸자가 실행되면서 메모리가 해제된다고 하였습니다. 그런데 스코프가 끝나기 전에 ::CoUninitialize() 함수가 실행되어 COM 관련 메모리가 이미 해제되어버렸기 때문에 ~Ptr의 소멸자에서 해제할 메모리가 없어지므로, 프로그램이 종료될 때 100% 이 부분에서 오류가 날 것입니다. 이를 방지하려면 위와 같이 ~Ptr클래스의 인스턴스를 선언하고 사용하는 부분을 스코프로 한번 더 묶어주면 오류가 없습니다.

 이제 document라고 이름붙인 루트 엘리먼트를 삽입하겠습니다.

/* ex05.cpp */
#include <windows.h>
#include <stdio.h>
#include <locale.h>

int main()
{
	::setlocale(LC_ALL, "");

	if (SUCCEEDED(::CoInitialize(NULL)))
	{
		{
			BSTR bstr1, bstr2, bstr3;
			IXMLDOMDocumentPtr xmlDocument;
			IXMLDOMProcessingInstructionPtr xmlProcessingInstruction;
			IXMLDOMElementPtr xmlElement;

			if (!SUCCEEDED(xmlDocument.CreateInstance(__uuidof(DOMDocument))))
			{
				::wprintf(L"xmlDocument가 생성되지 않았습니다.\n");
				::CoUninitialize();
				return -1;
			}

			bstr1 = ::SysAllocString(L"xml");
			bstr2 = ::SysAllocString(L"version=\"1.0\"");
			xmlDocument->createProcessingInstruction(bstr1, bstr2, &xmlProcessingInstruction);
			::SysFreeString(bstr2);
			::SysFreeString(bstr1);

			xmlDocument->appendChild(xmlProcessingInstruction, NULL);

			/* <document> </document> 부분 추가 */
			bstr1 = ::SysAllocString(L"document");
			xmlDocument->createElement(bstr1, &xmlElement);
			::SysFreeString(bstr1);

			xmlDocument->appendChild(xmlElement, NULL);

			xmlDocument-gt;get_xml(&bstr3);
			::wprintf(L"%s", (const wchar_t *)bstr3);
		}
		::CoUninitialize();
	}
	return 0;
}

 createElement를 통해 태그의 이름이 document인 엘리먼트를 만들었고 이를 xmlDocument에 직접 추가하여 루트 엘리먼트로 지정했습니다. get_xml로 결과를 확인해 보겠습니다. document 태그 내부의 내용이 비어있으므로 <document></document> 대신 <document />와 같이 축약되어 나타납니다.