본문 바로가기

Application Programming Interface/Windows API

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

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

BSTR 자료형의 근본은 wchar_t * 형입니다. 인텔리센스로 확인해 보면

typedef OLECHAR * BSTR;

이고 OLECHAR은 다시

typedef wchar_t OLECHAR;

이므로 겉모양으로는 Wide Char 문자열과 똑같다는 뜻입니다. 다만 저장되는 문자열의 구조가 통상적인 C-Style이 아닌 맨 처음에 문자열의 길이가 명시되는 BSTR이라는 차이가 있는 것이지요

BSTR을 사용하기 위해서 이전 포스팅에서는 SysAllocString, SysFreeString의 함수를 사용하여 C-Style의 문자열을 변환하였지만 문자열을 사용하는 매 순간마다 이런 함수들을 호출해 일일이 변환하는 것은 번거롭기 그지없습니다. 그래서 BSTR의 사용과 변환에 관련된 기능들을 묶어놓은 클래스가 _bstr_t입니다.

_bstr_t도 스마트 포인터처럼 자신이 선언된 스코프를 벗어날 때 자동으로 메모리를 해제하므로 별도로 해제 과정이 필요하지 않고 ANSI문자열과 Unicode문자열과도 변환이 간편하기 때문에 앞으로는 BSTR대신 _bstr_t를 사용하겠습니다.

이전 포스팅 ex05.cpp를 _bstr_t로 다시 작성하면

/* ex06.cpp */
#include <windows.h>
#include <stdio.h>
#include <locale.h>
#import <msxml4.dll>

int main(int argc, char * argv[])
{
	::setlocale(LC_ALL, "");

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

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

			bstr1 = TEXT("xml");
			bstr2 = TEXT("version=\"1.0\"");
			xmlDocument->createProcessingInstruction(bstr1, bstr2, &xmlProcessingInstruction);

			xmlDocument->appendChild(xmlProcessingInstruction, NULL);

			bstr1 = TEXT("document");
			xmlDocument->createElement(bstr1, &xmlElement);

			xmlDocument->appendChild(xmlElement, NULL);

			/* BSTR의 주소는 GetAddress() 메서드로 얻을 수 있습니다. */
			xmlDocument->get_xml(bstr3.GetAddress());
			::wprintf(L"%s", (const wchar_t *)bstr3);
		}
		::CoUninitialize();
	}
	return 0;
}

이 소스 중 문자열을 bstr로 변환하는 부분이

BSTR bstr;
bstr = ::SysAllocString(L"document");
xmlDocument->createElement(bstr, &xmlElement);
::SysFreeString(bstr);

이랬던 것이

_bstr_t bstr;
bstr = TEXT("document");
xmlDocument->createElement(bstr, &xmlElement);

이와 같이 실행 결과는 전과 같지만, 별도의 할당/해제 구문이 없이 문자열을 대입만 하면 알아서 내부적으로 변환이 이루어지니 소스가 직관적이고 메모리 누수의 우려도 적어집니다.

계속해서 엘리먼트에 어트리뷰트를 추가해 보겠습니다. 어트리뷰트(attribute)는 태그에 붙는 일종의 속성입니다. document 엘리먼트에 name이라는 어트리뷰트를 추가하고 여기에 "tapito.tistory.com"을 대입해보겠습니다. 즉 아래와 같은 형태의 xml을 만들 것입니다.

<?xml version="1.0"?>
<document name="tapito.tistory.com">
</document>

위 xml을 만들기 위한 소스 코드는

/* ex07.cpp */
#include <windows.h>
#include <stdio.h>
#include <locale.h>
#import <msxml4.dll>

