본문 바로가기

Programming Language/C&C++

SAFEARRAY의 간단한 사용법 정리

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

SafeArray 객체 생성 및 파괴

SafeArray를 생성하고 파괴하는 과정을 알아보기 위해 다음 코드를 작성해 본다. 배열을 구성하는 각 원소의 자료형이 부호 없는 8비트 정수(바이트 형)이고 10개로 구성된 배열의 생성법은 다음과 같다.

 /* Example: SafeArrayCreate, SafeArrayDestroy */
#include 
#include 

#include 
#include 

int main(int argc, char * argv[])
{
	HLOCAL hLocal = NULL;
	LPSAFEARRAYBOUND lpSafeArrayBound = NULL;
	LPSAFEARRAY lpSafeArray = NULL;

	hLocal = LocalAlloc(LHND, sizeof(SAFEARRAYBOUND));
	
	/* SafeArray 생성을 위한 개수 및 시작 인덱스 지정 */
	lpSafeArrayBound = (LPSAFEARRAYBOUND)LocalLock(hLocal);
	lpSafeArrayBound->lLbound = 0; // 첫 번째 항목의 인덱스는 0번부터 시작함.
	lpSafeArrayBound->cElements = 10; // 배열 항목의 총 개수는 10개임.

	/* 배열 생성하기 */
	assert((lpSafeArray = SafeArrayCreate(VI_UI8, 1, lpSafeArrayBound)) != NULL);

	/* TODO: Something 0 */

	/* 배열 파괴하기 */
	assert(SUCCEEDED(SafeArrayDestroy(lpSafeArray)));

	printf("END");

	return 0;
}

 

배열을 생성하기 위해 호출하는 함수는 SafeArrayCreate이고 파괴하는 함수는 SafeArrayDestroy이다. 이 두 함수에서 사용되는 자료형은 SAFEARRAYBOUND, SAFEARRAY이다.

 

SAFEARRAYBOUND 구조체

typedef struct tagSAFEARRAYBOUND {
    ULONG cElements;
    LONG lLbound;
} SAFEARRAYBOUND, *LPSAFEARRAYBOUND;

 

1차원 배열에 대해 그 크기와 시작 인덱스를 명시하는 구조체이다.

cElements
1차원 배열에 대해 원소의 총 개수를 명시한다.
lLBound
첫 번째 원소의 인덱스를 지정한다. C 계열의 언어는 첫 번째 원소의 인덱스가 항상 0번이지만, Basic 계열의 언어는 첫 번째 원소의 인덱스가 기본적으로 1번이고, Pascal 계얼의 언어는 0, 1번 이외의 다른 번호로 직접 지정할 수 있다. COM(Component Object Model)은 여러 언어의 프로젝트에서 코드를 재사용할 수 있으므로 이를 고려하여 지정하는 속성이다.

 

SAFEARRAY 구조체

typedef struct tagSAFEARRAY {
    USHORT cDims;
    USHORT fFeatures;
    ULONG cbElements;
    ULONG cLocks;
    PVOID pvData;
    SAFEARRAYBOUND rgsabound[1];
    } SAFEARRAY, *LPSAFEARRAY;

현재 메모리의 어딘가에 할당되어 있는 SAFEARRAY 객체에 대한 정보를 보관하는 구조체이다.

cDims
이 배열의 차원을 지정한다. 1차원 배열이면 1, 2차원 배열이면 2의 순서이다.
fFeatures
이 배열이 갖는 특성을 나타내는 비트 플래그 집합이다. 위의 MSDN에서 지정 가능한 플래그 상수와 그 의미를 확인할 수 있다.
cbElements
이 배열을 구성하는 원소 1개의 크기를 지정한다.
cLocks
일종의 레퍼런스 카운트와 비슷하다. Lock 계열의 함수에 의해 잠겨진 회수를 보관한다. 멀티 스레드로 운영되는 프로그램에서 스레드 안정성을 위해 내부적으로 사용된다.
pvData
배열의 실제 원소들이 있는 메모리의 번지를 가리킨다.
rgsabound
이 배열을 구성하는 각 차원에 대해 지정된 위의 SAFEARRAYBOUND 속성이 시작되는 부분이다. 즉, 각 차원별로 배열의 크기와 첫 번째 원소의 인덱스 번호가 기록되어 있다.

 

SafeArrayCreate 함수

SAFEARRAY* SafeArrayCreate(
    _In_ VARTYPE vt,
    _In_ UINT cDims,
    _In_ SAFEARRAYBOUND * rgsabound
);

