서비스 프로그램을 작성하다 보면, 디버깅을 할 때 현재 어플리케이션으로
구동중인지 서비스로 구동중인지를 내부적으로 구분할 수 있어야한다.
(여러가지 방법이 있겠지만, 아는게 없어서 ㅜㅜ)

이를 쉽게 해결하는 방법은 다음과 같다.

메인 쯔음해서 GetConsoleWindow() 함수를 호출한 결과를 가지고 여부를
자동으로 구분할 수 있다.

응용 프로그램 모드로 구동하게 되면 위 함수가 HWND 타입의 핸들을 리턴하게 된다.
하지만 시스템 서비스 모드로 구동중이라면 NULL을 리턴한다.

즉, 프로그램 main에서 저 핸들값을 이용하여 StartServiceCtrlDispatcher 를 호출할 것인지
디버그용 메인을 호출할 것인지를 결정할 수 있는 것이다.

곁들여서 GetUserName() 를 이용하여 현재 어떤 계정으로 구동하는지도
첨부하면 금상첨화겠죠?

http://www.kriss.re.kr/ 한국 표준과학 연구원

SNTP (Simple Network Time Protocol)를 이용하며, 한국표준시 Time Server에 자동으로 접속하여 시간을 맞출 수 있다. (타임서버의 도메인: time.kriss.re.kr)


참고 소스는 http://www.codeproject.com/KB/IP/csntp.aspx
CSNTPClient - An SNTP Implementation

참고 자료는 http://blog.naver.com/cyonsang?Redirect=Log&logNo=140015175394
SNTP(단일 네트워크 기간 프로토콜) 시간 서버 목록


위와 같은 자료가 필요한 이유는, 배포된 프로그램에 시간을 이용해서 확인하는 작업등이
존재할경우 CMOS나, OS시간을 변경시켜버리면 확인하기 힘든 부분이 존재하기 때문이다.
여러가지 방식으로 막을 수는 있겠지만, 인터넷에 연결되어져야 하는 컴퓨터라면 혹은
인터넷이 되어야만 사용할 수 있는 프로그램이라면 위 방법으로 쉽게 해결할 수 있다.
프로그램을 작성하다 보니.. 파일의 시간을 변경할 일이 발생했다...
Win32 API에서 여러가지 함수를 제공하기는 하는데.. time_t를 바로 지원하지는 않는다. -_-;;;

다행히도 MSDN에서 이를 지원하는 KB가 있어 옮겨 보았다.

How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME

Article ID : 167296
Last Review : November 21, 2006
Revision : 3.3
This article was previously published under Q167296

SUMMARY

Under UNIX platforms, file times are maintained in the form of a ANSI C runtime arithmetic type named 'time_t', which represents seconds since midnight January 1, 1970 UTC (coordinated universal time).

Under Win32 platforms, file times are maintained primarily in the form of a 64-bit FILETIME structure, which represents the number of 100-nanosecond intervals since January 1, 1601 UTC (coordinate universal time).

This article shows how to convert UNIX time to other Win32 time formats.

MORE INFORMATION

The following function converts a filetime in the UNIX time_t format to a Win32 FILETIME format. Note that time_t is a 32-bit value and FILETIME is a 64-bit structure, so the Win32 function, Int32x32To64() is used in the following function:
   #include <winbase.h>
   #include <winnt.h>
   #include <time.h>

   void UnixTimeToFileTime(time_t t, LPFILETIME pft)
   {
     // Note that LONGLONG is a 64-bit value
     LONGLONG ll;

     ll = Int32x32To64(t, 10000000) + 116444736000000000;
     pft->dwLowDateTime = (DWORD)ll;
     pft->dwHighDateTime = ll >> 32;
   }
				
Once the UNIX time is converted to a FILETIME structure, other Win32 time formats can be easily obtained by using Win32 functions such as FileTimeToSystemTime() and FileTimeToDosDateTime().
   void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst)
   {
     FILETIME ft;

     UnixTimeToFileTime(t, &ft);
     FileTimeToSystemTime(&ft, pst);
   }
				

APPLIES TO
Microsoft Win32 Application Programming Interface, when used with:
    Microsoft Windows 95
    Microsoft Windows 98 Standard Edition
    Microsoft Windows Millennium Edition
    Microsoft Windows NT 3.51 Service Pack 5
    Microsoft Windows NT 4.0
    Microsoft Windows 2000 Standard Edition
    Microsoft Windows XP Professional

Back to the top

Keywords:
kbdatetime kbhowto kbkernbase kbprogramming KB167296
 

권한에 관한 부분은 상당히 방대한 양이라.. 그냥 필요해서 쓰는것만 추려봤습니다.
MSDN에 잘 나와았지만 대부분 WCHAR 타입의 인자를 넘겨야해서 그냥 껍데기를
씌운 형태입니다. ^^;

