III 비동기 및 원격 질의 들
비동기 질의 (AsyncPing 프로젝트)
지금까지 WMI 질의를 동기 방식으로 수행애 보았습니다. 이 방식은 WMI 소비자의 스레드가 질의를 시작하고 난 다음 결과가 다시 전파될 때까지 기다리는 것입니다. 특히 대기하는 동안 스레드는 실행을 중지하고 질의가 완료 된 후에야 다시 시작합니다.
이것은 분명히 잘 동작할 것이며 결과를 만들어 내지만, 방식에 뚜렷한 단점이 있습니다. 즉, WMI 질의를 완료하는 데 약간의 시간이 걸릴 수 있다는 것입니다. 질의 스레드가 다른 작업을 계산하여 소비할 수 있는 시간으로 리소스 활용도를 높일 수 있을 것입니다.
WMI는 비동기 질의를 제공합니다. 비동기 쿼리를 만들 때 쿼리 스레드는 쿼리가 완료될 때까지 기다리지 않습니다. 대신 싱크라고 하는 특수 COM 인터페이스를 구현하는 클래스 인스턴스를 제공합니다.
sink는 WMI 쿼리 결과를 처리하기 위해 소비자를 대신하여 WMI 공급자가 호출하는 특정 메서드를 구현합니다. 이 말은, WMI 공급자와 소비자 모두 비동기 쿼리를 만들 때 Out-of-Process 실행을 수행합니다.
소비자의 경우 결과 계산은 프로세스 외부에서 수행됩니다(동기 쿼리와 마찬가지로).
공급자의 경우 더 이상 소비자가 아니라 처리를 트리거(시작)하는 공급자이기 때문에 결과 처리는 프로세스 외부에서 수행됩니다.
이 상황은 다음 하위 섹션에서 논의될 보안과 관련하여 몇 가지 유의해야 점들을 가지고 있습니다.
비동기 쿼리를 만들 때 결과 IWbemClassObject 인스턴스는 호출에서 즉시 반환되지 않습니다.
대신 IWbemObjectSink 클래스의 인스턴스가 IWbemServices::ExecQueryAsync로 전달됩니다.
다음은 IWbemServices::ExecQueryAsync의 프로토타입입니다:
HRESULT ExecQueryAsync( const BSTR strQueryLanguage,
const BSTR strQuery, long lFlags,
IWbemContext* pCtx,
IWbemObjectSink* pResponseHandler )
lFlags 매개변수는 질의 실행 방법에 대한 특정 세부 정보를 제어하는 데 사용됩니다.
예를 들어 동기 쿼리와 마찬가지로 반환 할 양방향 반복자를 요청할 수 있습니다.
또한 진행 중인 호출의 간헐적인 상태를 sink에 보고하도록 WMI에 지시할 수 있습니다. pResponseHandler는 실제 결과 처리를 구현하는 sink 인스턴스입니다. API 측면에서 두 접근 방식의 유일한 차이점은 두 가지입니다.
Win32_PingStatus 쿼리를 비동기적으로 만드는 코드는 다음과 같습니다:
CComPtr< CPingSink > pPingSink = new CPingSink;
hr = service->ExecQueryAsync( L"WQL", L"SELECT * FROM Win32_PingStatus " \
L"WHERE Address=\"127.0.0.1\"", 0, NULL,
pPingSink );
CPingSink는 앞서 언급한 sink 클래스입니다. COM 인터페이스 IWbemObjectSink를 구현합니다. 이 인터페이스는 두 가지 방법을 정의합니다(물론 IUnknown의 방법에 추가로):
HRESULT Indicate( long lObjectCount, IWbemClassObject** apObjArray );
HRESULT SetStatus( long lFlags, HRESULT hResult, BSTR strParam,
IWbemClassObject* pObjectParam );
IWbemObjectSink::Indicate는 apObjArray에서 반환된 쿼리 결과를 처리하기 위해 호출됩니다.
이 배열의 결과 수는 lObjectCount로 표시됩니다. 필요 시 공급자가 Indicate를 여러 번 호출할 수 있습니다.
WMI 공급자는 SetStatus를 호출하여 소비자에게 완료 및/또는 진행률 정보를 나타냅니다.
WBEM_FLAG_SEND_STATUS가 ExecQueryAsync의 lFlags 매개 변수에 설정된 경우 SetStatus는 공급자에 의해 여러 번 호출될 수 있습니다.
그렇지 않은 경우 정확히 한 번 호출되고 질의의 완료를 나타냅니다. strParam 및 pObjectParam은 전자의 경우 복잡한 오류 정보에 사용됩니다.
좀 더 안전한 비동기 질의 만들기(SecAsyncPing 프로젝트)
WMI와 비동기식으로 상호 작용하기로 한 결정에는 두 가지 주요 의미가 있습니다.
동기 쿼리는 단순하고 단일 스레드 특성이지만 비동기 질의(또는 관련 결과 처리)는 다중 스레드 유형에 가깝습니다. 질의 스레드, 즉 소비자는 질의를 시작한 후 계속되고 얼마 후 WMI 스레드는 객체 싱크의 콜백을 실행합니다.
객체 sink의 수명을 제어하는 코드는 이를 반영해야 합니다.
두 번째 의미는 첫 번째 의미의 직접적인 결과입니다. WMI는 소비자 프로세스에서 객체 싱크의 콜백을 실행하므로 소비자와는 다른 보안 컨텍스트로 이 작업을 수행할 수 있습니다.
따라서 더 하위 권한을 가진 WMI 공급자는 더 상위 권한을 가진 소비자의 데이터 구조에 액세스할 수 있습니다. 소비자에게 이는 신뢰할 수 있는 구성 요소가 됩니다.
분명히 이것이 받아들여지지 않는 상황이 있습니다.
한 가지 솔루션은 다음 하위 섹션에서 설명하는 반동기 쿼리를 대신 사용하는 것이며, 다른 하나는 보안되지 않은 아파트 내에 sink 객체를 실행하는 것입니다.
보안되지 않은 아파트에서 sink를 실행한다는 것은 액세스 확인을 수행하는 다른 특수 프로세스에서 sink를 실행한다는 것을 의미합니다. 소비자가 Windows XP 또는 Windows Server 2003 플랫폼에서 실행되는지 여부에 따라 이에 대한 세부 정보가 서로 약간 다릅니다.
기본 개념은 sink 객체 대신 IWbemServices::ExecQueryAsync에 전달되는 스텁 객체를 래핑하는 것입니다.스텁 객체는 UNSECAPP.EXE에서 실행을 호스팅하는 실제 sink에 대한 공급자의 호출을 전달 해 줍니다.
액세스 검사는 Windows Server 2003의 경우 UNSECAPP.EXE 구현 또는 IWbemObjectSink::Indicate 및 IWbemObjectSink::SetStatus 메서드 내부의 sink 자체에서 수행됩니다. 싱크 객체에 의한 WMI 설정 및 결과 처리는 이전 하위 섹션과 동일합니다.
이 접근 방식을 용이하게 하기 위해 WMI 소비자는 CoCreateInstance를 호출하여 IUnsecureApartment 클래스를 인스턴스화합니다.
CComPtr< IUnknown > pApartment;
HRESULT hr = CoCreateInstance( CLSID_UnsecuredApartment, NULL, CLSCTX_LOCAL_SERVER,
IID_IUnsecuredApartment,
reinterpret_cast< void** >( &pApartment ) );
Windows Server 2003에서 반환된 인스턴스는 실제로 IWbemUnsecuredApartment 클래스입니다. 마찬가지로 지원되는 IUnsecuredApartment 대신 이 클래스를 사용하면 콜백의 보안이 다소 쉬워집니다. 물론 Windows Server 2003에서 실행되는 소비자는 원하는 경우 IUnsecuredApartment를 사용할 수 있습니다.
여기에서 IWbemUnsecuredApartment를 직접 인스턴스화하지 않는다는 점에 유의해야 합니다. MSDN의 예는 완전히 잘못되었습니다.
실제 스텁 생성은 매우 쉽게 수행됩니다. Windows XP에서 실행할 때 소비자는 IUnsecuredApartment::CreateObjectStub을 호출합니다:
HRESULT CreateObjectStub( IUnknown* pObject, IUnknown** ppStub )
pObject는 sink 객체이고 ppStub는 새로 생성된 스텁 객체입니다. 이 메서드는 pObject가 NULL인 경우 E_POINTER를 반환합니다. 앞에서 언급했듯이 액세스 확인 구현은 소비자의 책임입니다. 예제 프로젝트에서 이 클래스는 CPingSinkUnsecure 입니다.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WBEM\CIMOM\UnsecAppAccessControlDefault 아래의 레지스트리 값이 0(기본값)인 경우 UNSECAPP.EXE에서 액세스 검사를 수행하지 않습니다.
Windows Server 2003에서는 Windows Server 2003 이전 플랫폼에서는 이 키를 사용할 수 없습니다. 스텁 객체를 만들 때 이 동작을 재정의할 수 있습니다. 스텁 생성은 IWbemUnsecuredApartment 인스턴스에서 CreateSinkObject를 호출하여 수행됩니다.
HRESULT CreateObjectSink( IUnknown* pSink, DOWRD dwFlags, LPCWSTR wszReserved,
IWbemObjectSink** ppStub )
pSink에서 원래 싱크로 호출되는 dwFlags의 값은 UnsecApp.exe의 동작을 제어합니다: WBEM_FLAG_UNSECAPP_DEFAULT_CHECK_ACCESS는 위에서 언급한 레지스트리를 사용합니다.
WBEM_FLAG_UNSECAPP_CHECK_ACCESS 와 WBEM_FLAG_UNSECAPP_DONT_CHECK 는 레지스트리를 무시하고 접근 확인을 할지 말지에 관련 된 것입니다.
예를 들어 액세스 확인을 강제하려면 다음과 같이 CreateSinkStub을 호출합니다:
CComPtr< IWbemObjectSink > pStub;
CComPtr< IWbemUnsecuredApartment > aprt
= pApartment->QueryInterface( IID_IWbemUnsecuredApartment,
reinterpret_cast< void** >( &aprt ) );
HRESULT hr = aprt->CreateSinkStub( pSink, WBEM_FLAG_UNSECAPP_CHECK_ACCESS, NULL,
&pStub );
싱크 객체 대신 스텁을 전달하면 소비자는 이전 하위 섹션에서 처럼 쿼리를 만듭니다:
hr = service->ExecQueryAsync( L"WQL", L"SELECT StatusCode FROM Win32_PingStatus " \
L"WHERE Address=\'127.0.0.1\'", 0, NULL, pStub );
반동기 질의들(Project SemiSyncPing)
이전 하위 섹션에서 설명한 것처럼 비동기 쿼리는 WMI 공급자를 신뢰할 수 있는 구성 요소로 가정하기 때문에 일부 보안 문제를 일으킬 수 있습니다.
이것이 허용되지 않는 시나리오가 있습니다. 다소 비효율적인 동기 쿼리 방식으로 돌아가는 대신 WMI 소비자는 쿼리를 반동기적으로 수행하도록 선택 하는 것입니다.
이는 동기 쿼리 결과의 안전한 처리와 비동기 쿼리 결과 계산의 다중 스레드 특성을 결합하는 실행 모드입니다. 동시에 싱크 클래스를 구현할 필요가 없습니다. 결과는 동기 쿼리와 마찬가지로 IEnumWbemClassObject 인스턴스에 의해서 직접 반환됩니다.
따라서 반동기 쿼리의 메커니즘은 동기 쿼리와 비교할 때 매우 간단합니다:
동기 쿼리는 결과를 계산하고(아마도 다른 스레드 또는 프로세스에서) 완료된 후에만 준비된 열거자를 결과로 반환하는 반면, 반동기 쿼리는 열거자를 준비 하고, 해당 요소를 보류(pending) 중으로 표시한 다음 다른 스레드에서 계속 진행 된 실제 결과 계산과 함께 반환됩니다.
두 경우 모두 IEnumWbemClassObject:Next를 호출할 때 이 메서드는 열거형의 다음 요소 상태를 검사하고 필요한 경우 요소 상태가 비보류(non-pending) 상태가 되도록 지정된 시간을 기다립니다.
함께 제공되는 소스 코드는 동기 및 반동기 메커니즘 간의 이러한 유사성을 반영합니다.
lFlags의 추가 WBEM_FLAG_RETURN_IMMEDIATELY 플래그를 제외하고 반동기 쿼리의 경우 ExecQuery 호출은 동일합니다.
이 플래그는 WMI가 쿼리를 반동기적으로 처리하고 WBEM_S_NO_ERROR를 결과 코드로 사용하여 즉시 ExecQuery 호출을 완료하도록 지시합니다(보류 중인 다른 오류 조건이 없는 경우):
CComPtr< IEnumWbemClassObject > enumerator;
hr = service->ExecQuery( L"WQL", L"SELECT * FROM Win32_PingStatus " \
L"WHERE Address=\"127.0.0.1\"",
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL, &enumerator );
호출이 성공적으로 완료되면 이제 소비자는 lTimeOut 매개 변수에 지정된 timeout으로 IEnumWbemClassObject::Next를 호출하여 열거자에 반환된 열거형 결과에 대해 실시간으로 폴링합니다:
while ( ( hr = enumerator->Next( 1L, 1L, &ping, &retcnt ) ) == WBEM_S_TIMEDOUT );
동기 쿼리를 사용하는 경우 이는 WBEM_INFINITE로 설정되어 제한 시간이 만료되지 않음을 나타냅니다.WBEM_NO_WAIT는 시간 초과가 전혀 없음을 의미합니다.
열거의 결과를 사용할 수 있게 되면 Next 호출이 WBEM_NO_ERROR 결과 코드와 함께 반환됩니다.
Timeout 만료는 WBEM_S_TIMEDOUT을 반환하여 표시됩니다. 결과 집합이 비어 있거나 끝에 도달한 경우 WBEM_S_FALSE가 반환됩니다.
열거형에 둘 이상의 요소가 포함되어 있고 소비자가 모든 요소를 사용하지 않기로 결정이 되면 열거형에 대해 Release를 호출하면 WMI가 결과 계산을 중지하고 이는 열거형과 해당 콘텐츠를 마지막드로 해제합니다.
반동기 쿼리는 비동기 쿼리를 만드는 데 권장되는 방법입니다. 비동기식보다 설정하기 쉽고 이와 관련된 다중 스레딩 및 보안 문제를 피할 수 있습니다. 그러나 비동기 방식이 필요한 상황이 있을 수 있습니다.
예를 들어, 소비자의 요구 사항이 특정 시나리오에서 실시간으로 폴링하는 것이라면 구현하기 어려울 수 있습니다.
'프로그래밍' 카테고리의 다른 글
[JavaScript] console.log() 함수에 여러변수를 한줄로 ? (0) | 2023.02.15 |
---|---|
[C++]WMI 질의 만들기-3 (0) | 2023.02.14 |
[C++]WMI 질의 만들기 - 1 (0) | 2023.02.12 |
WMI C++ 응용프로그램 예제들 (0) | 2023.02.11 |
WMI 데이터 수집 프로그램 예제 (1) | 2023.02.10 |