TMemIniFile 클래스에 대해 살펴 보고자 한다.


1. 객체 위치

     TMemIniFile은 TIniFile처럼 IniFiles 유닛에 구현되어있다.

     C++Builder에서는 #include <IniFiles.hpp>

     Delphi에서는 Uses절에   IniFiles 를 추가하면 된다.


2. TIniFile 과 같은점 

    TIniFile처럼 TCustomIniFile 를 상속받았다.
     그래서 사용방법은 TIniFile클래스와 거의 동일하다


3. TIniFile과 MemIniFile의 다른점
  
   @ TIniFile은?

     ; TIniFile클래스는 Ini파일의 어떤 값을 읽어오거나 쓸때 
       매번 File을 Open하여 읽기/쓰기 하고 File을 Close한다.

      즉 Read를 100번 반복하면 , 100번이나 File을 open/close하게 된다.
      
      * 많은 Data를 Read/Write할때 속도가 매우 느리다.
          매번 Section과 Key를 파일내용 검색해서 찾아야 할것이다.(windows-api)

      * 하지만 Key값 하나라도 Write하자마다 바로 file에 기록된다.

      * 또한 Read를 반복하더라도 중간에 File이 변경된것이 있으면 변경된 값을 읽어오게 된다.


  @ TMemIniFile은?

     ; 반면 MemIniFile은 이름이 말해 주듯이
       매번 File을 Open하여 읽기/쓰기 하지 않고,  
       파일의 내용을 전부 한번에 읽어와 메모리에 가지고 있으며
       어떤 Key값을 Read/Write할때는 메모리에서 읽어오고 , 메모리에다가 쓰기만 한다.

       1) 메모리에서 읽고 쓰기 하니 , read/write 속도가 빠르다.

       2) 반면 write하더라도 UpdateFile() 함수를 호출하기 전까지는 파일에 적용이 되지 않는다.

       3) 당연한 얘기지만 중간에 파일 내용이 변경되더라도
          Read할때 Memory에서만 읽어오므로, 실제 파일의 값을 읽어오진 않는다.
     

      

4. MemIni 읽고 쓰기 예제

  
//---------------------------------------------------------------------------
#include "IniFiles.hpp"
//---------------------------------------------------------------------------
void __fastcall TForm6::Button1Click(TObject *Sender)
{
    TMemIniFile *MemIni=new TMemIniFile("D:\\Test.ini");
    MemIni->ReadString("HEADER","TEST_KEY","");
    MemIni->WriteString("HEADER","TEST_KEY","TEST_MEMINI_Data");
    MemIni->UpdateFile();
    delete MemIni;
}
//---------------------------------------------------------------------------
     


위와같이 간단한 Key 한두개를 읽고 쓰기 할때는 그냥 TIniFile이 좋을것이다.

하지만
파일 내용이 많고
Ini 객체를 만들어서 프로그램 중간중간 읽고/ 쓰기를 할경우엔  MemIni가 효율적일수 있다.


5. MemIni에서 Disk-File의 내용을 다시 읽어오려면?
   Rename함수를 호출해주면 된다.


void __fastcall TForm6::Button2Click(TObject *Sender)
{
     TMemIniFile *MemIni=new TMemIniFile("D:\\Test.ini");
     ShowMessage(MemIni->ReadString("HEADER","TEST_KEY","없나요?"));
     MemIni->Rename(MemIni->FileName,true);//같은 파일로 하면, load를 true로 해서 다시 load한다.
     ShowMessage(MemIni->ReadString("HEADER","TEST_KEY","없나요?"));
     delete MemIni;
}
//---------------------------------------------------------------------------



6. MemIni와 IniFile의 또 다른점 ?

   MemIni 파일을 save하면 파일 중간에 주석이나 공백 라인이 사라진다.

//원래 Ini 파일

  
[HEADER]
FILE_VER=2
TRAIN_COUNT=1
RFIDs=00008273,
AXIS_COUNT=4,
CAR_DIR=1,
ENTER_TIME=20120316134033
LEAVE_TIME=20120316134057

RECOGNIZE_RATE_ALL=100.0%,16/16
RECOGNIZE_RATE_NEL=100.0%,16/16

;NUM=fileName,기준,검사결과,두께1,두께2,두께3,(위치1),(위치2),(위치3)
;결과값 0=OK , 1=NG(위험) , 2=사진확인 , 3=Warning(주의) 