1. 유저 영역
   -> 유저 생성, 유저 삭제, 유저 목록, 암호 변경, 유저가 속한 그룹 목록, 유저 정보등..

2. 그룹 영역
  -> 그룹 생성, 그룹 삭제, 그룹 목록, 그룹에 유저 추가, 그룹에서 유저 삭제등..

3. 권한 영역
  -> 유저의 SID 얻어오기, SID를 문자열로 만들기, 현재 유저의 권한 얻어오기
      권한의 활성화/비활성화, 프로세스에 속한 권한 목록 얻어오기, 권한 상승
      프로세스 소유자 얻어오기 등등...


// Policy.h: interface for the CPolicy class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_)
#define AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define RTN_OK 0
#define RTN_USAGE 1
#define RTN_ERROR 13

#include <ntsecapi.h>
#include <vector>
#include <comutil.h>
#include <lm.h>
#include <sddl.h>
#include <Ntsecapi.h>


namespace CLM // Control Local Machine
{
    LPCTSTR GetLogonUser();
    LPCTSTR GetHostName();
    LPCTSTR GetDomainName();

    LPWCH  MBtoWC(LPCTSTR src, PWCHAR dst, int dstlen);
    LPTSTR WCtoMB(LPCWCH src, LPTSTR dst, int dstlen);

    void InitLsaString(PLSA_UNICODE_STRING LsaString, LPWSTR String);

    NTSTATUS OpenPolicy(
        LPCTSTR ServerName,                // machine to open policy
        DWORD DesiredAccess,            // desired access to policy
        PLSA_HANDLE PolicyHandle);        // resultant policy handle, must be LsaClose(PolicyHandle)

    NTSTATUS OpenPolicy(
        PLSA_HANDLE PolicyHandle);        // resultant policy handle, must be LsaClose(PolicyHandle)

    BOOL GetUserSid(
        LPCTSTR servername,                // where to lookup account
        LPCTSTR username,                // account of interest
        PSID *Sid);                        // resultant buffer containing SID, must be HeapFree(GetProcessHeap(), 0, pSid);

    BOOL GetCurrentUserSid(
        PSID *pSid);                    // resultant buffer containing SID, must be HeapFree(GetProcessHeap(), 0, pSid);

    BOOL GetSIDToString(PSID sid, LPTSTR& strstring);

    namespace USER
    {
        /*  Declared in Lmaccess.h; include Lm.h. Use Netapi32.lib.

            NetUserAdd                Adds a user account and assigns a password and privilege level. 
            NetUserChangePassword    Changes a user's password for a specified network server or domain. 
            NetUserDel                Deletes a user account from the server. 
            NetUserEnum                Lists all user accounts on a server. 
            NetUserGetGroups        Returns a list of global group names to which a user belongs. 
            NetUserGetInfo            Returns information about a particular user account on a server. 
            NetUserGetLocalGroups    Returns a list of local group names to which a user belongs. 
            NetUserSetGroups        Sets global group memberships for a specified user account. 
            NetUserSetInfo            Sets the password and other elements of a user account. 
        */

        NET_API_STATUS NetUserAdd(
            LPCTSTR servername,                // NULL is localcomputer
            LPCTSTR username,                // Not NULL
            LPCTSTR password,                // allow NULL
            LPCTSTR comment,                // allow NULL
            DWORD privilege,                // USER_PRIV_GUEST, USER_PRIV_USER, USER_PRIV_ADMIN
            DWORD flags);                    // 권한에 대한 제한. UF_SCRIPT | UF_DONT_EXPIRE_PASSWD 기타 등등..

        NET_API_STATUS NetUserChangePassword(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR username,                // NULL is current logon user
            LPCTSTR oldpassword,            // current password, Not NULL
            LPCTSTR newpassword);            // new password, Not NULL
    
        NET_API_STATUS NetUserDel(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR username);                // Not NULL
    
        NET_API_STATUS NetUserEnum(
            LPCTSTR domainname,                // NULL is localcomputer
            LPDWORD dwCount,                // user count
            LPUSER_INFO_20& userinfos);        // user info array, must be free -> NetApiBufferFree(userinfos);
    
        NET_API_STATUS NetUserGetGroups(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR username,                // NULL is current user
            std::vector<_bstr_t>& parray);    // global group name array

        NET_API_STATUS NetUserGetInfo(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR username,                // NULL is current user
            LPUSER_INFO_20& userinfos);        // user info array, must be free -> NetApiBufferFree(userinfos);
    
        NET_API_STATUS NetUserGetLocalGroups(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR username,                // NULL is current user
            std::vector<_bstr_t>& parray);    // local group name array
    
        NET_API_STATUS NetUserSetGroups(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR username,                // NULL is current user
            LPCTSTR groupname);                // Not NULL, global groupname
    }


