지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]

[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]
[강좌로 보는 서비스 프로그래밍] 가끔 쓸모있는 관련 함수들 [4/?]

이번에는 간단하게 설치된 서비스의 시작, 중지, 일시정지, 일시정지 풀기에 관한 내용을을 짤막하게 다루어 본다.

먼저 서비스 매니저의 핸들을 얻는다. 서비스 매니저의 핸들을 이용하여, 특정 서비스를 제어하는 것이 가능하다.
SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

서비스 매니저 핸들을 이용하여, 원하는 서비스를 오픈한다.
SC_HANDLE hSrv = OpenService(hScm, "열고자 하는 서비스의 이름", SERVICE_ALL_ACCESS);

해당 서비스의 핸들을 이용하여, 서비스를 시작한다.
StartService(hSrv, 0, NULL);

해당 서비스의 핸들을 이용하여, 서비스를 중지 시킨다.
SERVICE_STATUS ss;
ControlService(hSrv, SERVICE_CONTROL_STOP, &ss);

해당 서비스의 핸들을 이용하여, 서비스를 일시 중지 시킨다.
SERVICE_STATUS ss;
ControlService(hSrv, SERVICE_CONTROL_PAUSE, &ss);

해당 서비스의 핸들을 이용하여, 일시 중지된 서비스를 재개시킨다.
SERVICE_STATUS ss;
ControlService(hSrv, SERVICE_CONTROL_CONTINUE, &ss);

위와 같은 형태로 서비스의 동작 상태를 변경 하였을 때, 현재의 서비스 동작 상태를 읽어온다.
SERVICE_STATUS ss;
ControlService(hSrv,SERVICE_CONTROL_INTERROGATE,&ss);

// 지금까지 나온 SERVICE_STATUS 의 구조에 대하여 잠깐 살펴보자.
typedef struct _SERVICE_STATUS {
   DWORD   dwServiceType;                     // 서비스의 타입(파일드라이버/커널드라이버/일반서비스 ...)
   DWORD   dwCurrentState;                     // 서비스의 동작 상태
   DWORD   dwControlsAccepted;             // 서비스가 받을 수 있는 이벤트 설정 상태
   DWORD   dwWin32ExitCode;                  // 상태 변경주 에러가 발생했을 시 에러코드
   DWORD   dwServiceSpecificExitCode;    // 위와 같으나 좀 다르다.
   DWORD   dwCheckPoint;                       // 적당히 무시
   DWORD   dwWaitHint;                           // 적당히 무시
} SERVICE_STATUS, *LPSERVICE_STATUS;

그외에 설치할 때 당시의 여러 설정이나 상태를 변경하려면
ChangeServiceConfig 혹은 ChangeServiceConfig2  를 이용하여, 다양한 변경을 가해줄 수 있다.

서비스의 동작을 외부에서 제어했을 경우, 내부에서는 해당 상태에 맞도록 상태 설정을 처리해 주어야 한다.
(만약 내부에서 알맞게 상태를 변경하여 주지 않으면, 외부에서는 응답없음과 같은 기괴한 동작을 할것이다.)
SERVICE_STATUS ss;
ss.dwCurrentState = 원하는 상태;
SetServiceStatus(hSrv, &ss);

/* 설정 가능한 상태
#define SERVICE_STOPPED                        0x00000001
#define SERVICE_START_PENDING                  0x00000002
#define SERVICE_STOP_PENDING                   0x00000003
#define SERVICE_RUNNING                        0x00000004
#define SERVICE_CONTINUE_PENDING               0x00000005
#define SERVICE_PAUSE_PENDING                  0x00000006
#define SERVICE_PAUSED                         0x00000007
*/

저렇게 열어준 핸들은 사용후에 받드시 닫아주어야 한다.
CloseServiceHandle(hSrv);
CloseServiceHandle(hScm);

이전 장에서 서비스를 설치/제거, 여기서는 시작/중지를 확인하였으니, 다음 장에서는 서비스 본체에 대한 정리를 하고자 한다.
( 흐름을 보기 위한 부분으로 에러처리 없이 가장 간단한 형태로 설정이 되어 있으므로, 다음에는 추가적인 처리가 있을것이다.)

지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]

[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]

지난 장에서는 실제 서비스 몸통을 만들기 전에 만들어진 서비스를 설치/제거하는 아주 초간단 구현을 살펴보았다.
원래는 이번장에서 서비스를 중지, 일시중지, 재시작 상태를 변경시키는 기능에 대하여 진행하려고 하였으나
몇몇 분의 요청에 의하여 지금까지 열거된 기능중에 필요한 함수 몇개를 먼저 소개하고자 한다.

1. 사용자 계정에 권한을 할당하는 UserPrivileges 함수.
#define TARGET_SYSTEM_NAME L"."
LSA_HANDLE GetPolicyHandle()
{
    LSA_OBJECT_ATTRIBUTES ObjectAttributes;
    WCHAR SystemName[] = TARGET_SYSTEM_NAME;
    USHORT SystemNameLength;
    LSA_UNICODE_STRING lusSystemName;
    NTSTATUS ntsResult;
    LSA_HANDLE lsahPolicyHandle;

    // Object attributes are reserved, so initialize to zeros.
    ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));

    //Initialize an LSA_UNICODE_STRING to the server name.
    SystemNameLength = wcslen(SystemName);
    lusSystemName.Buffer = SystemName;
    lusSystemName.Length = SystemNameLength * sizeof(WCHAR);
    lusSystemName.MaximumLength = (SystemNameLength+1) * sizeof(WCHAR);

    // Get a handle to the Policy object.
    ntsResult = LsaOpenPolicy(
       &lusSystemName,    //Name of the target system.
       &ObjectAttributes, //Object attributes.
       POLICY_ALL_ACCESS, //Desired access permissions.
       &lsahPolicyHandle  //Receives the policy handle.
        );

    if (ntsResult != ERROR_SUCCESS)
        return NULL;
    return lsahPolicyHandle;
}

BOOL InitLsaString(PLSA_UNICODE_STRING pLsaString, LPCWSTR pwszString)
{
    DWORD dwLen = 0;

    if (NULL == pLsaString)
        return FALSE;

    if (NULL != pwszString)
    {
        dwLen = wcslen(pwszString);
        if (dwLen > 0x7ffe)   // String is too large
            return FALSE;
    }

    // Store the string.
    pLsaString->Buffer = (WCHAR *)pwszString;
    pLsaString->Length =  (USHORT)dwLen * sizeof(WCHAR);
    pLsaString->MaximumLength= (USHORT)(dwLen+1) * sizeof(WCHAR);

    return TRUE;
}

BOOL GetUserSID(PSID sid, const char* username)
{
   DWORD SidBufSz;
   char DomainNameBuf[256] = {0};
   DWORD DomainNameBufSz;
   SID_NAME_USE SNU;
   DomainNameBufSz = 256;

   if(!LookupAccountName(NULL, username, sid, &SidBufSz, DomainNameBuf, &DomainNameBufSz, &SNU))
       return FALSE;
    return TRUE;
}

BOOL UserPrivileges(PSID AccountSID, LSA_HANDLE PolicyHandle, wchar_t* pszPrivilege)
{
    LSA_UNICODE_STRING lucPrivilege;
    NTSTATUS ntsResult;

    // Create an LSA_UNICODE_STRING for the privilege names.
    if (!InitLsaString(&lucPrivilege, pszPrivilege))
        return FALSE;

    ntsResult = LsaAddAccountRights(
        PolicyHandle,  // An open policy handle.
        AccountSID,    // The target SID.
        &lucPrivilege, // The privileges.
        1              // Number of privileges.
        );

    if (ntsResult == ERROR_SUCCESS)
        return TRUE;
    else
        return FALSE;
}
MSDN에 있는 샘플 소스를 가져다가 추려서 사용하고 있는 기능입니다. 윈도우에서 계정이나 보안에 상관있는 API 등을 호출하거나 사용하려면
권한에 관한 문제에 자주 걸리는데, 저것 말고도 알아야 할 기능들이 참 많죠.

2. 프로세스에 권한을 할당하는 ProcessPrivilege 함수
BOOL ProcessPrivilege(HANDLE pid, char* pszPrivilege, BOOL bEnable)
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;

    // Get a token for this process.
    if (!OpenProcessToken(pid, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return FALSE;

    // Get the LUID for the shutdown privilege.
    LookupPrivilegeValue(NULL, pszPrivilege, &tkp.Privileges[0].Luid);

    tkp.PrivilegeCount = 1;  // one privilege to set
    tkp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_REMOVED;

    // Get the shutdown privilege for this process.

    return AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
}
유저도 권한을 가져야 하겠지만, 유저가 실행시킨 프로세스도 권한이 있어야 할때가 많습니다.

3. 사용자 데스크탑에 GUI를 띄워주는 CreateProcessToDesktop 함수.
BOOL CreateProcessToDesktop(char* pszExecute, UINT sampling)
{
    HANDLE hd = OpenProcess(PROCESS_ALL_ACCESS, FALSE, sampling);
    if(hd == NULL)
        return FALSE;

    HANDLE token = NULL;
    if(OpenProcessToken(hd, TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE, &token) == FALSE)
    {
        CloseHandle(hd);
        return FALSE;
    }

    HANDLE newtoken = NULL;
    if(::DuplicateTokenEx(token, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, NULL,
          SecurityImpersonation, TokenPrimary, &newtoken) == FALSE)
    {
        CloseHandle(hd);
        CloseHandle(token);
        return FALSE;
    }

    CloseHandle(token);

    void* EnvBlock = NULL;
    CreateEnvironmentBlock(&EnvBlock, newtoken, FALSE);

    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};

    if(::CreateProcessAsUser(newtoken,
        pszExecute,
        NULL,
        NULL,
        NULL,
        FALSE,
        NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT |
        CREATE_NEW_CONSOLE  | CREATE_SEPARATE_WOW_VDM |
        CREATE_NEW_PROCESS_GROUP,
        EnvBlock,
        NULL,
        &si,
        &pi) == TRUE)
    {
        CloseHandle(hd);
        CloseHandle(newtoken);
        return TRUE;
    }

    CloseHandle(hd);
    CloseHandle(newtoken);
    return FALSE;
}
예전에는 저걸 구현하기 위해 OpenWindowStation 과 OpenDesktop 을 이용해서 오만가지 필드를 채워가며 굉장히 있어보이는 긴 코드를
이용해서 구현했었습니다. 위 함수는 뛰우고 싶은 윈도우 세션을 구해서, 해당 세션에 있는 프로세스 아무거나 하나 가져온다음에, 그 핸들을
복사해서 그 핸들이 존재하는 세션에 그냥 띄워 버리는 겁니다.