[00008273]
1=20120316134053_00008273_1_1_NEL.bmp, 5.00, 0, 17.79  , 0.00 , 0.00 , 425.18, 688.81 , 431.51 , 806.09 , 0.00, 0.00 , 0.00 , 0.00 , 0.00, 0.00 , 0.00 , 0.00 , 261.00, 624.96 , 717.00 , 598.81 , 0.1515 
2=20120316134053_00008273_1_2_NEL.bmp, 5.00, 0, 18.27  , 0.00 , 0.00 , 713.05, 675.39 , 713.20 , 793.27 , 0.00, 0.00 , 0.00 , 0.00 , 0.00, 0.00 , 0.00 , 0.00 , 412.00, 601.85 , 875.00 , 601.25 , 0.1550 
3=20120316134053_00008273_1_3_NEL.bmp, 5.00, 3, 8.23  , 0.00 , 0.00 , 529.30, 670.42 , 530.95 , 724.80 , 0.00, 0.00 , 0.00 , 0.00 , 0.00, 0.00 , 0.00 , 0.00 , 367.00, 593.64 , 824.00 , 580.18 , 0.1513 

     
  

//MemIni로 Load한후에 UpdateFile하면?
다음과 같이 주석이나 공백라인이 다 사라지게 된다.

  
[HEADER]
FILE_VER=2
TRAIN_COUNT=1
RFIDs=00008273,
AXIS_COUNT=4,
CAR_DIR=1,
ENTER_TIME=20120316134033
LEAVE_TIME=20120316134057
RECOGNIZE_RATE_ALL=100.0%,16/16
RECOGNIZE_RATE_NEL=100.0%,16/16

[00008273]
1=20120316134053_00008273_1_1_NEL.bmp, 5.00, 0, 17.79  , 0.00 , 0.00 , 425.18, 688.81 , 431.51 , 806.09 , 0.00, 0.00 , 0.00 , 0.00 , 0.00, 0.00 , 0.00 , 0.00 , 261.00, 624.96 , 717.00 , 598.81 , 0.1515
2=20120316134053_00008273_1_2_NEL.bmp, 5.00, 0, 18.27  , 0.00 , 0.00 , 713.05, 675.39 , 713.20 , 793.27 , 0.00, 0.00 , 0.00 , 0.00 , 0.00, 0.00 , 0.00 , 0.00 , 412.00, 601.85 , 875.00 , 601.25 , 0.1550
3=20120316134053_00008273_1_3_NEL.bmp, 5.00, 3, 8.23  , 0.00 , 0.00 , 529.30, 670.42 , 530.95 , 724.80 , 0.00, 0.00 , 0.00 , 0.00 , 0.00, 0.00 , 0.00 , 0.00 , 367.00, 593.64 , 824.00 , 580.18 , 0.1513
     



7. MemIni에만 있는 기능?

    그리고 TIniFile에는 없으면서 MemIniFile에만 있는 기능이 있다.
    그것은 파일이 저장이 없이,  INI 방식의 Memory상의 저장소로 사용이 가능하다는 것이다.

  


void __fastcall TForm6::Button3Click(TObject *Sender)
{
    TMemIniFile *MemIni=new TMemIniFile(""); //file-nothing
    MemIni->WriteString("HEADER","TEST_KEY","Memery save");
    ShowMessage(MemIni->ReadString("HEADER","TEST_KEY",""));
    delete MemIni;
}
//---------------------------------------------------------------------------

     

 

          

      

현재 ICT  Project에 TIniFile을 많이 쓰고 있다.

많은 Key를 read/write하고 있기 때문에 속도가 많이 느린 편이다.

장단점을 잘 파악하여, MemIniFile로 변경을 고려해 볼 필요가 있을것 같다.


       

      


음...

TreeView를 조직도 및 여러가지 데이터를 관리하고 보여주는데 많이 사용한다.


그런데 VCL에서 

TTreeView의 TTreeNode에는 담을수 있는 정보에 한계가 있다.


Text
Data
ImageIndex
StateIndex
SelectedIndex

등..


TTreeNode에 보다 많은 정보를 담아야할때( 연결해야) 할때는 어떻게 해야하나?


방법1.

   void * Data 활용 

   일반적으로  정보는 따로 가지고 있고

   void *Data에다가 Poiner를 연결해서 많이 쓴다.

 

  
  class TMyClass
  {

    public:
          String sName;
  };

    

   //정보 저장  

   Node->Data = (void *) MyData;

   
   //정보 활용 
   TMyClass *MyData=(TMyClass *)Node->Data;
   MyData->sName;  

  
     

  

 방법 1-1

   간단한 숫자 정보일경우 

   Node->Data=(void *) iNumber ;   // 곧바로 숫자 정보를 집어 넣는다.