    namespace GROUP
    {
        /*  Declared in Lmaccess.h; include Lm.h. Use Netapi32.lib.

            NetLocalGroupAdd        Creates a local group. 
            NetLocalGroupAddMembers Adds one or more users or global groups to an existing local group. 
            NetLocalGroupDel        Deletes a local group, removing all existing members from the group. 
            NetLocalGroupDelMembers Removes one or more members from an existing local group. 
            NetLocalGroupEnum        Returns information about each local group account on a server. 
            NetLocalGroupGetInfo    Returns information about a particular local group account on a server. 
            NetLocalGroupGetMembers Lists all members of a specified local group. 
            NetLocalGroupSetInfo    Sets general information about a local group. 
            NetLocalGroupSetMembers Assigns members to a local group. 
        */
    
        NET_API_STATUS NetLocalGroupAdd(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR groupname,                // Not NULL, groupname
            LPCTSTR comment);                // Allow NULL

        NET_API_STATUS NetLocalGroupAddMembers(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR groupname,                // Not NULL, groupname
            LPCTSTR username);                // Not NULL, username

        NET_API_STATUS NetLocalGroupDel(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR groupname);                // Not NULL, groupname
    
        NET_API_STATUS NetLocalGroupDelMembers(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR groupname,                // Not NULL, groupname
            LPCTSTR username);                // Not NULL, username

        NET_API_STATUS NetLocalGroupEnum(
            LPCTSTR domainname,                // NULL is localcomputer
            LPDWORD dwCount,                // group count
            PLOCALGROUP_INFO_1& groupinfo);    // group info array, must be free -> NetApiBufferFree(groupinfo);

        NET_API_STATUS NetLocalGroupGetInfo(
            LPCTSTR domainname,                // NULL is localcomputer
            LPCTSTR groupname,                // Not NULL, groupname
            PLOCALGROUP_INFO_1& groupinfo);    // group info, must be free -> NetApiBufferFree(groupinfo);

    };

    
    
    namespace PRIVILEGE
    {
        // 현재 프로세스에 주어진 권한을 인에이블/디스에이블 시킨다.
        BOOL SetProcessPrivilege(
            LPCTSTR lpszPrivilege,            // privilege to grant
            BOOL bEnablePrivilege);            // add or remove

        // 사용자 계정에 특별한 권한을 추가시킨다.
        NTSTATUS SetPrivilegeOnAccount(
            LSA_HANDLE PolicyHandle,        // open policy handle
            PSID AccountSid,                // SID to grant privilege to
            LPWSTR PrivilegeName,            // privilege to grant (Unicode)
            BOOL bEnable);                    // enable or disable

        // 해당 유저에게 할당된 부가적인 프리빌리지를 얻어온다.
        // 일반적인 프리빌리지를 얻어올 경우는, 해당 유저가 띄운 프로세스를 이용하여
        // 아래의 GetPrivilegeFromProcessHandle 함수를 이용하여 가져온다.
        NTSTATUS EnumPrivilegeOnAccount(
            LSA_HANDLE PolicyHandle,        // open policy handle
            PSID AccountSid,                // SID to grant privilege to
            std::vector<_bstr_t>& parray);    // privileges array

        // 프로세스 핸들을 이용하여 해당 유저 이름을 얻어온다.
        BOOL GetUserFromProcessHandle(
            LPTSTR AccountName,                // account of interest
            DWORD* cbName,                    // string buffer length
            HANDLE hProcess = NULL);        // process id, NULL is owner process

        // 프로세스 아이디를 이용하여 해당 유저 이름을 얻어온다.
        BOOL GetUserFromProcessID(
            LPTSTR AccountName,                // account of interest
            DWORD* cbName,                    // string buffer length
            DWORD nProcessID = 0xFFFFFFFF);    // process id, 0xFFFFFFFF is owner process

        // 프로세스 핸들을 이용하여 해당 프로세스에 설정된 프리빌리지 목록을 얻어온다.
        BOOL GetPrivilegeFromProcessHandle(
            std::vector<_bstr_t>& parray,    // privileges array
            HANDLE hProcess = NULL);        // process id, NULL is owner process

        // 프로세스 아이디을 이용하여 해당 프로세스에 설정된 프리빌리지 목록을 얻어온다.
        BOOL GetPrivilegeFromProcessID(
            std::vector<_bstr_t>& parray,    // privileges array
            DWORD nProcessID = 0xFFFFFFFF);    // process id, 0xFFFFFFFF is owner process
    };
};

#endif
// !defined(AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_)







작업을 하다보니 4GB 크기가 넘는 파일을 다루어야 할 일이 생겼네요.
윈도우즈 커널에 _hread, _hwrite 등의 숨겨진 API가 제공되지만..
이런거 찾아다가 처리하기가 여간 귀찬아서.. ㅎㅎ;