4. 프로그램이 서비스로 구동중인지 알아보는 IsServiceMode 함수
BOOL IsServiceMode()
{
    // need _WIN32_WINNT as 0x0500 or later
    return GetConsoleWindow() ? FALSE : TRUE;
}
서비스 일경우는 해당 GetConsoleWindow 가 NULL 을 리턴하고, 응용프로그램 모드에서 돌아가면 해당 콘솔의 핸들이 리턴됩니다.
이걸 이용해서 서비스로 구동중인지, 그냥 응용프로그램 모드로 구동중인지를 내부적으로 감지해서 다르게 동작하도록 구현하는게 가능합니다.

5. 해당 서비스에 걸린 디펜던시를 찾는 FindDependencyService 함수
UINT FindDependencyService(char* pszServiceName, LPENUM_SERVICE_STATUS& lpDependencies)
{
    SC_HANDLE hScm = OpenSCManager(".", NULL, SC_MANAGER_CREATE_SERVICE);
    if(hScm == NULL)
        return 0;

    SC_HANDLE hSrv = OpenService(hScm, pszServiceName, GENERIC_READ);
    if(hSrv == NULL)
    {
        CloseServiceHandle(hScm);
        return 0;
    }

    DWORD dwBytesNeeded = 0, dwCount = 0;
    if(EnumDependentServices(hSrv, SERVICE_STATE_ALL, NULL, 0, &dwBytesNeeded, &dwCount))
        goto ERROR_LABEL;
    else if(GetLastError() == ERROR_MORE_DATA)
    {
        lpDependencies = (LPENUM_SERVICE_STATUS) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBytesNeeded );
        if(lpDependencies == NULL)
            goto ERROR_LABEL;

        if ( !EnumDependentServices( hSrv, SERVICE_STATE_ALL, lpDependencies,
            dwBytesNeeded, &dwBytesNeeded, &dwCount ) )
        {
            HeapFree(GetProcessHeap(), 0, lpDependencies);
            lpDependencies = NULL;
            goto ERROR_LABEL;
        }
    }

ERROR_LABEL:
    CloseServiceHandle(hSrv);
    CloseServiceHandle(hScm);
    return dwCount;
}
그냥 지정된 서비스에 디펜던시가 걸린 목록을 쭉 뽑아주는 기능을 담당합니다.

#. 그외 윈도우 보안과 관련된 링크들
    [자료] 로그온 사용자, 이름과 유저그룹 정보 
    [MSDN] 윈도우 보안 카테고리
    [MSDN] Authentication Functions and Samples
    [MSDN] Authorization Functions and Samples
    [codeguru] 서비스 카테고리
    [codeproject] System Programming - Windows Services

추릴 만한 함수들과 관련 링크들은 해당 페이지에 지속적으로 업데이트를 하겠습니다.

이번 장에서는 서비스의 몸통을 만들기 전에 서비스를 인스톨 하는 부분과 제거하는 부분에 대하여 알아볼 것이다.
서비스를 설계할 때 기능도 당연히 중요하지만 어떤 동작 상태를 주어 기동하게 할것인가에 대하여 생각해 보아야한다.

1. 먼저 서비스 이름이나 설명을 어떻게 설정할 것인가?
   -> 단순한 문제이지만, 서비스 이름이나 실행파일명, 그리고 이에 따른 설명은 해당 서비스가 어떠한 기능을 하게 되는지
       그리고, 사용하는 사람들이 직관적으로 알아 볼 수 있도록 명확하게 하여야 한다.

2. 서비스를 구동 시키는 계정은 어떻게 처리할 것인가?
   -> 보통 아주 일반적으로 디폴트 계정 즉, SYSTEM(Local System Account) 계정을 사용하게 된다. 
       아주 일반 적인 상황이지만, 간혹 Administrator 계정으로 설정해야 하거나, 혹은 특정 Users 계정으로 구동해야 하는
       경우도 없지않다.

       SYSTEM 계정이 아닌 다른 계정을 사용하게 되면 해당 계정에 SeServiceLogonRight 권한을 주어야한다.
       또한 암호가 비어있는 유저일 경우는 이것 외에도 보안 설정을 풀어주어야 한다.

3. 초기 구동시 자동 시작으로 할것인가? 수동 시작으로 할것인가?
   -> 보통 자동시작으로 초기 설정을 확정하는 경우가 많은데... 서비스가 순수하게 독립 기능을 수행할 경우는 모르겠지만
       먼가 연계되는 부분이 존재한다면, 사용자가 시작 시켜주기를 기다려야 할 수도 있다.
       이러한 상황을 무시하고 무조껀 자동 시작으로 해놓으면, 서비스가 구동에 실패하면서 시스템 로그에 서비스 구동 실패
       로그를 쌓을 것이다. 

4. 서비스가 구동에 실패하였을 경우, 어떠한 처리를 할것인가?
   -> 보통 정상적으로 서비스가 구동되기를 기대하지만, 아주 황당한 이유등으로 서비스 구동이 실패할 수 있다.
       주로 겪는 문제가 Network Adapter 장애로 인하여 소켓등에 문제가 생기거나, 과부하로 인해 스토리지가 깨지거나
       AD 서버 장애로 인하여 로그인에 실패하거나 DBMS가 죽어 있거나 .... 등등.. 여러가지 문제가 발생할 수 있다.

5. 특정 서비스에 의존하는 부분이 존재하는가?
   -> 특수 목적용으로 만들어진 서비스중에서는 DBMS에 의존하던가? 무선랜 서비스가 켜져 있어야 한다던가?
       원격의 관리를 위하여 여러 서비스가 활성화 되어 있어야 한다건가.. 

       편하게 이러한 시스템 에러에 대응하여 디펜던시를 걸어 놓으면 가장 편하게 에러 상황을 피해 나갈 수 있다.
       문제는 이렇게 디펜던시를 걸어 놓으면 상위 서비스에 장애가 생겼을 경우, 하위 서비스는 아예 구동 조차도
       되지 않으므로 어떠한 문제가 발생하였는지 전혀 알 수 가 없다는 것이다.

       그래서, 디펜던시를 걸어 놓는 방법은 추천하지 않는다. 특정 서비스에 종속적이라면 서비스가 초기 구동될 때
       해당 서비스들의 구동 상태를 점검하고 꺼져 있는건 시작 시켜주고 문제가 생기면 에러 로깅을 해주고 종료해야 한다.

6. 데스크탑 유저들과 연동해야 하는 부분이 존재하는가?
   -> 서비스는 백그라운드에서 구동되기 때문에 사실상 로그온된 상태의 유저들에게 화면상으로 정보를 전달하는게 불가능하다.
       보통 IPC를 통하여 사용자 프로그램과 연동하지만, 서비스에서 GUI 어플리케이션을 실행 시켜 줄 수도 있다.
 
       데스크탑과 연동이라는 옵션을 이용하면, 이러한 부분을 일부 커버할 수 있지만, 이건 순수하게 Console Session 만 해당한다.
       즉, 터미널 유저에게는 영향을 못미친다는 것이다.

       서비스와 연동하는 GUI에서 특정 정보를 전달하고 종료했을 때, 서비스 구동에 문제가 생기면 로그에만 남겨야할까?


      
머 저렇게 하나씩 준비해서 서비스의 설치단계를 진행하는 사람은 없겠지만, 한번 쯤은 점검해볼 필요는 있지 않을까?
사실상 개발할 때 문제가 되거나 가장 귀찬은 영역이 4, 5, 6 번 영역이다... -_-;;;

다음의 예제 코드는 아주 간단한 서비스 설치 코드이다. 설정을 최소화한 상태에서는 저렇게 쉬울 수가 없다.
SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
   if (hScm == NULL)
       return GetLastError();

   SC_HANDLE hSrv = CreateService(
       hScm,
       "서비스의 고유 이름",
       "서비스의 외부 표시 이름",
       SERVICE_ALL_ACCESS,
       SERVICE_WIN32_OWN_PROCESS,
       SERVICE_AUTO_START,
       SERVICE_ERROR_NORMAL,
       "서비스 실행파일 경로",
       NULLNULLNULL, NULL, NULL); 

   if(hSrv )
       CloseServiceHandle(hSrv);
   CloseServiceHandle(hScm);

자, 아주 쉽게 서비스의 설치가 완료되었다. 간단하게 이름을 주고, 자동시작하고, 에러는 적당히 시스템로그에 남기고 끝이다.
보통 대부분의 서비스의 설치는 저 상태라도 전혀 문제가 없다...

1. 시스템을 관리하기 위하여 특정한 SP_USER 라는게 생성되었다. 모든 동작이 해당 계정에서 처리되며, 각종 레지스트리도 저 유저 계정하에서
    동작되도록 처리해야 한다. 해당 계정으로 만들어진 대부분은 Administrator 의 접근을 배제 시켜 놓았다.

작업의 편의성을 위해서라도 서비스의 구동 계정을 SP_USER 라고 맞출 필요가 방금 생긴것이다. 왜? 방금 생각해 낸것이니까 ㅡ.,ㅡ
그렇다면 SP_USER가 암호를 가지고 있는가? 아닌가에 따라 작업이 또 달라진다.

SP_USER 가 암호를 가진 유저라면 해당 계정에  SeServiceLogonRight 권한만 주면 문제가 없겠지만, 아쉽게도 암호가 비어있을 경우는
또다른 문제를 생성시킨다. 즉, 기본적으로 서비스를 구동시키는 계정은 암호를 비워둘 수가 없다.