SafeArray 객체를 생성하는 함수이다.

vt
배열을 구성하는 원소들의 자료형이다. VARTYPE 자료형이 가질 수 있는 옵션 중 하나를 선택할 수 있다. 단, VT_ARRAY, VT_BYREF, VT_EMPTY, VT_NULL 옵션을 지정하는 것은 유효하지 않다.
cDims
본 배열의 차원을 지정한다.
rgsabound
각 차원별로 지정할 bound 속성이다.

 vt 매개변수로 올 수 있는 값은 다음과 같다.

속성
VT_VOIDC-style의 void형을 나타내는 자료형이다.
VT_BOOLBoolean 값이다.
VT_INT시스템 정의 크기의 부호 있는 정수이다.
VT_I11바이트 부호 있는 정수이다.
VT_I22바이트 부호 있는 정수이다.
VT_I44바이트 부호 있는 정수이다.
VT_I88바이트 부호 있는 정수이다.
VT_UINT시스템 정의 크기의 부호 없는 정수이다.
VT_UI11바이트 부호 없는 정수이다.
VT_UI22바이트 부호 없는 정수이다.
VT_UI44바이트 부호 없는 정수이다.
VT_UI88바이트 부호 없는 정수이다.
VT_PTR포인터형이다.
VT_INT_PTR레지스터가 지원 가능한 크기의 부호 있는 포인터형이다.
VT_UINT_PTR레지스터가 지원 가능한 크기의 부호 없는 포인터형이다.
VT_R4단정밀도 부동 소수점의 실수이다.
VT_R8배정밀도 부동 소수점의 실수이다.
VT_DECIMAL16바이트 고정 소수점의 실수이다.
VT_CY통화형 실수이다.
VT_DATE날짜 및 시각을 나타내는 자료형이다.
VT_BSTRBasic/Pascal 형식의 문자열이다.
VT_LPSTR멀티바이트 또는 싱글바이트의 문자열이다.
VT_LPWSTR와이드 문자열이다.
VT_CARRAYC-style의 배열이다.

 

그 외, VT_DISPATCH, VT_ERROR, VT_VARIANT, VT_UNKNOWN, VT_HRESULT, VT_SAFEARRAY, VT_USERDEFINED, VT_RECORD, VT_FILETIME, VT_BLOB, VT_STREAM, VT_STORAGE, VT_STREAMED_OBJECT, VT_STORED_OBJECT, VT_BLOB_OBJECT, VT_CF, VT_CLSID, VT_VERSIONED_STREAM, VT_BSTR_BLOB, VT_VECTOR 등을 지정할 수 있다.

객체 생성에 성공하면 그 객체의 주소를 반환하고, 그렇지 않을 경우 NULL을 반환한다.

 

SafeArrayDestroy 함수

HRESULT SafeArrayDestroy(
    _In_ SAFEARRAY *psa
);

할당받은 SafeArray 객체를 파괴한다.

파괴에 성공하면 S_OK를 반환하고 그렇지 않을 경우 E_INVALIDARG 또는 DISP_E_ARRAYISLOCKED 등을 반환한다.

 

원소의 자료형 구하기

SafeArrayGetVartype 함수를 사용하여 배열을 구성하는 원소들의 자료형을 가져올 수 있다.

HRESULT SafeArrayGetVartype(
    _In_  SAFEARRAY * psa,
    _Out_ VARTYPE * pvt
);

 

psa
자료형을 알아 볼 SafeArray 객체이다.
pvt
자료형이 반환될 변수이다.

 

반환 값 얻기에 성공하면 S_OK를 반환하고 그 외 문제가 있으면 E_INVALIDARG을 반환한다.

 

원소의 개수 구하기

앞서 설명한대로 SafeArray에서는 인덱스가 0 또는 1부터 시작한다는 것이 보장되지 않는다. 특정 차원의 원소의 개수를 구하기 위해서는 그 차원에서 (맨 마지막 원소의 인덱스) - (맨 처음 원소의 인덱스) + 1를 계산하는 방법을 사용한다. 이를 위해 SafeArrayGetLBound, SafeArrayGetUBound 함수가 사용된다.

위 예제 코드의 /* TODO: Something */영역에 다음 코드를 넣어 실행해본다.

// LONG lLBound = 0, lUBound = 0, lCount = 0;
assert(SUCCEEDED(SafeArrayGetLBound(lpSafeArray, 1, &lLBound)));
assert(SUCCEEDED(SafeArrayGetUBound(lpSafeArray, 1, &lUBound)));
assert((lCount = (lUBound - lLBound + 1)) >= 0);

 