대형 파일을 그냥 fopen, open, OpenFile 등등.. 어떤 API로 처리하든지
그냥 읽고 쓰기는 가능합니다.
(단, 이 방법을 사용하시더라도 fseek, lseek는 불가능합니다.
 파일을 seek해야 할 경우는 처음에 이야기 처럼 kernel에 숨겨진 위의 함수들을
 찾아서 사용하시던지, Visual Studio 2005 이상 버전을 사용하세요.

http://support.microsoft.com/kb/q100513/
위의 주소는 MS의 KB로 Visual Basic에서 대형파일을 어떻게 처리하는지에
대한 내용입니다. 내용을 보시고 VC로 포팅하는건 ~~
)



단지, 크기를 읽을 때 문제가 발생하죠..
Visual Studio 2005 이상 버전에서는 64비트 크기를 지원하는 새로운
API들이 제공되니 그냥 그걸 쓰시면 되고...

저는 몇몇 가지를 빼곤 대부분 Visual Studio 6.0을 사용하는 관계로, 6.0을 사용하시는
분들에게만 해당되는 내용이 될것입니다.

아래는 데브피아에 올라온 원문입니다.

 4.2기가 바이트 이상의 파일을 읽고 싶습니다. [답을 찾았습니다.]  | VC++ 일반 2007-04-09 오전 11:07:43
김태권 (jukyou)  김태권님께 메시지 보내기김태권님을 내 주소록에 추가합니다.김태권님의 개인게시판 가기 번호: 650715   / 평점:  (9.0)  / 읽음:80

안녕하십니까? 이전에 제 우매한 질문에 여러분께서 답변을 해주셔셔 감사드린다는 말씀 전해 드립니다.


저번 주 주말에 답을 찾아서 답을 올립니다.


강태호 (cartarsis) 님께서 답변 주신 내용과 MSDN을 읽어 보고 해결하였습니다.

우선 4.2기가 바이트 이상의 파일을 읽어야하는 상황이 있으신 분들을 위해 글을 남겨 놓습니다.



소스는 아래와 같습니다.


HANDLE hFile;

unsigned long nSizeHigh = 0;

unsigned long nSizeLow = 0;

__int64 nSize = 0;