그냥 서비스 계정을 SYSTEM 으로 만들면 아주 편한데, 저거 하나 때문에 골머리 썩어보신 분이 있을 겄이다. (없으면 나만 그랬나.. ㅡㅜ);
(물론 서비스 매니저에서 마우스로 클릭해주고, 보안 관리자에서 보안을 손으로 풀어주면 아주 쉽게 해결되는 부분이기도 하다. 아주 아주 쉽다...)
(항상 코딩으로 먼가를 해결해야 할때가 문제인거다 -_-;;;)

서비스 설치에 저런게 있다는거고 모두 저런 자질구래한 것에 신경 쓸 필요는 없다. 필요한 사람에게 아주 가끔 도움이 될 수 있을까?
세세한 설치방법과 에러 처리에 관한 부분은 샘플 코드를 작성하는 부분으로 넘긴다.

자 다음으로는 서비스를 제거해봐야 겠다.
   SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
   if (hScm == NULL)
       return GetLastError();

   SC_HANDLE hSrv = OpenService(hScm, "서비스 이름", SERVICE_ALL_ACCESS);
   if (hSrv == NULL)
       goto ERROR_LABEL;

   if(!DeleteService(hSrv))
       goto ERROR_LABEL;

   ERROR_LABEL:
   if(hSrv)
       CloseServiceHandle(hSrv);
   if(hScm)
       CloseServiceHandle(hScm);
이전 설치와 마찬가지로 아주 쉽다. 더불어 서비스를 제거할 경우는 '서비스 이름' 이라는 단 하나만 알고 있으면 된다.
별도의 에러처리는 실제 프로그램을 하면서 하나씩 진행하면 되므로, 특별한 에러처리나 이런것은 배제하였다.

다음으로는 여기서 중간 중간 설명하던 권한이나, 서비스 상태에서 데스크탑에 GUI를 띄워주거나 이런 잘잘한 기능을 구현한 함수들을
일부 소개한다. 앞으로 가끔 쓰일것이므로, 미리 포스팅하여 참고할 수 있도록 할것이다.



지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]


지난 내용에서 서비스가 구동되는 순서는 어떠한지, 또한 서비스 프로그래밍의 장/단점은 어떠한지에
대하여 간략하게 살펴보았다. 자세한 기술 관점으로 넘어가기전에 서비스 관리자에서 (services.msc)
살펴볼 수 있는 항목들에 대하여 간략하게 설명하고, 이러한 항목들이 프로그래밍되는 API 와 연관되는지
살펴보도록 하겠다.

전체적으로 외향적인 부분을 살펴보는 것이므로, 주의깊게 살펴보지 않아도 작업하는데 지장은 없다.

<윈도우에서 제공하는 서비스 관리자의 모습>

가장 일반적으로 접하게 되는 GUI로써, 서비스를 시작/중지/일시 정지 시킬 수 있고 서비스의 현재 동작
상태를 살펴볼 수 있다. 해당 서비스가 어떠한 계정으로 구동중인지도 확인이 가능하다.


1. General (일반 속성)

A. 서비스 이름 (Service name)
    외부에서 서비스를 제어하기 위한 고유한 이름이다.
    모든 Pirntable Charcter는 모두 사용가능하다.

B. 표시 이름 (Display name)
    서비스 이름은 고유명사 형태의 간략화된 상태로
    사용되는 이름이지만, 표시 이름은 그 서비스의 특성을
    알려주는데 사용되는 별칭과 같은 형태이다.

C. 설명 (Description)
    해당 서비스의 사용 목적이나 기능에 대하여 알려주는
    내용으로, 핵심적인 내용을 요약하여 보여준다.

D. 실행파일 경로 (Path to executable)
    해당 서비스가 설치되어 있는 전체 경로를 나타낸다.
    단, 실제로 구동될 경우는 Windows\System32 경로에서
    구동되므로, 구동시 상대 경로 설정에 주의해야 한다.

위의 4가지 속성중, 서비스 이름과 실행 경로는 필수 정보이고
나머지 2가지 정보는 부가 정보이므로 생략이 가능하다.

 E. 시작 형태 (Startup type)
    머신이 구동될 때, 해당 서비스가 자동으로 시작될것인지
    사용자에 의하여 수동으로 시작될 것인지를 나타낸다.
    (Disable은 사용을 임시로 막아 놓은 상태)

   F.  서비스 상태 (Servces status)
       해당 서비스의 동작 상태를 시각적으로 보여준다. 다음과 같이 여러단계를 가진다.
       시작중, 시작됨, 중지중, 중지됨, 일시중지중, 일시중지됨, 재시작중...

       //
       // Service State -- for CurrentState
       //
       #define SERVICE_STOPPED                        0x00000001
       #define SERVICE_START_PENDING            0x00000002
       #define SERVICE_STOP_PENDING              0x00000003
       #define SERVICE_RUNNING                        0x00000004
       #define SERVICE_CONTINUE_PENDING     0x00000005
       #define SERVICE_PAUSE_PENDING            0x00000006
       #define SERVICE_PAUSED                           0x00000007

  G. 동작 제어 버튼
      서비스의 동작 상태를 변경하는데 사용되는 버튼들이다.
      SetServiceStatus API를 이용하여, 간단하게 서비스의 동작을 제어할 수 있다.


  H. 시작 파라미터 (Start parameter)
      서비스를 서비스 관리자에서 시작시킬 때 서비스의 메인 함수에 인자를 전달할 수 있도록 방법을 제공해주는
      부분이다. 해당 위치에 적절한 파라미터를 위치 시킨 후에 시작 시키면 서비스는 해당 파라미터를 인식하여
      필요한 처리를 할 수 있다.
      단, 주의 할점으로는 휘발성이므로, 한번 적용하고 나서 다시 시작할 때 적용되지 않는다는 것에 주의하자.

여기까지 서비스의 속성창 중에서 첫번째에 보여지는 일반 속성에 대하여 간략하게 알아보았다.
그렇다면, 위에 나열된 것들이 프로그램적으로는 어디에서 사용되는지를 간단하게 살펴보고자 한다.

그럼 처음으로 CreateService Function 함수에 대하여, 살펴보고 위에 나온 내용들이 어디에 사용되는지 확인하자.

SC_HANDLE WINAPI CreateService(
  __in       SC_HANDLE hSCManager,              서비스 매니저의 핸들
  __in       LPCTSTR lpServiceName,                A. 서비스 이름
  __in_opt   LPCTSTR lpDisplayName,            B. 표시 이름
  __in       DWORD dwDesiredAccess,               서비스의 액세스 권한
  __in       DWORD dwServiceType,                    서비스의 종류(파일 드라이버, 커널 드라이버, 독립실행 서비스, 프로세스공유 서비스)
  __in       DWORD dwStartType,                         E. 시작 형태 (자동/수동/사용안함)
  __in       DWORD dwErrorControl,                    서비스 시작 실패시의 후속처리(시스템 재시작, 무시, 무시 및 이벤트로그 기록)
  __in_opt   LPCTSTR lpBinaryPathName,      F. 실행파일 경로
  __in_opt   LPCTSTR lpLoadOrderGroup,      @.@ (1)
  __out_opt  LPDWORD lpdwTagId,                  @.@ (2)
  __in_opt   LPCTSTR lpDependencies,           다른 서비스가 시작된 후에 구동할 필요가 있을 경우, 먼저 실행되어야할 서비스 이름들.. (3)
  __in_opt   LPCTSTR lpServiceStartName,    서비스를 시작시킬 때 사용될 유저 이름 (4)
  __in_opt   LPCTSTR lpPassword ,                   유저 이름이 지정되었을 경우 해당 유저의 암호

)

서비스를 생성할 때 위처럼 복잡하게 많은 인자들이 전달되지만 대부분이 생략 가능한 것들이다.
위에서 파란색으로 표기된 인자들은 필수 인자들인데, 설정하는데 전혀 어려울것이 없다.
나머지 아래의 5개가 조금 헷갈리는 것들인데

(1), (2) 은 나도 잘 모르겠다. 항목을 살펴보면 서비스를 그룹으로 묶을 수 있고, 해당 그룹 목록을 작성할 수 있는것을 보면
먼가가 있어보이는데.. 값을 넣거나, NULL로 해놔도 먼가 변화를 찾아볼 수 없다 -_-;;;

아래는 해당 관련된 정보를 담고 있는 레지스트리 키 이름이다.
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\ServiceGroupOrder
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\GroupOrderList

(3) 번은 "LanmanWorkstation\0LanmanServer\0LmHosts\0\0" 과 같이 여러개의 서비스 사이를 \0으로 구분하고 문자열의
마지막을 \0\\으로 구분해 주면 된다.


               <위의 설정을 이용하여 적용된 디펜던시 상태>

(4) 번은 사용자의 컴퓨터 계정 이름을 넣어주면 되는데, 도메인 사용자는 "도메인이름\사용자이름",
로컬 머신 계정은 ".\사용자이름" 과 같이 도메인을 구분해서 넣어주어야 한다.


2. Log on (시작 계정)
일반 속성에서는 말 그대로 서비스를 사용하는데 필요한 몇가지 필수 요소들의 정보를 가지고 있다. 또한 이렇게 생성된
서비스를 제어할 수 있는 방법을 제공한다. 아래의 그림은 시작 계정을 시스템 계정이 아닌 사용자 정의 계정을 적용
시켜놓은 상태의 모습이다. 하나씩 그 의미를 살펴보자.
A. Log on as: Local System account
   위 Radio Button 을 첵크하면 기본적으로 시스템
   (SYSTEM) 계정으로 서비스가 구동된다. 프로그램
   적으로는 계정을 설정 하지 않고 CreateService에서
   NULL 을 입력하면 되는것이다. 일반적으로 서비스는
   SYSTEM 계정으로 구동하길 권장한다.

B. Allow service to interact with desktop
   제목과 같이 서비스가 사용자가 로그온한 테스크탑과
   서로 연동되도록 설정하는 항목이다. SYSTEM 계정일
   때만 사용이 가능하다.