SafeArrayGetLBound / SafeArrayGetUBound 함수

HRESULT SafeArrayGetLBound(
    _In_ SAFEARRAY * psa,
    _In_ UINT nDim,
    _Out_ LONG * plLbound
);

HRESULT SafeArrayGetUBound(
    _In_ SAFEARRAY * psa,
    _In_ UINT nDim,
    _Out_ LONG * plUbound
);

 SafeArray의 특정 차원에서 각각 시작 원소의 인덱스와 끝 원소의 인덱스를 가져온다.

psa
인덱스를 구할 SafeArray의 포인터이다.
nDim
인덱스를 구할 특정 차원이다.
plLbound / plUbound
지정한 차원에서 시작 인덱스 또는 끝 인덱스를 반환받을 변수이다.

 

반환 값 얻기에 성공하면 S_OK를 반환하고 nDim의 값 또는 다른 인덱스 등이 잘못되었으면 DISP_E_BADINDEX을 반환한다. 만일 인덱스로 쓰이는 숫자가 너무 커서 연산 시 CPU에 오버플로우가 발생한다면 DISP_E_OVERFLOW를 반환하고 그 외 매개변수에 문제가 있으면 E_INVALIDARG을 반환한다.

 

원소에 값 설정하고 가져오기

원소에 값 설정하기

// LONG lIndex = 0; unsigned char * lpByte = NULL;
assert(SUCCEEDED(SafeArrayLock(lpSafeArray)));
assert(SUCCEEDED(SafeArrayAccessData(lpSafeArray, &lpByte)));
for (lIndex = 0; lIndex < lCount; lIndex++) {
    /* TODO: Something */
    lpByte[lIndex] = 100 + (lIndex - 1);
}
lpByte = NULL;
assert(SUCCEEDED(SafeArrayUnaccessData(lpSafeArray)));
assert(SUCCEEDED(SafeArrayUnlock(lpSafeArray)));

SafeArray의 특정 원소를 가져오고 설정하기 위해 SafeArrayLock, SafeArrayUnlock 함수를 사용해 이 스레드에서 독점할 수 있도로 객체를 잠그고, 잠겨있는 동안 SafeArrayAccessData, SafeArrayUnaccessData 함수를 사용해 배열의 포인터를 얻어서 값을 가져오거나 설정한다.

원소의 값 가져오기

// LONG lIndex = 0; unsigned char * lpByte = NULL;
assert(SUCCEEDED(SafeArrayLock(lpSafeArray)));
assert(SUCCEEDED(SafeArrayAccessData(lpSafeArray, &lpByte)));
for (lIndex = 0; lIndex < lCount; lIndex++) {
    /* TODO: Something */
    printf("item[%ld] = %d\n", lIndex, lpByte[lIndex]);
}
lpByte = NULL;
assert(SUCCEEDED(SafeArrayUnaccessData(lpSafeArray)));
assert(SUCCEEDED(SafeArrayUnlock(lpSafeArray)));

SafeArrayLock / SafeArrayUnlock 함수

HRESULT SafeArrayLock(
    _In_ SAFEARRAY * psa
);

HRESULT SafeArrayUnlock(
    _In_ SAFEARRAY * psa
);

SafeArray 객체의 cLocks 필드를 증가하거나 감소시킨다.

psa
잠금 회수를 증가하거나 감소시킬 SafeArray 객체이다.

성공하면 S_OK, psa 매개변수가 잘못되었으면 E_INVALIDARG, 그 외 오류는 E_UNEXPECTED를 반환한다.

 

SafeArrayAccessData / SafeArrayUnaccessData 함수

HRESULT SafeArrayAccessData(
    _In_ SAFEARRAY * psa,
    _Out_ void ** ppvData
);

HRESULT SafeArrayUnaccessData(
    _In_ SAFEARRAY * psa
);

SafeArray로부터 포인터 배열을 얻거나 해제한다.

psa
포인터 배열을 얻거나 해제할 SafeArray 객체이다.
ppvData
포인터 배열이 반환될 변수이다.

성공하면 S_OK, psa 매개변수가 잘못되었으면 E_INVALIDARG, 그 외 오류는 E_UNEXPECTED를 반환한다.

 

참고 URL: [MSDN] C++ - Introducing the SAFEARRAY Data Structure https://msdn.microsoft.com/en-us/magazine/mt778923.aspx