hFile = CreateFile(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

nSizeLow = GetFileSize(hFile, &nSizeHigh);

nSize = nSizeHigh;

nSize <<= 32;

nSize |= nSizeLow;

TRACE("Size : %I64d\n", nSize);


좋은 하루 되세요~~


그럼 이만 줄입니다.

윈도우즈 서비스 프로그램을 작성할 때
두가지 형태의 파라미터를 전달 받을 수 있습니다.

1. 서비스파일.exe  -install -u:aaa -p:pass 기타등등
2. 서비스 관리자 -> 시작 매개변수

이렇게 두가지로 전달 받을 수 있는데요..
1번과 같은 경우는 일반적인 콘솔 프로그램과 다를바가 없지요.
즉, main 의 argc, argv 파라미터를 바로 처리하면 됩니다.

2번도 사실 1번과 거의 동일합니다.
다만 파라미터를 검출하는 위치만 다를뿐 100% 같은 방식을 사용합니다.
차이점이라면 StartServiceCtrlDispatcher 에서 등록한 서비스 메인 함수에서
파라미터를 처리한다는 차이점이 있습니다. ^^;

상당히 많은 시간을 날렸네요.. ㅜㅜ

이전 내용하고 비슷한데요..
이전에 서비스계정에 이름은 있고, 암호는 없는 사용자를 처리할 때는

HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Lsa
의 limitblankpassworduse 값을 1로 변경하면서 마무리 지었습니다.
윈도우즈 XP프로와, 서버계열에서는 아주 잘되던데..

윈도우즈 XP 홈에서는 저기까지는 잘되는데 나머지 난관이 발생하더군요..
사용자 계정 자체에 서비스로 로그온할 수 있는 보안 권한을 상승 시켜주어야 했습니다.

계정과 암호 (비어있을 경우) 정상적인데도 불구하고 서비스가 시작되지 않더군요.
서비스 관리자를 열어서 다시 서비스의 로그온 정보에서 암호를 지워주고 시작을
누르면 계정을 "서비스로 로그온 권한을 상승하였습니다." 라는 메시지가 나오면서
시작이 잘되더군요.

문제는 이걸 프로그램적으로 자동화 시켜야한다는게 문제였는데.. ^^;


#include <Ntsecapi.h>
#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 AddPrivileges(PSID AccountSID, LSA_HANDLE PolicyHandle)
{
 LSA_UNICODE_STRING lucPrivilege;
 NTSTATUS ntsResult;
 
 // Create an LSA_UNICODE_STRING for the privilege names.
 if (!InitLsaString(&lucPrivilege, L"SeServiceLogonRight"))
  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;
}

사용법은 아래와 같습니다.
 LSA_HANDLE Policy = GetPolicyHandle();
 BYTE buffer[1024] = {0};
 PSID sid = (PSID)buffer;
 if(GetUserSID(sid, "까마귀"))
  AddPrivileges(sid, Policy);

위와 같은 방법으로 사용자 계정에다가 직접 SeServiceLogonRight 권한을 넣어
주는 방법으로 해결했습니다.

자료가 없어서 헤딩좀 했는데.. 도움이 되시기를...

이것 때문에 헤맨 시간을 생각하면.. ㅇㅎㅎ ㅠㅠ~~

윈도우즈 서비스로 제작한 프로그램을 서비스에 등록할 경우..
로컬시스템 계정이나 암호를 가진 유저는 잘 등록이 되고
동작도 잘됩니다.

하지만 빈문자열로 암호를 가지지 않은 계정은 서비스가 로그온에 실패하여
서비스가 정상적으로 수행되지 못합니다.

이걸 프로그램적으로 해결하기 위해 백방으로 찾아봤지만..
해결을 못했었는데 이렇게 허무할 수가 ㅠㅠ;

http://support.microsoft.com/kb/303846/ko


오류 메시지: 계정 제한 때문에 로그온할 수 없습니다

기술 자료 ID : 303846
마지막 검토 : 2001년 11월 21일 수요일
수정 : 1.0
이 문서는 이전에 다음 ID로 출판되었음: KR303846

현상

원격 데스크톱 도구를 사용하여 Windows XP 기반 컴퓨터에 연결하려고 하면 아래의 오류 메시지가 나타날 수도 있습니다.
계정 제한 때문에 로그온할 수 없습니다.

위로 가기

원인

연결하는 데 사용한 계정에 암호가 없으면 이러한 문제가 발생할 수 있습니다. 암호가 없는 계정을 사용할 때는 원격 데스크톱 연결을 설정할 수 없습니다.

위로 가기

해결 방법

원격 데스크톱 연결을 설정할 수 있도록 이 문제를 해결하려면 해당 컴퓨터의 콘솔에서 로그온한 다음 해당 사용자 계정에 대해 암호를 설정하십시오.

위로 가기

현재 상태

이것은 의도적으로 설계된 동작입니다.

위로 가기

추가 정보

정책을 사용하여 빈 암호 제한을 해제할 수 있습니다. 이 정책을 찾아서 변경하려면 다음과 같이 하십시오.
1. 시작을 누르고, 실행을 누르고, gpedit.msc를 입력한 다음 확인을 눌러 그룹 정책 편집기를 시작합니다.
2. 컴퓨터 구성\Windows 설정\보안 설정\로컬 정책\보안 옵션\계정: 콘솔 로그온 시 로컬 계정에서 빈 암호 사용 제한을 엽니다.
3. 콘솔 로그온 시 로컬 계정에서 빈 암호 사용 제한을 두 번 누릅니다.
4. 사용 안함을 누른 다음 확인을 누릅니다.
5. 그룹 정책 편집기를 종료합니다.
참고: 기본적으로 이 정책은 사용함으로 설정되어 있습니다.
InsertItem은 중요도도 높고, 다양한 양식을 제공함으로 별도로 취급한다.

1. int InsertItem(int nItem, LPCTSTR lpszItem);
2. int InsertItem(int nItem, LPCTSTR lpszItem, int nImage);
3. int InsertItem(const LVITEM* pItem);

샘플은 이전에 다루었던 장 중에서 하나씩 시작하기의 샘플을 수정하여 진행한다.
또한 LVITEM 구조체에 대한 설명은 링크를 참조한다.

1번과 2번에 대한 설명은 아이템의 인덱스, 레이블 문자열, 아이템의 이미지 인덱스와 같이
이해하기 쉽고 용법도 간단하므로 생략한다. (기존 장에서도 충분히 다루었다.)


아이템에 문자열만 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
리스트컨트롤.InsertItem(&item);
아주 간단하게 해당 아이템의 인덱스에 문자열만을 넣어 보았다.
item.mask 값에 LVIF_TEXT 가 설정되었는데, 해당 문자열을 처리함을 알려준다.

이미 들어있는 아이템의 문자열을 변경하려면 SetItemText를 이용하여 변경이 가능하고
그 값을 가져오려면 GetItemText를 이용하면 된다.


아이템에 문자열과 이미지 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
리스트컨트롤.InsertItem(&item);
리스트 컨트롤에 이미지 리스트가 연결되어 있다면 LVIF_IMAGE 마스크를 넣어 이미리 리스트에해당하는 인덱스를 넣음으로써 컨트롤에 아이콘이 표시되도록 한다.

이미지 리스트를 연결하였음에도 불구하고, 이미지의 인덱스를 설정하지 않으면 기본값으로
0번 이미지가 그려짐에 주의하자.


한번 넣은 이미지를 변경하기 위한 별도의 메서드는 제공되지 않는다. 이미 넣은 이미지를
변경하기 위해서는 SetItem 메서드를 이용하여 변경이 가능하다.

예) 0번 아이템의 이미지를 5번 이미지로 바꾸기.