방법2

   또 다른 좋은 방법으로 

    TTreeNode 클래스를 상속받은 Node-Class를 만들어서 Add하는 방법이 있다.


   그런데 TTreeView는 TTreeNode객체를 직접생성해서 TreeView에 Add하는 방식이 아니라

   TreeView를 통해서 객체를 생성한단.

   

 바로  OnCreateNodeClass 라는 이벤트를 이용하는 것이다.

  이 이벤트에서 TTreeNode를 상속받은 class의 MetaClass를 연결해주면 된다.


간단한 샘플 코드를 작성해 보았다.

프로그램 화면

샘플 코드

  


//---------------------------------------------------------------------------
// Group 정보를 담는 Node-Class
//---------------------------------------------------------------------------
class TGroupNode : public TTreeNode
{
private:
    String FGroupName;
    String FHints;
        int FTag;

public:
    __property   String GroupName={read=FGroupName,write=FGroupName};
    __property   String Hints={read=FHints,write=FHints};
    __property   int    Tag={read=FTag,write=FTag};

};

//---------------------------------------------------------------------------
//개인(사람) 정보를 담는 Node-Class
//---------------------------------------------------------------------------
class TPersonNode : public TTreeNode
{
private:
    String FName;
    String FAddress;
    String FPhoneNumber;
    String FHints;
        int FTag;

public:
    __property   String Name={read=FName,write=FName};
    __property   String Address={read=FAddress,write=FAddress};
    __property   String PhoneNumber={read=FPhoneNumber,write=FPhoneNumber};
    __property   String Hints={read=FHints,write=FHints};
    __property   int    Tag={read=FTag,write=FTag};

};

//---------------------------------------------------------------------------
// Node의 MetaClass 정보 연결 함수 
//---------------------------------------------------------------------------
void __fastcall TForm1::TreeViewCreateGroupNodeClass(TCustomTreeView *Sender, TTreeNodeClass &NodeClass)
{
     NodeClass = __classid(TGroupNode);
}
void __fastcall TForm1::TreeViewCreatePersonNodeClass(TCustomTreeView *Sender, TTreeNodeClass &NodeClass)
{
    NodeClass = __classid(TPersonNode);
}


//---------------------------------------------------------------------------
// TreeView에 Node 추가 
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    //
    TGroupNode *GNode;
    TPersonNode *PNode;
    for(int i=0;i<3;i++)
    {
        //Group-Node추가  
        TreeView1->OnCreateNodeClass=TreeViewCreateGroupNodeClass;
        GNode=(TGroupNode *)TreeView1->Items->Add(NULL,"Group"+IntToStr(i));
        GNode->GroupName="그룹["+IntToStr(i)+"]";
        GNode->Data=(void *)1;

        //Group-Node의 Child에 Person-Node 추가 
        TreeView1->OnCreateNodeClass=TreeViewCreatePersonNodeClass;
        for(int i=0;i<10;i++)
        {
            PNode=(TPersonNode *)TreeView1->Items->AddChild(GNode,"사람"+IntToStr(i));
            PNode->Name="사람"+IntToStr(1);
            PNode->Address="대한 민국  서울";
            PNode->PhoneNumber="010-1234-5678";
            PNode->Tag=i+1;
        }
    }
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// GroupNode 정보 활용
//---------------------------------------------------------------------------
void __fastcall TForm1::PmGroupNameClick(TObject *Sender)
{
    if(TreeView1->Selected==NULL || TreeView1->Selected->Level!=0)return;
    TGroupNode *GNode=(TGroupNode *)TreeView1->Selected;
    ShowMessage(GNode->GroupName );
}


//---------------------------------------------------------------------------
// Person-Node의 정보 활용
//---------------------------------------------------------------------------
void __fastcall TForm1::PmAddressClick(TObject *Sender)
{
    if(TreeView1->Selected==NULL || TreeView1->Selected->Level<1)return;
    TPersonNode *PNode=(TPersonNode *)TreeView1->Selected;
    ShowMessage(PNode->Address);

}
//---------------------------------------------------------------------------
void __fastcall TForm1::PmPhoneNumberClick(TObject *Sender)
{
    if(TreeView1->Selected==NULL || TreeView1->Selected->Level<1)return;
    TPersonNode *PNode=(TPersonNode *)TreeView1->Selected;
    ShowMessage(PNode->PhoneNumber);
}
//---------------------------------------------------------------------------

     



그럼... 


다음 그림과 같기 rtf형식의 문자열이 Memo에 들어있는데..

이놈을 어떻게 RichEdit 에 넣을까?