int main(int argc, char * argv[])
{
	::setlocale(LC_ALL, "");

	if (SUCCEEDED(::CoInitialize(NULL)))
	{
		{
			_bstr_t bstr1, bstr2, bstr3;
			VARIANT variant; // 어트리뷰트의 값을 전달할 때는 VARIANT 형을 사용합니다.
			IXMLDOMDocumentPtr xmlDocument;
			IXMLDOMProcessingInstructionPtr xmlProcessingInstruction;
			IXMLDOMElementPtr xmlElement;
			IXMLDOMAttributePtr xmlAttribute;

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

			bstr1 = TEXT("xml");
			bstr2 = TEXT("version=\"1.0\"");
			xmlDocument->createProcessingInstruction(bstr1, bstr2, &xmlProcessingInstruction);

			xmlDocument->appendChild(xmlProcessingInstruction, NULL);

			bstr1 = TEXT("document");
			xmlDocument->createElement(bstr1, &xmlElement);

			/* name="tapito.tistory.com" 부분을 만들기 위한 과정입니다. */
			bstr1 = TEXT("name"); // 어트리뷰트의 이름인 "name"이라는 문자열을 BSTR로 변환합니다.
			bstr2 = TEXT("tapito.tistory.com"); // 어트리뷰트의 값인 "tapito.tistory.com"이라는 문자열을 BSTR로 변환합니다.
			/* 어트리뷰트의 값을 VARIANT 구조체에 싣기 위한 과정입니다. */
			V_VT(&variant) = VT_BSTR; // VARIANT는 문자열 데이터를 가지고 있음을 지정합니다.
			V_BSTR(&variant) = bstr2; // VARIANT 구조체 내부에서 문자열을 기억할 수 있는 공간에 bstr2를 대입합니다.
			xmlDocument->createAttribute(bstr1, &xmlAttribute); // "name"이라는 이름의 어트리뷰트를 만들고
			xmlAttribute->put_value(variant); // 이 어트리뷰트에 VARIANT로 포장된 BSTR을 값으로 지정합니다.
			xmlElement->setAttributeNode(xmlAttribute, NULL); // 만들어진 어트리뷰트를 루트 엘리먼트에 추가합니다.

			xmlDocument->appendChild(xmlElement, NULL);

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

실행 결과는 이렇습니다.

VARIANT 구조체가 새로 등장하였는데, 이는 문자열, 문자, 정수, 실수 등 모든 자료형이 기억될 수 있는 자료형입니다. C언어에서는 변수의 자료형이 처음부터 명시가 되지만, Basic 언어는 변수가 정수를 기억했다가 어느 시점에는 문자열이나 다른 형식의 데이터를 기억하는 경우도 있는데 이 때 사용되는 자료형이 VARIANT입니다. 위 예제에서는 BSTR 문자열을 VARIANT 구조체에 실어 보내는 것을 시험해 보았는데, 이번에는 정수와 실수도 xml 어트리뷰트로 지정해 봅시다. 만들고자하는 xml은 아래와 같습니다.

<?xml version="1.0"?>
<document name="tapito.tistory.com" number="1000" average="3.14159">
</document>

이를 만드는 소스는

/* ex07.cpp */
#include "stdafx.h"

int main(int argc, char * argv[])
{
	::setlocale(LC_ALL, "");

	if (SUCCEEDED(::CoInitialize(NULL)))
	{
		{
			_bstr_t bstr1, bstr2, bstr3;
			VARIANT variant;
			IXMLDOMDocumentPtr xmlDocument;
			IXMLDOMProcessingInstructionPtr xmlProcessingInstruction;
			IXMLDOMElementPtr xmlElement;
			IXMLDOMAttributePtr xmlAttribute;

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

			bstr1 = TEXT("xml");
			bstr2 = TEXT("version=\"1.0\"");
			xmlDocument->createProcessingInstruction(bstr1, bstr2, &xmlProcessingInstruction);

			xmlDocument->appendChild(xmlProcessingInstruction, NULL);

			bstr1 = TEXT("document");
			xmlDocument->createElement(bstr1, &xmlElement);

			bstr1 = TEXT("name");
			bstr2 = TEXT("tapito.tistory.com");
			V_VT(&variant) = VT_BSTR;
			V_BSTR(&variant) = bstr2;
			xmlDocument->createAttribute(bstr1, &xmlAttribute);
			xmlAttribute->put_value(variant);
			xmlElement->setAttributeNode(xmlAttribute, NULL);

			/* number="1000" 부분을 만들기 위한 과정 */
			bstr1 = TEXT("number"); // 어트리뷰트의 이름은 "number"
			/* VARIANT 구조체를 통해 이 어트리뷰트의 값으로 정수 값인 1000이 실려 보내지도록 VARIANT를 설정합니다. */
			V_VT(&variant) = VT_INT; // 시스템 기본 정수
			V_INT(&variant) = 1000; // 값은 1000
			xmlDocument->createAttribute(bstr1, &xmlAttribute);
			xmlAttribute->put_value(variant);
			xmlElement->setAttributeNode(xmlAttribute, NULL);

			/* average="3.14159" 부분을 만들기 위한 과정 */
			bstr1 = TEXT("average"); // 어트리뷰트의 이름은 "average"
			/* VARIANT 구조체를 통해 이 어트리뷰트의 값으로 단정밀도 부동소수인 3.14159가 실려 보내지도록 VARIANT를 설정합니다. */
			V_VT(&variant) = VT_R4; // 단정밀도 (float형)
			V_R4(&variant) = 3.14159f; // 값은 3.14159
			xmlDocument->createAttribute(bstr1, &xmlAttribute);
			xmlAttribute->put_value(variant);
			xmlElement->setAttributeNode(xmlAttribute, NULL);

			xmlDocument->appendChild(xmlElement, NULL);

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

실행 결과는

VARIANT 구조체는 이처럼 실어보낼 데이터의 종류를 V_VT 매크로를 사용해 지정하고 실제 데이터 또는 그 데이터의 위치를 V_자료형 매크로를 통해 지정하면 됩니다. V_VT 매크로를 통해 지정할 수 있는 데이터의 종류는 다음과 같습니다.

정수 계열 자료형

VT_I1 = 부호 있는 8비트 정수(signed char), VT_UI1 = 부호 없는 8비트 정수 즉 바이트(unsigned char), VT_I2 = 부호 있는 16비트 정수(signed short), VT_UI2 = 부호 없는 16비트 정수(unsigned short), VT_I4 = 부호 있는 32비트 정수(signed long), VT_UI4 = 부호 없는 32비트 정수(unsigned long), VT_I8 = 부호 있는 64비트 정수(signed long long), VT_UI8 = 부호 없는 64비트 정수(unsigned long long)

VT_INT = 부호 있는 시스템 정수(signed int), VT_UINT = 부호 없는 시스템 정수(unsigned int)

실수 계열 자료형

VT_R4 = 단정밀도 부동 소수점(float), VT_R8 = 배정밀도 부동 소수점(double)

논리값 자료형

VT_BOOL = 논리값(bool)

문자열 자료형

VT_BSTR = BSTR 문자열

VT_LPSTR = C-Style의 ANSI 문자열(char *)

VT_LPWSTR = C-Style의 Wide 문자열(wchar_t *)

날짜 및 시간

VT_DATE = 날짜, VT_FILETIME = 파일이 작성된 날짜/시간

그 밖의 자료형

VT_UNKNOWN = 기타 IUnknown을 상속하는 클래스의 포인터형(IUnknown *)

VT_ERROR = COM 메서드의 실행 결과 SCODE. 즉 HRESULT형. 자세한 구조는 MSDN (http://msdn.microsoft.com/en-us/library/bb415337.aspx)참조

VT_CY = 모르겠음

VT_CLSID = COM 클래스의 식별 번호

VT_CF = 클립보드가 기억하는 내용

VT_BSTR_BLOB = 운영체제가 BSTR을 지원하지 않을 때 사용되는 형식이라는데 모름

VT_BLOB, VT_BLOBOBJECT = 모름

VT_DISPATCH = VB에서 사용되는 자동화 클래스 어쩌고 함

VT_STREAM, VT_STREAMED_OBJECT = 데이터를 읽고 쓸 수 있는 스트림(IStream *, C 라이브러리의 FILE * 정도에 해당)

VT_STORAGE, VT_STORED_OBJECT = 저장장치에 대해 직접 읽고 쓸 수 있는 클래스

VT_VERSIONED_STREAM = 모름

배열형

VT_ARRAY | VT_*** = 두 속성이 논리합 되어 배열인데 각 원소의 자료형이 뭐다라고 알려줌

VT_VECTOR | VT_*** = VT_ARRAY와 비슷한데 원소를 추가 또는 제거함으로써 배열의 크기가 가변적임을 알려줌

VT_BYREF | VT_*** = 변수가 가진 값을 그대로 전송하는 것이 아니라 변수의 위치를 전송함. C++언어의 int & 형 같은것임

VARIANT를 사용하는 방법은 데이터를 실어 보낼 때와 끄집어 낼 때로 나눌 수 있습니다.

데이터를 실어 보낼때는

VARIANT variant;
<typename> tmp = <value>;
V_VT(&variant) = VT_<typename>;
V_<typename>(&variant) = tmp;

데이터를 끄집어 낼 때는

VARIANT variant;
<typename> tmp = <default value>;
if(V_VT(&variant) = VT_<typename>) tmp = V_<typename>(&variant);