리스트컨트롤.SetItem(0, 0, LVIF_IMAGE, NULL, 5, 0, 0, 0);

LVITEM item = {0};
item.mask = LVIF_IMAGE;
item.iItem = 0;
item.iImage = 5;
리스트컨트롤.SetItem(&item);


아이템에 문자열과 부가 정보 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_PARAM;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.lParam = 4바이트 숫자형 데이터 or 부가정보의 포인터
리스트컨트롤.InsertItem(&item);
부가 정보 필드인 LVITEM::lParam은 LPARAM 타입의 변수로 기본타입은 long 이다.

초기 아이템 삽입시 저 정보의 설정이 불필요하고 나중에 설정하거나 가져오려면
SetItemData 와 GetItemData 메서드를 이용하여 처리할 수 있다.


아이템에 상태 정보 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.state = 상태값 조합
item.stateMask = 상태 마스크 플래그 조합
리스트컨트롤.InsertItem(&item);

상태 플래그는 다음과 같다.
LVIS_ACTIVATING
   지원 않함.
LVIS_CUT
   아이콘이 흐리게 보임 (cut & paste 동작시 주로사용)
LVIS_DROPHILITED
   drag&drop시 타겟 리스트 컨트롤일 경우 마우스 오버 상태에 따라 아이템을 하이라이트 시켜줌
LVIS_FOCUSED
   아이템에 포커스 사각형(쩜선 사각형)을 그려줌
LVIS_SELECTED
   아이템이 선택되어진 형태로 표시해줌
LVIS_STATEIMAGEMASK
   아이템의 이미지에 상태 이미지 선택할 수 있도록 함.
LVIS_OVERLAYMASK
   아이템에 오버레이 마스크용 아이콘을 겹쳐서 그려줌

볼드로 표기된 나머지는 상태 마스크와 상태값을 동일하게 넣으면 된다. 내용 자체도 이해하기
쉬우니 간단한 예만 적어본다.

예) 선택되어진 상태로 표시하기.
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.state = LVIS_FOCUSED;
item.stateMask = LVIS_FOCUSED;
리스트컨트롤.InsertItem(&item);

나머지 2개의 상태 마스크는 아래에 별도로 설명을 추가한다.


이미지에 오버레이 마스크 이미지 덧 씌우기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
item.state = INDEXTOOVERLAYMASK(오버레이 이미지 인덱스);
item.stateMask = LVIS_OVERLAYMASK;
리스트컨트롤.InsertItem(&item);
오버레이 이미지란 기본 이미지(아이콘) 위에, 다른 아이콘을 덧 씌워 그리는 것을 말한다.
탐색기를 살펴보면 단축 아이콘과 같은 경우

위 그림처럼 기본 이미지위에 오버레이 이미지가 그려져 있는 것을 알 수 있다.
이런 식으로 이미지를 두장 겹처서 그릴 때 사용하는 위와 같은 방법을 사용한다.

오버레이에 사용되는 이미지는 기본으로 연결된 이미지 리스트를 사용한다.


상태 이미지를 별도로 추가하기.
-----------------------------------------------------------------------------------
이건 코드를 달기 전에 먼저 간단한 설명을 전하고, 코드를 이용하여 나머지를 설명할 것이다.
리스트 하나 하나의 아이템이 어떤 상태에 따라 아이콘의 모양이 달라진다면 어떻게 구현할까?


(위 그림은 모두 폴더를 나타내는 아이콘이지만 상태에 따라 서로 다른 이미지를 가진다.)

1. 하나의 이미지 리스트에 몽땅 때려 놓고, 그 때 그 때 이미지를 변경한다.
2. LVSIL_STATE 로 이미지 리스트를 별도로 연결하여 놓고, 그 상태의 이미지를 넣는다.

1번 방법을 이용할 경우, 중간에 이미지가 새로 추가되거나 삭제되면 전체 인덱스 순서에
영향을 미치기 때문에 파급효과가 대단히 크다. 물론 많은 코드 수정도 불가피히다.

2번 방법을 이용할 경우, 이미 인덱스를 별도로 밖아 놓은 코드는 변경을 해야하지만 상태에 따른
이미지 변경 코드는 변화가 없다. 또한 기본 이미지리스트의 인덱스에 영향을 받지 않는다.

헤더파일
이미지리스트 작은이미지;
이미지리스트 큰이미지;
이미지리스트 상태이미지;