C. This account
   사용자가 계정을 직접 입력하여 해당 서비스가, 그 계정
   하에서 만 구동하도록 설정하는 것이다.

D. Hardware profile enable/disable
   해당 서비스에 연결가능한 하드웨어 프로파일을 사용하게
   하거나 못하게 하는 것이다. Disable 을 선택하면 서비스의
   사용이 불가능해진다는 점에서 서비스의 시작 타입을
   Disable 시켜 놓은 것과 동일하다.

이번 시작계정 챕터에는 항목은 작지만, 의외로 다루기 까다로운 설정들이 존재하는데... 프로그래밍의 어려움도 있지만
의미를 이해하기 쉽지않은 부분들이 존재한다.

Allow service to interact with desktop (데스크탑과 상호 연동)
해석해보면 데스크탑(사용자가 로그온한 상태의 윈도우 환경)과 연동이 가능하도록 지원해준다는 것이다.
일반적으로 서비스가 구동하게 되면, 어떠한 어플리케이션을 실행시키더라도 화면에 보여주지 못하는 background session 에서 구동된다.
즉, CreateProcess나 ShellExecute 혹은 ShellExecuteEx 를 이용하여, 콘솔이던  GUI이던 실행시키더라도 화면에 보여지지 않는다.
(물론 프로그램은 정상적으로 동작하지만 눈에 보이지 않으니, 프로세스가 떠있다는것 말고는 동작 상태를 확인하기 어렵다.)

 
코딩으로 보면 CreateService 함수에 인자를 전달할 때 SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS
를 전달하기만 하면 위의 그림처럼 첵크되어 해당 기능을 활성화 할 수 있다. 이 자체는 해당 기능을 활성화한것 뿐이지 이것으로
먼가가 자동으로 되는것은 없다.

저 옵션을 사용하면, 프로그램이 실행은 되지만 Console Session 에만 보여진다.
즉, 유저가 로그온한 세션에 프로그램을 띄워주는건 저 방법으로는 불가능하다는 것이다.

그렇다면, 서비스에서 로그온된 유저를 찾아 해당 유저의 세션에 프로그램을 띄워주는 것은 불가능한 것인가?
그 해법으로 CreateProcessAsUser 를 이용하는 방법이 있다. 이 챕터의 설명 범위를 넘어가는 것이기 때문에..
추후에 진행되는 예제중에 해당 처리 방법에 대한 것을 논의 하겠다.

하드웨어 프로파일에 관한건 별 쓸모가 없는 관계로 자세하게 다루지 않을 것이다.

3. Recovery
서비스를 수행하다보면, 여러가지 이유에 의해서 구동이 실패할 수 있다. 컴퓨터가 부팅되는 과정중에 수행되는 서비스의
실패를 어떻게 다룰것인가? 어떠한 이유로 실패하였고, 실패하였다면 그 후속 처리를 어떻게 할 것인가.. 등등.
성공하면 상관없지만, 실패하면 상관있는 그런 항목이다. 중요도가 상당히 높지만 자주 사용되지는 않는다.
사실상 윈도우의 시스템 서비스를 살펴보더라도 99% 쓰지 않거나, 쓴다고 하더라도 서비스 재시작이 고작이다.

왼쪽 그림에서 설명하는 내용도, 서비스가 실패
하였을 경우,

A. 그냥 무시
B. 서비스 재시작
C. 특정 프로그램 실행
D. 컴퓨터 재시작

저기서 C의 특정 프로그램 실행이란 항목을
선택하면 어떤 프로그램을 실행시킬건지 하고
인자를 기입할 수 있는 항목이 생성된다.

최대 3번 까지 재시도 할 수 있도록 제공하고

머 나머지는 실패했을 때 숫자세는데 무한정
셀수는 없으니까.. 언제 초기화 할것인가이다.

99.9%의 대열에 합류하면 이 항목은 전혀 의미
없는 항목이 되겠고, 먼지 몰라도 일단 해본다...
라고 하면, 헤딩을 좀 해야한다.


저기 항목에 있는 것을 설정하려면 ChangeServiceConfig2 API를 이용하여 편집하는 것이 가능하다. 단, 다른 것은
별 문제가 없는데 Restart th Computer 를 처리하려면, 즉, 문제가 생겼을 때 컴퓨터를 재 시작하려면 한가지 권한을
 필요로 한다. 머신을 리부팅 시킬수 있는 권한 즉, SE_SHUTDOWN_NAME 를 필요로 한다는 것이다.

서비스는 SYSTEM 계정으로  구동하므로 필요하다면 언제든지 저 권한을 할당할 수 있으므로 저것이 필요하다는 것을
알고만 있으면 별 문제 없다. AdjustTokenPrivileges 를 이용하여 추가하면 그만이다. 복잡하게 이야기 하면 끝이 없고
간단하게 샘플코드 일부를 정리함으로 이내용은 나중 예제를 위하여 남겨 놓는다.

  SERVICE_FAILURE_ACTIONS sfa = {0};
  SC_ACTION sa[3];
  
  sa[0].Delay = 2000;
  sa[0].Type = SC_ACTION_RESTART;
  sa[1].Delay = 2000;
  sa[1].Type = SC_ACTION_RUN_COMMAND;
  sa[2].Delay = 2000;
  sa[2].Type = SC_ACTION_REBOOT;

  sfa.dwResetPeriod = 600;
  sfa.lpRebootMsg = "";
  sfa.lpCommand = "";
  sfa.cActions = 3;
  sfa.lpsaActions = (SC_ACTION *)sa;

  ChangeServiceConfig2(hSrv, SERVICE_CONFIG_FAILURE_ACTIONS, &sfa);

정말 간단하게 끝나넹.. 요걸 수행하기 이전에  SE_SHUTDOWN_NAME 권한만 주면 되는 것이다.
알고서 진행하면 별거 아닌 것이지만, 모르고 찾을 때는 별 귀찬은 문제가 다 덤비는 것이 이 세계인가 보다.

4. Dependencies
머 이름만 거창하게 디펜던시즈라고 되어있는데.. 짧게 간추려 보면, 내가 만든 프로그램이 특정 서비스에 영향을 받는가? 혹은 주는가?
이러한 것이 있을 경우는 정확하게 명시해라.. 머 이런 의미이다.

내 프로그램을 특정 서비스 동작 이후에 구동되도록 하고 싶다면..
1. General 에서 설명한 (3) 번 처럼 영향을 받을 서비스 목록을 그냥 주르륵 넣어서 서비스를 생성하면 된다. 아주 쉽다.

그렇다면 내 서비스 프로그램에 디펜던시가 걸려있는 다른 서비스 목록이 있다면 이걸 어떻게 찾을 것인가?
EnumDependentServices 를 이용하여 그냥 긁어오면 된다.

이러한 작업이 왜 필요하냐면.. 내가 만약 특정 서비스를 중지 시켜야할 일이 있다고 가정했을 때, 디펜던시 걸린 서비스를 확인하지 않고
그냥 그 서비스를 중지 시키면 절대 죽지 않는다. 프로세스를 kill 한다면 모를까..
디펜던시가 걸린 하부 서비스를 모두 확인하고 그 서비스의 하부를 확인해서 아래부터 차례로 하나식 죽이고 마지막에 목표로한 서비스를
중지시켜야 정상적으로 중지된다.

-----------------------------------------------------------------------------------------------------------------------

지금 까지 서비스 매니저에서 볼 수 있는 몇몇 항목에 대하여 간단하게 기술하여 보았습니다. 모르고 넘어가도 되는 항목들도
많이 있으나, 그런게 있다는걸 알아두면 나중에 도움이 될까 싶어서 그림과 함께 적어 놓았으니 참고가 되셨으면 좋겠네요..

자, 다음으로는 구체적인 서비스보다는 서비스를 설치/제거/제어(시작-중지)에 대하여 정리하여 보도록 하겠습니다.
위 항목도 API를 최대한 간소하게 사용하면 아주 쉽고요, API의 인자들을 모두 채우려면 꽤나 복잡하겠죠? ^^;;;

간단하게 할겁니다 ㅡ,.ㅡ;;;

윈도우 서비스 카테고리에서 이것 저것 서비스 프로그래밍을 하면서 필요한 것들을 하나 둘 씩 진행해 보았다.

이런 것들을 정리하여, 서비스는 왜 필요한 것이며? 무엇 때문에 사용하는가?
어떻게 동작되며, 과연 어느 순간에 실행되는지...
서비스는 일반 콘솔 프로그램과 무엇이 다른지 하나씩 정리를 해보려한다.


일반적으로 윈도우가 기동되는 과정을 단순화 시키면 아래 그림과 같다.




즉, 서비스 프로그램은 윈도우가 부팅된 이후에, 로그온 되지 않은 상태에서도 구동되어진다는 큰 장점을 가지게 된다.

또한 일반적으로 Local System Account 로 구동되기 때문에 시스템을 제어하는데 필요한 가장 강력한
권한을 가진다고 말할 수 있다. 서비스를 설치할 때 혹은 설치한 이후에 특별하게 로그온에 필요한 사용자를
변경하지 않는 이상 강력한 권한을 행사할 수 있다.

두 번째와 같은 이유로, 비스타 커널 부터 적용되는
시스템 경로에 파일쓰기, 프로그램 파일즈 경로에
파일 쓰기, 로컬 시스템 레지스트리 액세스와 같은
모든 작업에 영향을 받지 않고...


특히나 UAC에 영향을 받지 않는다.


이렇게나 많은 장점이 있는데도 불구하고, 응용 프로그램을 서비스로 작성하지 않는 이유는 무엇일까?

1. GUI를 가질 수 없다.
    -> 서비스 프로그램은 콘솔 어플리케이션과 같은 방식으로 프로그래밍이 되면서도 stdin, stdout, stderr 와 같은
        콘솔 핸들을 가지지 못한다. 즉, 화면상의 출력이란것은 불가능하다.