[안되는 방법들..]

다음과 같은 방법으로 해서는 양식적용이 안된다.

안되는 방법1. 

    RichEdit->Lines->Assign( Memo1->Lines) ; 


안되는 방법2

   RichEdit->Lines->Text = Memo1->Lines->Text  ;


위 두가지 방법은 

그냥 text형식으로 richedit에 들어간다.


[되는 방법들..]


RichEdit에서 rtf 형식을 판단하는 부분은 

Stream으로 읽을때 하고 있다.

 파일스트림, 메모리스트림, 리소스 스트림등...


1. 리소스에 넣었다가 리소스 스트림으로 읽어오는 방법

  void __fastcall TForm1::Button2Click(TObject *Sender)
 {
TResourceStream *ResSt;
try
{
 ResSt = new TResourceStream((int)HInstance, L"RTF_DOCUMENT",L"RTF");
 RichEdit1->Lines->LoadFromStream(ResSt);
}
__finally
{
 delete ResSt;
}
 }

2. 파일에서 읽어오는 방법

   RichEdit1->Lines->LoadFormFile("파일명");

   //확장자가 rtf 가 아니라도 잘 판단해서 읽어온다.


3. 메모장의 내용을 Stream으로  내보냇다가 읽어오기 

void __fastcall TForm1::Button4Click(TObject *Sender)
{

TMemoryStream *ms=new TMemoryStream;
Memo1->Lines->SaveToStream(ms);
ms->Position=0;
RichEdit1->Lines->LoadFromStream(ms);
delete ms;
}


4.  문자열을 Stream에 넣어서 RichEdit로 ...

void __fastcall TForm1::Button6Click(TObject *Sender)
{

AnsiString sText=Memo1->Lines->Text;
TMemoryStream *ms=new TMemoryStream;
ms->WriteBuffer(sText.c_str(),sText.Length());
ms->Position=0;
RichEdit1->Lines->LoadFromStream(ms);
delete ms;
}


5. Unnicode문자열인 경우엔 다음과 같이..

void __fastcall TForm1::Button5Click(TObject *Sender)
{

String sText=Memo1->Lines->Text;
TEncoding* Encoding= TEncoding::Default;
TBytes Buffer=Encoding->GetBytes(sText);
TMemoryStream *ms=new TMemoryStream;
ms->WriteBuffer(&Buffer[0],Buffer.Length);
ms->Position=0;
RichEdit1->Lines->LoadFromStream(ms);
delete ms;
}


유니코드인경우엔..
정확한 메모리크기가 바로 나오지 않기때문에
Encoding 정보를 이용해서 길이를 파악하여서 Stream으로 밀어 넣어야 한다.


주의할점

Richedit에서 Stream을 읽을대 현재 Stream->Position부터 읽어들인다.

즉 RicheEdit로 LoadFormStream하기 전에 반드시 Stream의 Position을 0 (초기위치)으로 셋팅을 해줘야 한다.



그럼...

  1. ofalv 2012.04.03 13:00 신고

    다양한 방법이 있군요

프로그램에서
Print-Screen으로 화면 캡쳐를 막는 class 입니다.

문론 원천적으로 프로그램의 캡쳐를 완전히 막는 방법은 없습니다.
최소한의 캡쳐방비용 기능입니다.


  
#include <ClipBrd.hpp>
// ���
// http://msdn.microsoft.com/en-us/magazine/cc163713.aspx
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279(v=vs.85).aspx
// http://labnol.blogspot.com/2004/08/disable-print-screen-key-in-windows.html
//---------------------------------------------------------------------------
class TSnapShotBlock
{
    HWND m_hWnd[4];
    bool m_bBlock;
public:
    TSnapShotBlock()
    {
        m_bBlock=false;
        for(int i=0;i<4;i++)
        {
            m_hWnd[i]=AllocateHWnd(AllHwndWinProc);
        }
    }
    ~TSnapShotBlock()
    {
        SetSnapshotBlock(false);
        for(int i=0;i<4;i++)
        {
            DeallocateHWnd(m_hWnd[i]);
        }
    }
    //----------------------------------------------
    void __fastcall AllHwndWinProc(TMessage &Msg)
    {
        if(Msg.WParam==IDHOT_SNAPDESKTOP || Msg.WParam==IDHOT_SNAPWINDOW)
        {
            Clipboard()->Clear();
        }
    }
    //----------------------------------------------