소스파일
리스트컨트롤.SetImageList(&작은이미지, LVSIL_SMALL);
리스트컨트롤.SetImageList(&큰이미지, LVSIL_NORMAL);
리스트컨트롤.SetImageList(&상태이미지, LVSIL_STATE);
위와 같이 기본 이미지 리스트와는 별도로 상태 이미지를 담은 이미지 리스트를 만들어 연결한다.

LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
item.state = INDEXTOSTATEIMAGEMASK(상태 이미지 인덱스);
item.stateMask = LVIS_STATEIMAGEMASK;
리스트컨트롤.InsertItem(&item);


아이템에 이미지 여러개 넣기??? - 이게 정말 맞는 이해인지 모르겠다 (누가좀.. )
-----------------------------------------------------------------------------------

LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_INDENT;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
item.iIndent = 아이템의 이미지 너비의 복수???
리스트컨트롤.InsertItem(&item);

(iIndent 변수는 common control version 4.7 부터 적용되는 기능입니다.)
리스트 컨트롤에서 레포트 스타일일 경우 첫번째 아이템에 여러개의 이미지가 있는 것을 본적이
있는가? (전 본 기억이 없는듯.. 이것도 오늘 공부해서 써본것입니다. -_-)

저 iIndent 값은 기본적으로 0이며, 저기 숫자를 넣으면, 첫번째 아이템이
저 숫자* 아이콘의 너비 만큼 띄워져서 그려진다.
(아래의 그림은 iIdent를 1로 준 상태입니다.)

그림에서 보는 것 처럼 왼쪽에 아이콘의 너비인 16 픽셀만큼 띄워져서 그림이 그려지고. iIndent 값을 높게 주면 그 배수만큼 띄워져서 그려지게 된다.
Custom Draw나 Owner Draw를 이용하면 저기 비워놓은 공간에 먼가 작업을 해줄 수 있겠지만
그냥 쓸 때는 전혀 의미가 없어보인다. 흠..
(누구 자세히 아시는분 계시면 코멘트좀 달아주세요..)

common control version 6.0 부터는 group 이라는 기능이 지원이 되는데.. 아쉽게도 MFC 6.0
에서는 지원이 되지 않습니다. 현재 깔린 툴이 6.0 뿐이라.. 제 컴에 vs2005를 깔게되면 이 내용에
group에 관련된 내용도 추가하도록 하겠습니다. ㅋ~



위의 내용들을 이용하여 제작된 샘플이다.
-----------------------------------------------------------------------------------

이전에 다루었던 장 중에서 하나씩 시작하기의 샘플을 재 사용(-_-;;;) 하였으며 기존과 달라진 점은 공유 폴더나 단축아이콘도 이미지가 정확하게 표현된다는 점이다.

변경된 내용은 다음과 같다.

BOOL CSampleDlg::OnInitDialog()
{
    ... 중간 생략
    int ndx = 0;
    CFileFind finder;
    SHFILEINFO sfi;
    BOOL bWorking = finder.FindFile("C:\\*.*");
    while (bWorking)
    {
        bWorking = finder.FindNextFile();
        SHGetFileInfo(finder.GetFilePath(),
            0,
            &sfi,
            sizeof(SHFILEINFO),
            SHGFI_DISPLAYNAME | SHGFI_TYPENAME| SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ATTRIBUTES);
        ndx = AddItem2(finder, sfi, ndx);
    }
    // 찾은 파일의 파일속성 정보를 얻기 위하여 SHGFI_ATTRIBUTES 플래그가 추가되었다.

    ... 중간 생략
}

int CSampleDlg::AddItem2(CFileFind& file, SHFILEINFO& sfi, int ndx)
{
    CString szTemp;
    CTime fTime;

    LVITEM item = {0};
   
    // 아이템의 상태 정보를 넣기 위하여 LVIF_STATE 플래그를 추가함.
    item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    item.iItem = ndx;
    item.iSubItem = 0;
    item.pszText = sfi.szDisplayName;
    item.iImage = sfi.iIcon;
   
    // 단축 아이콘 속성일 경우.. 2번째 이미지를 오버레이 이미지로 사용함.
    if(sfi.dwAttributes & SFGAO_LINK)
    {
        item.state |= INDEXTOOVERLAYMASK(2);
        item.stateMask |= LVIS_OVERLAYMASK;
    }
    // 공뷰 폴더 속성일 경우.. 1번째 이미지를 오버레이 이미지로 사용함.
    else if(sfi.dwAttributes & SFGAO_SHARE)
    {
        item.state |= INDEXTOOVERLAYMASK(1);
        item.stateMask |= LVIS_OVERLAYMASK;
    }

    // 히든 속성일 경우는 별도이 이미지를 사용하지 않고, LVIS_CUT 상태를 이용하여
    // 흐릿하게 보이도록 함.

    if(sfi.dwAttributes & SFGAO_HIDDEN)
        item.state |= LVIS_CUT;

    ndx = m_list.InsertItem(&item);

    ... 중간 생략   
}