2. 데스크탑과의 연계가 어렵다.
   -> 로그온 되기 이전에 SYSTEM 계정으로 실행되기 때문에, 특정 로그온된 유저와 연계되어 어떤 작업을 진행
        하려면 까다로운 절차를 거쳐야 한다. 보통 이런걸 무시하기 위해서 보통 SOCKET를 이용해서 통신하기도 하는데
        하여튼 서버계열에서 로그온된 여러 유저와 개별적으로 분리된 통신을 하는건 상당히 피곤한 일이다.

3. 디버깅이 난해하다.
   -> 서비스로 실행되기 때문에 기본적으로 개발툴에서 직접 디버깅하는 것은 않된다.
        이를 위하여, 프로그램에 인자를 주어 debug 모드로 구동될 경우 서비스 핸들러를 거치지 않고 바로
        서비스 메인 함수를 호출하는 방식으로 콘솔 프로그램 처럼 디버깅이 가능하도록 할 수 는 있으나...
        서비스의 동작 특성에 맞도록 완전하게 디버깅 하는 것은 불가능하다.

        그래서, 일반적으로 디버깅을 위해서 별도의 로깅 장치를 만들어 놓고 열심히 써서 디버깅 하기도 한다.

4. 파일 처리나 MMF의 생성 등과 같은 작업에 제약을 받는다.
  -> 서비스에서 파일을 생성하면, Users 그룹은 읽기와 수행 권한만
      기본적으로 제공된다. 
      즉, 수정이나 쓰기 권한은 배재된다.

  -> MMF (Memory Mapped File) 의 경우 서비스에서 생성하게되면
      User 권한에서는 읽기 전용으로 여는 것조차 불가능하다.
     
      CreateFileMapping() 함수의 2번 째 인자..
      LPSECURITY_ATTRIBUTES lpAttributes에 적당한 권한을 채워서
      생성해 주어야 한다. 

      그냥 생성했을 경우, Users 그룹에서 해당 MMF를 오픈하게 되면
      ACCESS DENIED 에러를 만나게 될것이다.

      클라이언트 OS나, 일반 서버용 프로그램들은 거의 Administrators
      그룹에 속한 형태로 실행되기는 하지만, 모두 그러한 것은 아니니까


 


서비스에는 위에서 설명한 장/단점을 포함하여 조금더 많은 제약사항을 가지며, 각종 상태 표시의 까다로움, 난해한 에러 처리... 등등
간단하게 작성하면 이전의 샘플 코드 처럼 아주 쉽게 작성할 수 있고..  세밀하게 처리하려면 윈도우라고 하는 OS 에 대하여 꽤나 많은
지식을 필요로 하게 된다.

-------------------------------------------------------------------------------------------------------------------------

오랜시간 하나의 커다란 프로젝트를 진행하다 보니, 많이 지치고 너무도 구태의연해지는 내 모습에 새로운 활기를 불어 넣어 보고 싶어
강좌라는 이름으로 새롭게 각오를 다지고자 이 글을 쓰게 되었습니다.

머 특출나게 할줄 아는것도 없고, 한가지 분야에 대해 내세울만한 것도 없고.. 이것 저것 생각해 보다 선택한게.. ^^;;;
짬짬히 시간 만들어서 하나씩 진행해 보도록 하겠습니다.






메모리 맵드 파일은 서버가 죽었을 경우, 클라이언트가 이를 감지할 방법이 없었지만
해당 방식은 이를 바로 감지할 수 있는 장점을 제공해준다.

이런 저런 테클을 받아(^^) 여러가지 단위 테스트를 진행해본 결과,
해당 방식이 MMF보다 우월하다고 말하기 힘든 결과가 나왔다..
쓰기는 엇비슷하거나 1-5%정도 향상되었다고 말할 수 있지만, 읽기 속도는 MMF가 수배에서 수십배 빠르게 나타났다.
그냥 이런 방법도 있구나, 생각하고 읽어보자..



자, 그럼 서버가 해야할일은 무엇일까?

MYSTRUCT* pStruct = new MYSTRUCT;
int pid = _getpid(); // 서버PID
int addr = (int)pStruct; // 서버 메모리 주소

준비는 끝났다.. 헉 @.@?

다음으로 클라이언트 프로세스에게 pid 와 addr을 전달하기만 하면된다.
프로세스를 띄울 때 인자로 전달하던, 파일에 쓰던, 메시지를 던지던.. 그건 마음대로 하면 된다.

그렇다면 클라이언트가 해야할일은 멀까?

HANDLE m_hProcess = OpenProcess(PROCESS_VM_READ, FALSE, 서버PID);
ReadProcessMemory(m_hProcess, (LPCVOID)서버메모리주소, &pStruct, sizeof(MYSTRUCT), &ReadBytes);
이걸로 끝이다.

장점1. 서버가 죽으면 ReadProcessMemory 가 실패하기 때문에 바로 알 수 있다.
장점2. 사용이 간편하다? -_-;;;
프로세스간에 데이터를 공유할 때..
많이 사용되는 방법중에 하나가 공유 메모리(Memory Mapped File)를 사용하는 것이다.
일단 서버에서는 읽고/쓰기 모드로 열어서 내부에서 사용하고..
다른 한쪽 혹은 여러클라이언트 에서는 읽기 전용 모드로 열어서 사용하는 가장 간편한 방법을 사용해보자.
(사용하는 방식은 메모리를 액세스하는것과 동일하다..) 



위 그림처럼, 하나의 메모리 공간을 양쪽에서 액세스 할 수 있도록 제공해주는 점이 탁월한 장점이고,
또한 멀티 쓰레드? 멀티 코어? 와 상관없이...
서버는 읽기/쓰기 규칙을 지키고, 클라이언트는 읽기만 한다면 절대 공유 액세스 위반을 걱정할 필요가 없다.

우선 사용되는 API를 살펴보자. 아래에 해당하는 5개의 API만 사용해도 되며,
CreateFileMapping 함수의 첫번째(hFile) 인자와, 여섯번째(lpName) 인자만 주의하면 특별하게
인자에 신경쓰지 않아도 된다.

HANDLE WINAPI CreateFileMapping(
 __in          HANDLE hFile,
 __in          LPSECURITY_ATTRIBUTES lpAttributes,
 __in          DWORD flProtect,
 __in          DWORD dwMaximumSizeHigh,
 __in          DWORD dwMaximumSizeLow,
 __in          LPCTSTR lpName
);

※ HANDLE hFile
사용자가 이미 만들어 놓은 파일 핸들이나, INVALID_HANDLE_VALUE 를 전달할 수 있으며, INVALID_HANDLE_VALUE 이
전달되었을 경우는 시스템의 페이지 파일을 할당
해서 사용한다.
그러므로, 특별하게 큰 파일을 사용하는 경우가 아니라면 INVALID_HANDLE_VALUE 만으로도 충분하다.

※ LPCTSTR lpName
해당 공유 메모리를 확인하기 위한 고유한 이름을 나타낸다. 일반적인 클라이언트 OS 에서는 별 문제가 없겠지만
서버 OS에 Terminal Service 가 구동중이라면 Prefix 에 따라 그 특성이 달라진다.
모든 세션에서 접근할 수 있으려면 Global\이름 과 같이 Global\ 라는 Prefix를 붙혀주어야 한다.

HANDLE WINAPI OpenFileMapping(
 __in          DWORD dwDesiredAccess,
 __in          BOOL bInheritHandle,
 __in          LPCTSTR lpName
);

LPVOID WINAPI MapViewOfFile(
 __in          HANDLE hFileMappingObject,
 __in          DWORD dwDesiredAccess,
 __in          DWORD dwFileOffsetHigh,
 __in          DWORD dwFileOffsetLow,
 __in          SIZE_T dwNumberOfBytesToMap
);

BOOL WINAPI UnmapViewOfFile(
 __in          LPCVOID lpBaseAddress
);

BOOL WINAPI CloseHandle(
 __in          HANDLE hObject
);




※ 참고로, 서버프로그램이 종료되었을 경우, 클라이언트에서 메모리 맵드 파일의 현황만 가지고는 알수 없다는 단점이 있다.
즉, 서버가 죽어도 클라이언트가 살아서 데이터를 읽는데는 전혀 문제가 없기 때문에.. 이러한 부분은 별도로 확인해 주어야한다.

윈도우의 터미널 서버를 이용하는 경우, 해당 세션별 정보나 프로세스 정보
그러한 목록을 가지고 먼가 작업을 해야 하는경우.. 사용할 수 있는 간단한 래핑클래스 입니다.

class CWTSSession

클래스의 경우는 해당 세션  ID를 알고 있을 경우 세션의 디테일한 정보를 읽어 오는 클래스이죠.
클래스의 내부 멤버를 보시면 어떤 정보를 열거하는지 바로 확인이 가능합니다.

class CWTSBase
위 클래스는 어떤 세션이 존재하는지, 어떤 서버가 존재하는지, 프로세스 목록은 어떤게 있는지
등등을 열거해주는 기능을 제공해줍니다.

[-] Collapse

#include <WtsApi32.h>

class CWTSSession
{
public:
    CWTSSession();
    CWTSSession(HANDLE hWTS, DWORD SessionID);
    ~CWTSSession();

    CHAR    IPAddress[16];
    USHORT  HorizontalResolution;
    USHORT  VerticalResolution;
    USHORT  ColorDepth;
    USHORT  ProtocolType;
    LPTSTR  ClientName;
    LPTSTR  DomainName;
    LPTSTR  UserName;
    LPTSTR  WinStation;
protected:
    void Query(HANDLE hWTS, DWORD SessionID);
    void Init();
    void Free();
};

class CWTSBase
{
public:
    CWTSBase();
    virtual ~CWTSBase();

    BOOL Open(LPTSTR servername);
    void Close();
    void Free(void* data);
    HANDLE GetHandle();

    BOOL EnumSessions(PWTS_SESSION_INFO& info, LPDWORD count);
    BOOL EnumServers(LPTSTR domainname, PWTS_SERVER_INFO& info, LPDWORD count);
    BOOL EnumProcesses(PWTS_PROCESS_INFO& info, LPDWORD count);

protected:
    HANDLE m_hWTS;
}; 