    void __fastcall SetSnapshotBlock(bool bBlock)
    {
        m_bBlock=bBlock;
        if(bBlock)
        {
            RegisterHotKey(m_hWnd[0], IDHOT_SNAPDESKTOP,0, VK_SNAPSHOT);
            RegisterHotKey(m_hWnd[1], IDHOT_SNAPDESKTOP,MOD_CONTROL, VK_SNAPSHOT);
            RegisterHotKey(m_hWnd[2], IDHOT_SNAPDESKTOP,MOD_SHIFT, VK_SNAPSHOT);
            RegisterHotKey(m_hWnd[3], IDHOT_SNAPDESKTOP,MOD_WIN, VK_SNAPSHOT);

            RegisterHotKey(m_hWnd[0], IDHOT_SNAPWINDOW, MOD_ALT, VK_SNAPSHOT);
        }
        else
        {
            UnregisterHotKey(m_hWnd[0], IDHOT_SNAPDESKTOP);
            UnregisterHotKey(m_hWnd[1], IDHOT_SNAPDESKTOP);
            UnregisterHotKey(m_hWnd[2], IDHOT_SNAPDESKTOP);
            UnregisterHotKey(m_hWnd[3], IDHOT_SNAPDESKTOP);
            UnregisterHotKey(m_hWnd[0], IDHOT_SNAPWINDOW);
        }
    }

};
TSnapShotBlock g_SnapshotBlk;
     

 
 
사용방법은?

  
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    g_SnapshotBlk.SetSnapshotBlock(true);

    Application->OnMinimize=AppMinimize;
    Application->OnRestore=AppRestore;
}
//---------------------------------------------------------------------------
//프로그램이 minimize될때 snapshot을 허용하도록 한다.  
void __fastcall TForm1::AppMinimize(TObject *Sender)
{
    g_SnapshotBlk.SetSnapshotBlock(false);
}
//---------------------------------------------------------------------------
//프로그램이 Restore될때 snapshot을 막는다
void __fastcall TForm1::AppRestore(TObject *Sender)
{
    g_SnapshotBlk.SetSnapshotBlock(true);
}
     
 
....

Application이 Minimize되었을때는 Print-Screen을 허용하도록 한것입니다.


그럼..
 

음.. 현재 ICT 의 LogLib에는 다음과 같은 함수를 이용해 LogPrintf하고 있다.


void __cdecl XfAddIctLogFormat( int line, char *func, char*file,AnsiString type, char * lpszFormat, ...)
{
    int nBuf;
    char acLogMsg[4096];
    va_list args;
    va_start(args, lpszFormat);
    nBuf = vsnprintf(acLogMsg ,4095, lpszFormat, args);
    va_end(args);
    if( nBuf <1)return;
    XfAddIctLog(line,func,file,type, sLogMsg); // 파일에 기록
}


그런데 위 함수에는 문제점이 하나 있다.

충분히 Buffer를 확보해서 4 KByte까지  로그메세지를 기록할수 있지만
메세지 길이가 4KByte넘어가면 뒤로는 문자열이 짤리게 된다.


[ _vsctprintf ]
다음 링크에 보면
vsprintf로 문자열 조합을 하기 전에 문자열 길이를 확인할수 있는 방법이 있다.
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=8369&ref=8369 

_vsctprintf  라는 함수를 이용하여 조합될 문자열의 길이를 확인하고
문자열 길이가 크면 동적으로 메모리를 확보해서 printf 하는 방식이다.

_vsctprintf  -  http://msdn.microsoft.com/en-us/library/w05tbk72(VS.71).aspx

 

[C++Builder에서 
_vsctprintf 선언되어있지 않음]

C++Builder에서 위 _vsctprintf 를 써보려고 했더니..
stdio에 선언되어있지 않다? 엥?

 
 
[해결 방안]

C++Builder에서는 String 클래스에 printf 기능이 있다.
 
이미 cdecl 함수로 넘어온 가변 파라메터를 String클래스 멤버 함수로 넘기려면..

printf 대힌 vprintf 를 쓰면 된다.
 


void __cdecl XbfAddIctLogFormat( int line, char *func, char*file,AnsiString type, char * lpszFormat, ...)
{
   AnsiString sLogMsg;
    va_list args;
    va_start(args, lpszFormat);
    int iLen=sLogMsg.vprintf(lpszFormat,args);  // va_list 를 넘기는 함수 
    va_end(args);
    if(iLen<1)return;

    XbfAddIctLog(line,func,file,type, sLogMsg);
}
//----------------------------------------------------------------------------

 
C++Builder에서는 _vsctprintf   함수가 export 되어있지는 않지만
String클래스의 vprintf를 쓰면 얼마든지 킨 문자열도 조합이 가능하다.

그럼..
 

+ Recent posts