작업하다보니 단축아이콘도 만들어야 겠네.. -_-;;
마우스로 드래그만 하면 되는데 플그램으로 짜려면 왤케 이것 저것 해야하는게 많은건지..
MSDN에서 권장하는 IShellLink 인터페이스를 이용하여 제작합니다.

클래스로 만들 이유는 없는데, 이것 저것 추가될깨 있어어 그냥 껍데기를 씌웠습니다.
참고로 일반적인 헤더 외에, 아래꺼 추가되야 할겁니다.
#include <atlbase.h>
#include <comdef.h>

VC++ 6.0 SP6, 및 Platfrom SDK 2004년 버전에서 테스트됨.

헤더파일
-----------------------------------------------------------------------------------
class CFileShell 
{
public:
 CFileShell();
 virtual ~CFileShell();

    // 지정한 파일로 주어진 특수 경로에 단축아이콘을 만든다.
    BOOL CreateShortCut(const char* filepath, int nFolder, const char* dispname = NULL, const char* description = NULL, const char* workdir = NULL);

    // 특수한 경로에 주어진 이름의 폴더를 만든다.
    CString CreateSpecialForder(int nFolder, const char* forder_name);
};

1. CreateShortCut
   filepath         전체경로를 가진 파일 이름
   nFolder         CSIDL Costants 를 지칭합니다. 참고 SHGetSpecialFolderPath
   dispname      단축아이콘의 이름, 설정하기 않으면 원본 파일이름으로 대체
   description   단축아이콘 설명
   workdir          아이콘이 실행될 경로, 설정하지 않으면 원본 파일의 경로로 대체

2. CreateSpecialForder
   nFolder          CSIDL Costants 를 지칭합니다. 참고 SHGetSpecialFolderPath  
   forder_name  생성할 폴더의 이름



소스 파일
-----------------------------------------------------------------------------------
BOOL CFileShell::CreateShortCut(const char* filepath, int nFolder, const char* dispname, const char* description, const char* workdir)
{
    char TargetPath[MAX_PATH];
    CString fromPath=filepath, fromName, fromExt, ShortCutName;

    if(!SHGetSpecialFolderPath(NULL, TargetPath, nFolder, FALSE))
        return FALSE;

    HRESULT hr;
     CComPtr<IShellLink>   pISL;

    hr = pISL.CoCreateInstance( CLSID_ShellLink );
     if(FAILED(hr))
        return FALSE;

    // 단축 아이콘의 대상이 되는 파일을 설정한다.
    hr = pISL->SetPath ( filepath );
     if(FAILED(hr))
        return FALSE;

    // 주어진 소스 파일의 경로를 이용하여, 전체 패스, 이름, 확장자를 나눈다.
    int find = fromPath.ReverseFind('\\');
    if(find == -1)
        return FALSE;

    fromName = fromPath.Right(fromPath.GetLength()-find-1);
    fromPath = fromPath.Left(find+1);

    find = fromName.ReverseFind('.');
    if(find != -1)
    {
        fromExt  = fromName.Right(fromName.GetLength()-find-1);
        fromName = fromName.Left(find);
    }

    // 별도로 디스플에이용 이름을 주면 그것을 사용한다.
    if(dispname)
        fromName = dispname;

    // 별도로 수행 경로를 주면 그것을 이용한다.
    if(workdir)
        fromPath = workdir;


    // 단축 아이콘에 대한 설명을 단다.
    if(description)
    {
        hr = pISL->SetDescription ( description );
        if(FAILED(hr))
            return FALSE;
    }
   
    // 워킹 폴더를 설정한다.
    hr = pISL->SetWorkingDirectory ( fromPath );
    if(FAILED(hr))
        return FALSE;

   
    WCHAR ShortCutNameW[256] = {0};
    ShortCutName = fromPath + fromName + ".lnk";

    MultiByteToWideChar(CP_ACP, MB_COMPOSITE, ShortCutName, ShortCutName.GetLength(), ShortCutNameW, 256*sizeof(WCHAR));

    CComQIPtr<IPersistFile> pIPF( pISL );
     hr = pIPF->Save ( ShortCutNameW, FALSE );
     if ( FAILED(hr) )
        return FALSE;

    return TRUE;
}

CString CFileShell::CreateSpecialForder(int nFolder, const char* forder_name)
{
    char TargetPath[MAX_PATH];
    if(!SHGetSpecialFolderPath(NULL, TargetPath, nFolder, FALSE))
        return "";

    CString FullName = TargetPath;
    FullName += "\\";
    FullName += forder_name;

    if(!CreateDirectory(FullName, NULL))
    {
        DWORD le = GetLastError();
        if(le != ERROR_ALREADY_EXISTS)
            return "";
    }

    return FullName;
}

+ Recent posts