세션 아이디를 알고 있을 경우, 해당 세션의 세부정보를 읽어오는 샘플
CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, 세션아이디);

모든 세션에 걸친 프로세스 목록을 읽어오는 샘플
CWTSBase wts;
DWORD count;
PWTS_PROCESS_INFO info;
wts.EnumProcesses(info, &count);

어떤 세션이 존재하는지, 세션 목록 정보를 읽어오는 샘플
CWTSBase wts;
DWORD count;
PWTS_SESSION_INFO info;
wts.EnumProcesses(info, &count);

현재 나의 프로그램이 어떤 세션에 속해있는지를 판단하는 샘플
DWORD PID = GetCurrentProcessId();
DWORD SID = 9999999;
if(ProcessIdToSessionId(PID, &SID))
{
    CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, SID);
    printf("%s\n", ws.IPAddress);
    printf("%s\n", ws.ClientName);
    printf("%s\n", ws.DomainName);
    printf("%s\n", ws.UserName);
    printf("%s\n", ws.WinStation);
}

지정된 세션에 동일한 프로세스가 있을 경우, 해당 세션의 특정 프로세스에 메시지 보내기
오직 하나의 프로그램이 동작하도록 할경우를 제외한 하나의 세션당 하나의 프로그래만
구동되도록 작성해야 할경우.. 사용한 샘플입니다.
BOOL CALLBACK CROWBACK_WNDENUMPROC(HWND hWnd, LPARAM lp)
{
    DWORD target = lp;
    DWORD pid = 9999999;
    GetWindowThreadProcessId(hWnd, &pid);
    if(pid == target)
    {
        char caption[512] = {0};
        ::GetWindowText(hWnd, caption, 512);
        if(strstr(caption, "Alert Management"))
        {
            ::PostMessage(hWnd, 0x9999, 0, 0);
            return FALSE;
        }
    }

    return TRUE;
}

DWORD PID = GetCurrentProcessId();
DWORD SID = 9999999;

if(ProcessIdToSessionId(PID, &SID))
{
    AllowSetForegroundWindow(PID);

    char Module[512] = {0};
    ::GetModuleFileName(NULL, Module, 512);
    CString name = Module;
    name = name.Right(name.GetLength()-name.ReverseFind('\\')-1);
    CWTSBase wts;

    DWORD count;
    PWTS_PROCESS_INFO info2;
    wts.EnumProcesses(info2, &count);
    for(UINT i=0; i<count; i++)
    {
        if(info2[i].SessionId == SID && info2[i].ProcessId != PID)
        {
            if(name.CompareNoCase(info2[i].pProcessName) == 0)
            {
                ::EnumWindows(CROWBACK_WNDENUMPROC, info2[i].ProcessId);
                return TRUE;
            }
        }
    }
}

네트워크라는 개념이 생성되면서, 여러가지 계층간 통신이 생겨났다.
일반적으로 어플리케이션 개발자들이 사용하는 통신은 최상위 계층을 다루게 되며..
주로 소켓통신, 혹은 파이프와 같은 것을 이용하여 로컬 프로세스간 혹은 원격지 프로세스간에
통신을 하게된다.

여기서는 메시지를 주고받은 통신에서, 데이터를 공유하는 것을 통털어... 알고 있는것만 정리한다. -0-;;;
모르는 것을 어떻게 하나 ^^;;; (누가좀..)

이 계통에 들어선지 시간이 좀 흘럿지만, 참으로 다양한 방식이 존재하고..
공부해야할 것도 너무 많다.. -0-;;;

1. SOCKET - 통신
   통신 프로그램을 짜면 가장 먼저 접하는 것이 소켓이다. 하위호환 덕택에 아직도 명맥을 유지하는 BSD 모델과
   winsock으로 불리우는 2가지 모델을 윈도우에서 제공해준다.
   서버/클라이언트 모델로 제공되며, TCP/UDP 이렇게 상위 2가지를 주로 사용한다.
   (이건 머, 어떻게 심플하게 설명할 방법이 없다... 책사서 봐야지 ㅋ~)

2. ATOM - 공유
   시스템에서 제공하는 테이블을 이용하여, 문자열 데이터를 공유할 수 있도록 제공해준다.
   ATOM 이라고 불리우는 2바이트 숫자를 키 값으로 이용하여 255바이트까지의 문자열을 해당 시스템의 테이블에
   저장하고, 가져다 쓸 수 있다.  사용방법이 너무 간단하고, 윈도우 95까지도 지원한다.
   함수도 아래의 6개가 다이고, 그나마 로컬과 글로벌을 구분하기 위한 정도뿐이다. 

   9번 DDE 섹션을 보면, ATOM과 SendMessage 를 이용하여, 어떻게 데이터 통신을 할 수 있는지
   다양함 샘플을 보여준다. 

 
3. PIPE - 통신
   로컬머신, 혹은 로컬 네트웍 그룹내에서 사용이 가능한 통신 방식이다. 소켓보다 조금 들 복잡하지만 소켓처럼
   정밀하게 제어하기는 조금 쉽지 않다.
   이름 있는 파이프(양방향통신)과, 이름 없는 파이프(단방향통신)을 지원한다.

4. Mailslot - 통신
   메시지 큐잉을 지원하고, 비연결성이기 때문에 브로드 캐스팅을 지원한다. 사용하기가 간단하다.
   UDP와 같이 전송 보장을 지원하지 않고, 데이터크기가 400 바이트 이내라는 제한이 있다.



5. SendMessage, PostMessage - 통신
   윈도우가 존재한다는 범위내에서만 사용가능한 통신이며, WPARAM, LPARAM 에 해당하는 두개의 정수형 타입 데이터의
   전송이 가능하다. 큐잉을 보장해주지만, 윈도가 있어야한다는 제약이 있다.

6. WM_COPYDATA - 통신
   5번과 마찬가지로 윈도우의 메시지를 이용한 통신 방식이다. 매우 사용하기 쉽다. 윈도우즈 메시지 기반이기 때문에,
   메시지 펌프 처리를 해줘야한다. 메시지 통신의 빈도가 낮고, 빨리 구현하는 것이 우선이라면 이 방법이 좋을 수 있다.
   다만 메시지를 받는 쪽이 윈도우 핸들이 가지고 있어야 한다.



7. Memory Mapped File - 공유
   Memory Mapped File도 프로세스 간 통신에 이용할 수 있다. 다른 IPC들이 Memory Mapped File를 이용해서 구현되므로,
   속도가 중요하다면, Memory Mapped File이 가장 나은 선택이다.
   대량의 데이터를 다룰 때 주로 사용되지만, 매핑된 메모리를 블럭단위로 사용하게 되므로, 상호 액세스에 대한 규정을
   잘 만들어 관리해야 한다.

8. DLL Shared Sections - 공유
   DLL을 만들고 공유 영역을 설정하여 데이터를 공유하는 방식이다. 일단 DLL을 작성해야 하기 때문에 간단한 통신을 위하여
   이러한 것을 만드는 것은 조금 비효율 적이라고도 할 수 있다.
   또한 윈도우에 같은 dll 이 있다면, 경로가 서로 달라 각각의 dll을 로드하게 되면 이러한 것도 무용지물이다.

9. DDE, NetDDE - 통신
   자료를 좀 찾아보니, DDE는 SendMessage 와 ATOM을 조합하여, 시스템 ATOM 테이블을 이용한 데이터 통신이란다.
   데이터가 있을 경우는 공유메모리를 생성하여, 해당 메모리 주소를 전달하는 방식이고, 이러한 조합을 몇가지 내부적인
   메시지로 감싸서 사용자가 쉽게 사용할 수 있도록 해결해준다.



Note   If your application uses NULL atoms, you need not use the GlobalAddAtom and GlobalDeleteAtom functions. In this example, the client application creates two global atoms containing the name of the server and the name of the topic, respectively.

The client application sends a WM_DDE_INITIATE message with these two atoms in the lParam parameter of the message. In the call to the SendMessage function, the special window handle –1 directs the system to send this message to all other active applications. SendMessage does not return to the client application until all applications that receive the message have, in turn, returned control to the system. This means that all WM_DDE_ACK messages sent in reply by the server applications are guaranteed to have been processed by the client by the time the SendMessage call has returned.

After SendMessage returns, the client application deletes the global atoms.

Server applications respond according to the logic illustrated in the following diagram.

Server application response logic

To acknowledge one or more topics, the server must create atoms for each conversation (requiring duplicate application-name atoms if there are multiple topics) and send a WM_DDE_ACK message for each conversation, as illustrated in the following example.

When a server responds with a WM_DDE_ACK message, the client application should save a handle to the server window. The client receiving the handle as the wParam parameter of the WM_DDE_ACK message then sends all subsequent DDE messages to the server window this handle identifies.

If your client application uses a NULL atom for the application name or topic name, expect the application to receive acknowledgments from more than one server application. Multiple acknowledgements can also come from multiple instances of a DDE server, even if your client application does not NULL use atoms. A server should always use a unique window for each conversation. The window procedure in the client application can use a handle to the server window (provided as the lParam parameter of WM_DDE_INITIATE) to track multiple conversations. This allows a single client window to process several conversations without needing to terminate and reconnect with a new client window for each conversation.

Transferring a Single Item

Once a DDE conversation has been established, the client can either retrieve the value of a data item from the server by issuing the WM_DDE_REQUEST message, or submit a data-item value to the server by issuing WM_DDE_POKE.

Retrieving an Item from the Server

To retrieve an item from the server, the client sends the server a WM_DDE_REQUEST message specifying the item and format to retrieve, as shown in the following example.

In this example, the client specifies the clipboard format CF_TEXT as the preferred format for the requested data item.

The receiver (server) of the WM_DDE_REQUEST message typically must delete the item atom, but if the PostMessage call fails, the client must delete the atom.

If the server has access to the requested item and can render it in the requested format, the server copies the item value as a shared memory object and sends the client a WM_DDE_DATA message, as illustrated in the following example.

In this example, the server application allocates a memory object to contain the data item. The data object is initialized as a DDEDATA structure.

The server application then sets the cfFormat member of the structure to CF_TEXT to inform the client application that the data is in text format. The client responds by copying the value of the requested data into the Value member of the DDEDATA structure. After the server has filled the data object, the server unlocks the data and creates a global atom containing the name of the data item.

Finally, the server issues the WM_DDE_DATA message by calling PostMessage. The handle to the data object and the atom containing the item name are packed into the lParam parameter of the message by the PackDDElParam function.

If PostMessage fails, the server must use the FreeDDElParam function to free the packed lParam parameter. The server must also free the packed lParam parameter for the WM_DDE_REQUEST message it received.

If the server cannot satisfy the request, it sends a negative WM_DDE_ACK message to the client, as shown in the following example.

// Negative acknowledgment. 
 
PostMessage(hwndClientDDE, 
    WM_DDE_ACK, 
    (WPARAM) hwndServerDDE, 
    PackDDElParam(WM_DDE_ACK, 0, atomItem));

Upon receiving a WM_DDE_DATA message, the client processes the data-item value as appropriate. Then, if the fAckReq member pointed to in the WM_DDE_DATA message is 1, the client must send the server a positive WM_DDE_ACK message, as shown in the following example.

In this example, the client examines the format of the data. If the format is not CF_TEXT (or if the client cannot lock the memory for the data), the client sends a negative WM_DDE_ACK message to indicate that it cannot process the data. If the client cannot lock a data handle because the handle contains the fAckReq member, the client should not send a negative WM_DDE_ACK message. Instead, the client should terminate the conversation.

If a client sends a negative acknowledgment in response to a WM_DDE_DATA message, the server is responsible for freeing the memory (but not the lParam parameter) referenced by the WM_DDE_DATA message associated with the negative acknowledgment.

If it can process the data, the client examines the fAckReq member of the DDEDATA structure to determine whether the server requested that it be informed that the client received and processed the data successfully. If the server did request this information, the client sends the server a positive WM_DDE_ACK message.

Because unlocking data invalidates the pointer to the data, the client saves the value of the fRelease member before unlocking the data object. After saving the value, the client then examines it to determine whether the server application requested the client to free the memory containing the data; the client acts accordingly.

Upon receiving a negative WM_DDE_ACK message, the client can ask for the same item value again, specifying a different clipboard format. Typically, a client will first ask for the most complex format it can support, then step down if necessary through progressively simpler formats until it finds one the server can provide.

If the server supports the Formats item of the system topic, the client can determine once what clipboard formats the server supports, instead of determining them each time the client requests an item.

Submitting an Item to the Server

The client may send an item value to the server by using the WM_DDE_POKE message. The client renders the item to be sent and sends the WM_DDE_POKE message, as illustrated in the following example.


Note  Sending data by using a WM_DDE_POKE message is essentially the same as sending it by using WM_DDE_DATA, except that WM_DDE_POKE is sent from the client to the server.

If the server is able to accept the data-item value in the format rendered by the client, the server processes the item value as appropriate and sends the client a positive WM_DDE_ACK message. If it is unable to process the item value, because of its format or for other reasons, the server sends the client a negative WM_DDE_ACK message.

In this example, the server calls GlobalGetAtomName to retrieve the name of the item the client sent. The server then determines whether it supports the item and whether the item is rendered in the correct format (that is, CF_TEXT). If the item is not supported and not rendered in the correct format, or if the server cannot lock the memory for the data, the server sends a negative acknowledgment back to the client application. Note that in this case, sending a negative acknowledgment is correct because WM_DDE_POKE messages are always assumed to have the fAckReq member set. The server should ignore the member.

If a server sends a negative acknowledgment in response to a WM_DDE_POKE message, the client is responsible for freeing the memory (but not the lParam parameter) referenced by the WM_DDE_POKE message associated with the negative acknowledgment.

Establishing a Permanent Data Link

A client application can use DDE to establish a link to an item in a server application. After such a link is established, the server sends periodic updates of the linked item to the client, typically, whenever the value of the item changes. Thus, a permanent data stream is established between the two applications; this data stream remains in place until it is explicitly disconnected.

Initiating a Data Link

The client initiates a data link by posting a WM_DDE_ADVISE message, as shown in the following example.

In this example, the client application sets the fDeferUpd flag of the WM_DDE_ADVISE message to FALSE. This directs the server application to send the data to the client whenever the data changes.

If the server is unable to service the WM_DDE_ADVISE request, it sends the client a negative WM_DDE_ACK message. But if the server has access to the item and can render it in the requested format, the server notes the new link (recalling the flags specified in the hOptions parameter) and sends the client a positive WM_DDE_ACK message. From then on, until the client issues a matching WM_DDE_UNADVISE message, the server sends the new data to the client every time the value of the item changes in the server.

The WM_DDE_ADVISE message establishes the format of the data to be exchanged during the link. If the client attempts to establish another link with the same item but is using a different data format, the server can choose to reject the second data format or attempt to support it. If a warm link has been established for any data item, the server can support only one data format at a time. This is because the WM_DDE_DATA message for a warm link has a NULL data handle, which otherwise contains the format information. Thus, a server must reject all warm links for an item already linked, and must reject all links for an item that has warm links. Another interpretation may be that the server changes the format and the hot or warm state of a link when a second link is requested for the same data item.

In general, client applications should not attempt to establish more than one link at a time for a data item.

Initiating a Data Link with the Paste Link Command

Applications that support hot or warm data links typically support a registered clipboard format named Link. When associated with the application's Copy and Paste Link commands, this clipboard format enables the user to establish DDE conversations between applications simply by copying a data item in the server application and pasting it into the client application.

A server application supports the Link clipboard format by placing in the clipboard a string containing the application, topic, and item names when the user chooses the Copy command from the Edit menu. Following is the standard Link format:

application\0topic\0item\0\0

A single null character separates the names, and two null characters terminate the entire string.

Both the client and server applications must register the Link clipboard format, as shown:

cfLink = RegisterClipboardFormat("Link");

A client application supports the Link clipboard format by means of a Paste Link command on its Edit menu. When the user chooses this command, the client application parses the application, topic, and item names from the Link-format clipboard data. Using these names, the client application initiates a conversation for the application and topic, if such a conversation does not already exist. The client application then sends a WM_DDE_ADVISE message to the server application, specifying the item name contained in the Link-format clipboard data.

Following is an example of a client application's response when the user chooses the Paste Link command.

In this example, the client application opens the clipboard and determines whether it contains data in the Link format (that is, cfLink) it had previously registered. If not, or if it cannot lock the data in the clipboard, the client returns.

After the client application retrieves a pointer to the clipboard data, it parses the data to extract the application, topic, and item names.

The client application determines whether a conversation on the topic already exists between it and the server application. If a conversation does exist, the client checks whether a link already exists for the data item. If such a link exists, the client displays a message box to the user; otherwise, it calls its own SendAdvise function to send a WM_DDE_ADVISE message to the server for the item.

If a conversation on the topic does not already exist between the client and the server, the client first calls its own SendInitiate function to broadcast the WM_DDE_INITIATE message to request a conversation and, second, calls its own FindServerGivenAppTopic function to establish the conversation with the window that responds on behalf of the server application. After the conversation has begun, the client application calls SendAdvise to request the link.

Notifying the Client that Data Has Changed

When the client establishes a link by using the WM_DDE_ADVISE message, with the fDeferUpd member not set (that is, equal to zero) in the DDEDATA structure, the client has requested the server send the data item each time the item's value changes. In such cases, the server renders the new value of the data item in the previously specified format and sends the client a WM_DDE_DATA message, as shown in the following example.

In this example, the client processes the item value as appropriate. If the fAckReq flag for the item is set, the client sends the server a positive WM_DDE_ACK message.

When the client establishes the link, with the fDeferUpd member set (that is, equal to 1), the client has requested that only a notification, not the data itself, be sent each time the data changes. In such cases, when the item value changes, the server does not render the value but simply sends the client a WM_DDE_DATA message with a null data handle, as illustrated in the following example.

As necessary, the client can request the latest value of the data item by issuing a normal WM_DDE_REQUEST message, or it can simply ignore the notice from the server that the data has changed. In either case, if fAckReq is equal to 1, the client is expected to send a positive WM_DDE_ACK message to the server.

Terminating a Data Link

If the client requests that a specific data link be terminated, the client sends the server a WM_DDE_UNADVISE message, as shown in the following example.

The server checks whether the client currently has a link to the specific item in this conversation. If a link exists, the server sends the client a positive WM_DDE_ACK message; the server is then no longer required to send updates about the item. If no link exists, the server sends the client a negative WM_DDE_ACK message.

The WM_DDE_UNADVISE message specifies a data format. A format of zero informs the server to stop all links for the specified item, even if several hot links are established and each uses a different format.

To terminate all links for a conversation, the client application sends the server a WM_DDE_UNADVISE message with a null item atom. The server determines whether the conversation has at least one link currently established. If a link exists, the server sends the client a positive WM_DDE_ACK message; the server then no longer has to send any updates in the conversation. If no link exists, the server sends the client a negative WM_DDE_ACK message.

Carrying Out Commands in a Server Application

Applications can use the WM_DDE_EXECUTE message to cause a certain command or series of commands to be carried out in another application. To do this, the client sends the server a WM_DDE_EXECUTE message containing a handle to a command string, as shown in the following example.

In this example, the server attempts to carry out the specified command string. If it succeeds, the server sends the client a positive WM_DDE_ACK message; otherwise, it sends a negative WM_DDE_ACK message. This WM_DDE_ACK message reuses the hCommand handle passed in the original WM_DDE_EXECUTE message.

If the client's command execution string requests that the server terminate, the server should respond by sending a positive WM_DDE_ACK message and then post a WM_DDE_TERMINATE message before terminating. All other commands sent with a WM_DDE_EXECUTE message should be executed synchronously; that is, the server should send a WM_DDE_ACK message only after successfully completing the command.

Terminating a Conversation

Either the client or the server can issue a WM_DDE_TERMINATE message to terminate a conversation at any time. Similarly, both the client and server applications should be prepared to receive this message at any time. An application must terminate all of its conversations before shutting down.

In the following example, the application terminating the conversation posts a WM_DDE_TERMINATE message.

PostMessage(hwndServerDDE, WM_DDE_TERMINATE, 
    (WPARAM) hwndClientDDE, 0);

This informs the other application that the sending application will send no further messages and the recipient can close its window. The recipient is expected in all cases to respond promptly by sending a WM_DDE_TERMINATE message. The recipient must not send a negative, busy, or positive WM_DDE_ACK message.

After an application has sent the WM_DDE_TERMINATE message to the partner in a DDE conversation, it must not respond to messages from that partner, since the partner might have destroyed the window to which the response would be sent.

If an application receives a DDE message other than WM_DDE_TERMINATE after it has posted WM_DDE_TERMINATE, it should free all objects associated with the received messages except the data handles for WM_DDE_DATA or WM_DDE_POKE messages that do not have the fRelease member set.

When an application is about to terminate, it should end all active DDE conversations before completing processing of the WM_DESTROY message. However, if an application does not end its active DDE conversations, the system will terminate any DDE conversations associated with a window when the window is destroyed. The following example shows how a server application terminates all DDE conversations.





_M#]

평소 별로 관심을 가져보지 못하던 영역의 자료가 올라와서 한번 살펴보았습니다.
웹쪽과는 인연이 없다보니 잘 살펴보지 않는 영역의 자룐데, 잘 정리된걸 보니 이뿌네요 ㅜㅜ;
취미로 해보기엔 좀 벅차고, 일로 떨어지면 헤딩이라도 해볼텐데.. ^^;;;

 익스플로러 보안설정 변경  | ActiveX/COM 2008-12-22 오후 5:21:55
최재권 (houseman)  최재권님께 메시지 보내기최재권님을 내 주소록에 추가합니다.최재권님의 개인게시판 가기  / 등록자 IP: xxx.xxx.244.109 번호: 8208   / 평점:  (9.0)  / 읽음:187

인터넷 익스플로러의 보안 설정 변경 관련.

 

1. 신뢰할수 있는 사이트 추가.

2. 구역별 보안등급 변경.

3. 임의의 선택한 보안 수준 변경.

 

 

1. 신뢰할수 있는 사이트 추가. &  2. 구역별 보안등급 변경.

 

 HRESULT hr;
 IInternetSecurityManager * pSecurityMgr;
 IInternetZoneManager * pZoneMgr;
 DWORD dwEnum, dwZoneCount;
 DWORD dwZone;
 ZONEATTRIBUTES zoneAttr;
 int nLevel = 2;
 
 CoInitialize(NULL);


 // 먼저 IinternetSecurityManager 인터페이스를 초기화한다.
 hr = CoCreateInstance(CLSID_InternetSecurityManager, NULL, CLSCTX_ALL, IID_IInternetSecurityManager, (void**)&pSecurityMgr);
 if (hr != S_OK)
 {
       CoUninitialize();
       return;
 }


 // 다음으로 IinternetZoneManager 인터페이스를 초기화한다.
 hr = CoCreateInstance(CLSID_InternetZoneManager, NULL, CLSCTX_ALL, IID_IInternetZoneManager, (void**)&pZoneMgr);
 if (hr != S_OK)
 {
       CoUninitialize();
       return;
 }
 dwEnum = 0;

 

 // Zone Enumerator를 초기화한다.
 pZoneMgr->CreateZoneEnumerator(&dwEnum, &dwZoneCount, 0);

 

 // 2번 존에 대한 정보를 얻는다. 2번 존이 신뢰할 수 있는 영역 존이다.
 pZoneMgr->GetZoneAt( dwEnum, nLevel, &dwZone);
 pZoneMgr->GetZoneAttributes(dwZone, &zoneAttr);

 

 // 지금 예제는 보통의 HTTP 사이트를 등록하는 것이기 때문에 HTTPS만을 등록해야 하는 제약 조건을 없앤다.
 if (zoneAttr.dwFlags &ZAFLAGS_REQUIRE_VERIFICATION)
 {
       // 서버 확인 부분을 뺀다.
       zoneAttr.dwFlags = (zoneAttr.dwFlags & ~(ZAFLAGS_REQUIRE_VERIFICATION));
 }

 

 // 현재 보안 설정이 낮음이 아니면 낮음으로 설정한다.
 if (zoneAttr.dwTemplateCurrentLevel != 0x10000)
 {
       zoneAttr.dwTemplateCurrentLevel = 0x10000;
       zoneAttr.dwTemplateMinLevel = 0x10000;
 }
 pZoneMgr->SetZoneAttributes(dwZone, &zoneAttr);
 


 // 등록 사이트
 CString strDomain = "http://www.devpia.com";

 

 // 이 값을 유니코드로 변경한다.
 BSTR bDomain = strDomain.AllocSysString();
 


 // IInternetSecurity 인터페이스의 SetZoneMapping 함수를 이용해 등록한다.
 hr = pSecurityMgr->SetZoneMapping(nLevel, bDomain, SZM_CREATE);


 if (hr == E_ACCESSDENIED)  // 존 설정이 서버 확인을 필요로 하는 곳이면
 {
       ::MessageBox(NULL, "등록하려는 영역이 서버 확인이 필요한 것으로 설정되어 있습니다.", "영역 추가 에러", MB_OK);
 }
 else if (hr == ERROR_FILE_EXISTS) // 이미 다른 영역으로 등록된 것이면
 {
       ::MessageBox(NULL, "등록하려는 주소가 이미 다른 영역으로 설정되어 있습니다.", "영역 추가 에러", MB_OK);
 }
 


 ::SysFreeString(bDomain);
 


 if (dwEnum != 0)
       pZoneMgr->DestroyZoneEnumerator(dwEnum);
 


 pSecurityMgr->Release();
 pZoneMgr->Release();
 CoUninitialize();

 

--------------------------------------------------------------------------------

위의 코드는 한기용(Keeyong@wisenut.com)님의 코드를 그대로 인용 하였음.

 

 

 

1,2 의 내용만으로 거의 모든 문제가 해결되리라 생각된다.

그러나 몇몇 보안 관련 부분은 보안 등급을 최소로 해도 문제가 해결되지 않는 경우가 있다.

이 경우엔 직접 원하는 보안관련 설정을 원하는 수준으로 변경 해야만 한다.

대표적인 예가 "안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트"이다.

때때로 사소한 ActiveX들이 필요한 경우가 있는데 이런것들을 매번 서명을 받기도 번거로운 일이다.

이러한 문제를 해결하기위해서 보안수준을 변경할 필요가 있다.

 

이 항목을 직접 변경하는 코드를 살펴보자.

 

 

 

3. 임의의 선택한 보안 수준 변경.

CRegKey reg;     // #include <Atlbase.h>

if(reg.Open(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones\\2") == ERROR_SUCCESS)
{
     reg.SetValue((DWORD)0, "1201");  // 0:허용  1:확인  3:사용안함
     reg.Close();
}

if(reg.Open(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones\\2") == ERROR_SUCCESS)
{
     reg.SetValue((DWORD)0, "1201");  
     reg.Close();
}

 

----------------------------------------------------------------------

 

위의 예제는 [신뢰할수 있는 사이트>안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트 => 사용] 으로 레지스트리를 직접 변경하는 코드이다.

 

그러나, 한기용(Keeyong@wisenut.com)님의 글에서는 레지스트리를 직접 편집하는것은 좋지 않은 방법이라고 설명하고 있다. 그 이유를 다음과 같이 설명하고 있다.

---------------------------------------------------------------

그 이유는 다음 버전의 IE에서도 레지스트리의 이 항목들을 URL 보안 관련

정보를 저장하는데 사용한다는 보장이 없기 때문이다. 따라서 URL 보안 영역

관련 인터페이스를 사용하는 것이 좋다.

---------------------------------------------------------------

 

따라서, 방법의 선택은 독자의 몫이다.

 

 

※ Zones > 2 는 신뢰할수 있는 사이트를 나타낸다.

  • Zone 0 (My computer 영역을 의미)
  • Zone 1 (Local intranet 영역을 의미)
  • Zone 2 (trusted sites 영역을 의미)
  • Zone 3 (Internet 영역을 의미)
  • Zone 4 (Restricted sites 영역을 의미)

     

    ※ 똑같은 내용이 hkey_local_machine과 hkey_current_user 두곳에 존재하는데 필자는 그 차이를 모르겠다. 따라서 두곳 모두 바꾸어 준다. 그러나 테스트 결과 hkey_current_user만 수정해도 무방하였다.

     

    ※ SetValue의 첫번째 파라미터는 값을 나타낸다.

         0:허용  1:확인  3:사용안함

     

    ※ SetValue의 두번째 파라미터는 값이름을 나타낸다. (1201 : 안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트)

     

    참조

    엔트리 정의 기본값 권장값
    1001 서명된 ActiveX 컨트롤 다운로드 0 3
    1004 서명 안 된 ActiveX 컨트롤 다운로드 0 3
    1200 ActiveX 컨트롤과 플러그인 실행 0 1
    1201 안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트 1 3
    1400 액티브 스크립팅 허용 0 1
    1402 자바 애플릿 스크립트 0 1
    1406 도메인 간의 데이터 소스 액세스 0 3
    1407 스크립트를 통한 붙여넣기 작업 허용 0 3
    1601 암호화되지 않은 폼 데이터 제출 0 1
    1604 폰트 다운로드 0 3
    1606 사용자 데이터 유지 0 3
    1607 서로 다른 도메인 간의 하위프레임 이동 0 3
    1802 파일의 드래그 앤 드롭이나 복사 및 붙여넣기 0 1
    1803 파일 다운로드 0 3
    1804 IFRAME에 프로그램과 파일 시작 0 3
    1E05 소프트웨어 채널 사용권한 30000 20000

     

    MSDN참고: http://technet.microsoft.com/ko-kr/library/cc700750.aspx

     

    ※ 보안관련 수정 내용은 새로 시작되는 익스플로러부터 적용된다.

    ----------------------------------------------------------------------------------------------

    최재권 jk9053@nate.com


  • + Recent posts