Qt는 크로스 컴파일이 가능한 GUI개발 라이브러리이다.

자바처럼 한번의 소스코드 개발로 여러 운영체제에서 동일하게 작동한다. 자바는 컴파일도 한번만 하면 바이트코드 프로그램이 완성되지만..

Qt의 경우는 운영체제 별로 다시 컴파일 해야 한다. 하지만 네이티브로 실행되는 바이너리 파일이 만들어지게 된다..

물론 운영체제별로 약간 다르게 작동 할수 있겠지만 당연히 작동 결과에 대한 차이가 있을 경우에는 API문서에 포함 되어 있다. API문서도 비교적 깔끔하게 잘 정리 되어있다.

특별히 소스 수정없이 여러 운영체제에서 안정적으로 돌아 갈 수 있다.

그리고 오픈소스 프로그램을 개발 할땐 GPL라이센스로 무료로 이용할 수 있다. 상용 라이센스 가격은 정확히 모르겠으나 MS쪽이랑 비슷하거나 약간 더 저렴한 거 같았다..

Qt라이브러리 쓰기위해서 http://trolltech.com/ 로 가서 Qt With MinGW 받아서 깔면 된다.
하지만 여기에는 디자이너 툴은 포함되어있지만, 소스를 편집하기 위한 IDE툴이 없다. 따라서 Eclipse CDT나 다른 프로그램이 필요하다.

하지만 Visual Studio 가 익숙해 VS에서 사용하려면~~?? 인터넷에서 흔하게 Visual Studio에서 개발하고 MS 컴파일러로 컴파일 까지 하는 문서들은 있었지만..MS컴파일러로 컴파일 해야할 이유까지는 없었다.. 근냥 편집툴로만 MS Visual Studio를 쓰고 싶었다..

우선 PATH를지정 해야 한다.
Qt 4.3 Command Prompt 아이콘을 클릭하면 자동으로 패스가 지정된다. 하지만 우리가 원하는 것은 VS에서 Qt를 컴파일 하기 위한 것이므로 내 컴퓨터 등록정보에서 환경 변수를 등록해야 한다..
사용자 삽입 이미지

QTDIR = C:\Qt\4.3.1
PATH =  C:\Qt\4.3.1\bin;C:\MinGW\bin
QMAKESPEC = win32-g++


위와같이 환경 변수를 추가 한다.

Microsoft Visual Studio를 실행 하여
File- New - Project... 메뉴를 선택한다.
사용자 삽입 이미지
Visual C++ 카테고리에서 Makefile Project를 선택 한다.

사용자 삽입 이미지

마법사 대화 상자가 뜨면 우선 Finish를 누른다. 컴파일 명령어는 메뉴에서 다시 지정하도록 합니다.

Project - Property메뉴를 선택한다..
왼쪽 트리에서 Configuration Properties - NMake를 선택합니다.

사용자 삽입 이미지

Build Command Line오른쪽에 있는 [...]버튼을 눌러서 아래와 같이 입력한다.

qmake -project
qmake
make
사용자 삽입 이미지

Rebuild All Command Line라인과  Clean command Line에는 " make clean "등의 명령을 적절히 넣으면된다.

Output은 실행 파일이 생성될 위치이다..
기본적으로 Release 폴더 밑에 폴더 이름으로 실행파일이 생긴다.. 거기에 맞춰 입력 하면 된다.

Output은 "Release\프로젝트명.exe" 이렇게 주면 되다..

기본적인 설정을 마쳤다..

Solution Exploler Tool Windows에서 프로젝트를 선택하고 오른쪽 버튼을 눌러 Add- New Item을 선택한다.
사용자 삽입 이미지

C++ File 을 선택한다..
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
     QApplication app(argc, argv);
     QLabel *label = new QLabel("Hello Qt!");
     label->show();
     return app.exec();
}


위와 같이 한번 입력해보고 컴파일 실행 버튼을 눌러보자... 컴파일과 실행이 되었었을 것이다.

하지만 Qt 라이브러리에 대한 인텔리센스는 작동하지 않을 것이다. 그럴 경우.. Tools에 Options...
Project and Solution - VC++ Directory - Include File에서 Qt관련 include가 있는 곳에 폴더를 지정해 준다..

{QTDIR}\include\ActiveQt
{QTDIR}\include\Qt
{QTDIR}\include\Qt3Support
{QTDIR}\include\QtAssistant
{QTDIR}\include\QtCore
{QTDIR}\include\QtDBus
{QTDIR}\include\QtDesigner
{QTDIR}\include\QtGui
{QTDIR}\include\QtNetwork
{QTDIR}\include\QtOpenGL
{QTDIR}\include\QtScript
{QTDIR}\include\QtSql
{QTDIR}\include\QtSvg
{QTDIR}\include\QtTest
{QTDIR}\include\QtUiTools
{QTDIR}\include\QtXml


위와 같이 폴더를 추가하면 인테리센스가 작동한다. 한결 편하게 작업 할 수 있을 것이다..

신고

멀티 쓰레드를 사용하고 프로그램이 작성가능하다면 좋겠지만, 실제 멀티 쓰레드를 사용하지 않고 프로그램을 작성하게 될경우 매우 복잡하게 되거나 구현이 불가능한 경우가 생기게된다.

예전에 만든 프로그램에서 멀티 쓰레드를 사용하지 않고, 짧은 간격으로 리턴하고, TIMER 이벤트로 호출하는 방법을 쓴적이 있다.

문제는 리턴하기 때문에 현재 수행한 상태를 저장해 두고 리턴 했고, 타이머에 의해서 호출되면 스위치 문에 의해 다시 이전 수행을 이어서 하게 만들었다.

그 뿐만 아니라 프로그램을 읽기가 매우 힘들고 유지하기도 어려웠다.

멀티 쓰레드는 또한 자원의 활용도를 높여 줘 시스템의 성능 또한 높여주게 된다.

//실제 수행 가능한 소스는 아니고 예제 입니다.

#define RUN_FLAG 1;
#define END_FLAG 2;

Class CTest
{
private:
  int m_threadFlag;
  CWinThread* m_threadMacro;
  //동시에 접근 하지 못하도록 막음
  CCriticalSection critical;
public:
  void run(void);
  void stop(void);
  void stopForce(void);
  int getListSize();
  void doSomething(void);
}

//수행 쓰레드를 시작
void CTest::run(void)
{
  m_threadFlag = RUN_FLAG;
  m_threadMacro = AfxBeginThread (macroThread,&*this);
}

//쓰레드를 종료
void CTest::stop(void)
{
  m_threadFlag = END_FLAG;
}

//강제로 쓰레드 종료
void CTest::stopForce(void)
{
  m_threadMacro->SuspendThread();
}

//크릭티칼 섹션이 필요한 경우
void CTest::doSomething(void)
{
  critical.Lock();
  //필요한 작업
  critical.Unlock();
}


//쓰레드 함수
UINT macroThread( LPVOID pParam )
{
  CTest* ct = ((CTest*)pParam);
  
  for(int i = 0; i < ct->geListSize(); i++)
  {
    //플래그가 종료일 경우 return함
    if(ct->getThreadFlag == END_FLAG) return 0;
    ct->doSometing(void);
  }
  
  return 0;
}



UINT macroThread( LPVOID pParam )


쓰레드로 수행될 함수를 전역 함수로 만든다. 파라미터는 LPVOID한개를 가지게 된다.

쓰레드에 넘겨줘야 할 값이 있으면 이 파라미터 한개로 넘겨주면된다. 갯수가 하나이기 여러개의 값을 넘겨주기 위해서는  때문에 구조체나 클래스 객체에 담아서 넘겨줘야 한다.

AfxBeginThread (macroThread,&*this);


아래와 같은 함수를 수행하게 되면 새로운 쓰레드가 생성되고 쓰레드가 시작된다.

반환 값은 CWinThread* 이다.

//플래그가 종료일 경우 return함
if(ct->getThreadFlag == END_FLAG) return 0;

일반적으로 쓰레드가 종료될 필요가 있으면 위와 같이 종료 플래그에 의해서 종료하면 된다.  이런 방식이 불가능할 경우는 아래와 같은 방법을 사용할 수 있다.

m_threadMacro->SuspendThread();


위와 같이 강제로 종료해도 되지만, 위와 같이 강제로 종료하게 될 경우, 쓰레드가 수행 도중 종료되게 된다. 위 예제에서는 doSometing()함수 호출되어 뭔가 하는 도중 일을 끝마치지 못한 상태에서 종료될 수도 있게된다.

이렇게 되더라도 특별한 문제가 없는 경우 위와 같이 종료하면되고, 문제 발생의 소지가 있다면 필요에 따라 조치를 취해줘야 한다.

critical.Lock();
//필요한 작업 Critical Section
critical.Unlock();


동기화가 필요할 경우에는 다음과 같이 동기화를 위해 lock과 unlock이 가능하다.

CCriticalSection critical;


같은 CCriticalSection 객체의 Critical Section에는 하나의 쓰레드만 수행되게 된다. Lock을 걸게 되면 다른 쓰레드에서는 이 부분에서 Unlock()이 될때 까지 기대리게 된다..
신고
소스 코드에서 다음과 같은 매크로를 삽입한다.
#define CRTDBG_MAP _ALLOC

메모리 누수 확인을 위한 부분에 다음과 같은 코드를 삽입한다음
 _CrtDumpMemoryLeaks();

Visual Studio에서 디버그 모드로 수행하면 output윈도우에서 메모리 누수를 확인 할 수 있다.
사용자 삽입 이미지

프로그램이 종료 되면 반환되지 않은 메모리는 운영체제에서 일반적으로 반환 해준다. 하지만 수행도중 메모리 누수가 계속 해서 발생된다면 프로그램은 죽을 수 밖에 없다.

종료시점에 메모리 반환도 필요성이 없지만, 나중에 프로그램이 변경될 시 문제 발생의 가능성을 가지고 있을수도 있으므로 종료시점에서도 메모리 반환 하는 습관을 들이는 것이 좋을 것으로 생각 된다.

C나 C++로 과제를 할 경우 상당히 어렵다. 더디다고 할까나? Visual Studio같은 멋진 개발 툴이 있음에도 생각보다 쉽게 프로그램을 만들 수가 없다.

특히 버그가 있음에도 짧은 프로그램에서는 정상적으로 돌아가는 경우도 있었다.

함수안에서 new로 메모리를 활당하고 delete한후 그 포인터값을 반환하고...
함수를 호출 한 쪽에서 그 포인트로 멤버 변수에 접근 했는데..

컴파일러나 운영체제에 따라 정상 수행되는 경우도 있었다. 일부로 안되는 에러 상황을 만들려고 했었는데.. 정상적으로 수행되어.. 순간 혼란 스러웠던 적도 있다..

얼마전 C로 작성한 과제가 gcc/vc6/vc2007 모두에서 오류/경고 가 없었음에도 불구하고.--; 특정 컴파일러로 만든 실행파일은 실행 안되는 경우가 있었다.. 이런건 경고라도 떠야 뭐가 문제인지 찾지 과제 제출 기한도 바쁜데.. 어떻게 찾으란 말인가??ㅠㅠ;;

그래서 어쩔수 없이 컴파일러 별로 테스트한 후 성공 여부를 맨위에 주석으로 적어서 재출하는 방법을 사용했다.

정말 신경을 쓰고 짜지 않으면 짧은 프로그램이라도 문제가 발생할지 모른다..--;; 아직 스킬이 부족해서 그런거라 생각이 들지만..ㅠㅠ;; 언젠가 프로그래머의 대가 되고 싶다...
신고

김정민님이 어떻게 윈도우를 찾느냐고 물어보셔서~~ 작성합니다.. 많은 도움이 되었으면 좋겠네요..

CMelOnSniperMacro는 매크로를 수행하는 클래스입니다.

이 클래스의 맴버변수로는 변환해야할 파일들 현재 몇번째 파일 변환중인지.. 몇번째 스텝인지 등을 저장하는 변수들이 있습니다.

다이어로그 클래스에서 실제 이 Macro클래스 인스턴스가 만들어집니다.. 타이머에 의해 m_DoMacro맴버 함수가 정해진 시간에 호출 됩니다. 맴버 변수가 가지고 있는 현재 상태를 가지고 매크로를 수행하는 부분입니다.

그 중 윈도우창을 찾는 부분의 소스들입니다. 중복되는것은 생략했습니다.
 

bool CMelOnSniperMacro::m_FindMainWindow(void)
{
 m_wndMelon.m_hWnd = ::FindWindow(TEXT("MelOnFrameV30"),NULL);
 if(m_wndMelon.m_hWnd == false) return false;
 return true;
}

::FindWindow(TEXT("MelOnFrameV30"),NULL); 이 함수는
첫번째 인자로 윈도우의 클래스 이름
두번째 인자는 캡션 이름
으로 윈도우를 찾고 핸들을 반환합니다.. 최상위 윈도우를 찾을 수 있습니다. NULL을 입력 할 수 있습니다.

m_wndMelonCWnd타입입니다.

만약 윈도우 내부에 있는 자식 윈도우를 찾고자 하거나 특정 윈도우 다음에 위치한 윈도우를 얻고자 할때 는
FindWindowEx(HWND, HWND, LPCWSTR, LPCWSTR);함수를 사용하시면 됩니다..
첫번째 인자는 부모 윈도우의 핸들
두번째 인자는 인자 윈도우에 다음에 위치하는 윈도우 핸들을 얻어 옵니다.
세번째 인자는 클래스명
네번쨰 인자는 캡션 명입니다..

물론 NULL값을 인자로 넘겨 줄 수 있습니다.

int CMelOnSniperMacro::m_DoMacro()
{
 
 switch(m_nStep)
 {
  case 2://찾아보기창 띄우기
  //컨버팅 변환창에서 파일 찾기 버튼 누르기
  {
   CWnd* wndDCFConverting =m_findWindow(TEXT("#32770"), _T(""), 530,455);
   if(wndDCFConverting->m_hWnd != NULL){
    wndDCFConverting->PostMessage(WM_COMMAND,0x836,0x0);
    m_nStep++;
   }
   delete(wndDCFConverting);
  }
  break;
 }
 return 0;
}

그러나 멜론 3.0으로 업데이트 되면서 대부분의 윈도우에 캡션이 없습니다. 특이 윈도우 디자인에 이미지가 많은 경우 대부분 FindWindow로 한번에 찾기 어렵습니다..

또한 클래스명 또한 다이얼로그 박스에 기본값인 #32770입니다. 따라서 같은 클래스명과 이름의 윈도우가 수개 내지 수십개가 됩니다.

그래서 윈도우를 구별하기위해 윈도우 크기를 가지고 윈도우를 찾는 함수 m_findWindow라는 함수를 구현 했습니다... 물론 이 방법은 윈도우크기가 고정되어 바꿀 수 없는 윈도우에 한해서 쓰일 수 있는 방법입니다.. 윈도우 크기가 바뀔수 있는 경우라면 또 다른 방법을 생각 해봐야겠죠..

CWnd* wndDCFConverting =m_findWindow(TEXT("#32770"), _T(""), 530,455);

윈도우를 받아 옵니다.

    wndDCFConverting->PostMessage(WM_COMMAND,0x836,0x0);

혹시 윈도우를 찾지 못할 수도 있으므로 if문을 사용 찾은 경우에만 원하는 메세지를 찾은 창에 보냅니다. 위 다른 윈도우에 명령을 보내는 방법은 PostMessageSendMessage함수 가 있습니다.

PostMessage는 비동기로 메세지를 보내고 SendMessage는 동기로 메세지를 보냅니다..

이 함수의 인자, Msg와 wParam, lParam의 값은 Spy++이나 별도의 유틸을 통해서 쉽게 얻을 수 있습니다. WM_COMMAND, 0x836, 0x0은 멜론 스나이퍼에서 [파일찾기] 버튼을 눌러주는 역활을 합니다.

아래가 윈도우의 크기로 창을 얻어오는 함수의 실제 구현입니다.

CWnd* CMelOnSniperMacro::m_findWindow( CString strClass, CString strWindow, int height, int width)
{
 CWnd* findWindow = new CWnd();
 
 do{  
  findWindow->m_hWnd = ::FindWindowEx(NULL,findWindow->m_hWnd,strClass, strWindow);
  RECT r;
  //창의 크기로 윈도우 판별함
  findWindow->GetWindowRect(&r);
  int thisHeight = r.bottom-r.top;
  int thisWidth = r.right - r.left;
  if(thisHeight == height && thisWidth==width){
   return findWindow;
  }

 }while(findWindow->m_hWnd != NULL);

 findWindow->m_hWnd = NULL;
 return findWindow;
}


findWindow->m_hWnd = ::FindWindowEx(NULL,findWindow->m_hWnd,strClass, strWindow);
우선 가장 처음 do{}while()반복무을 수행할 경우 findWindow->m_hWndNULL입니다.. 따라서 해당 클래스이름과 윈도우명을 가진 첫번째 윈도우를 찾습니다..

그 다음 윈도우크기를 비교해 크기까지 동일하면 우리가 찾으려 했던 윈도우이므로~~ 해당 윈도우를 반환하고..

만약 윈도우 크기가 다르면 다음 윈도우를 찾아야합니다.

do{}while()반복문이 처음이 두번째 수행되는 것이라면  findWindow->m_hWnd가 이전에 찾았던 윈도우입니다.

FindWindowEx에서 두번째 인자가 이 다음 윈도우를 찾아라 하는 인자입니다.. 그럼으로 두번째 해당윈도우 클래스명과 이름을 가지는 두번째 윈도우를 찾게 되는 것입니다.
findWindow->m_hWnd = ::FindWindowEx(NULL,findWindow->m_hWnd,strClass, strWindow);

계속 해서 찾다가 더이상 다음 윈도우가 없을경우~~ null을 반환하고 while()문을 빠져 나오게 됩니다..
신고


VC6.0에서는 기본 설치 옵션이 유니코드를 사용하지 않는다. 따라서 설치시 유니코드를 사용하도록 옵션을 지정해주거나, 이미 설치를 하였다면 설치시디의

E:\VC98\MFC\LIB

에서 라이브러파일을 모두 복사해온다.

Project-Setting-C/C++탭에서 Processor Defination:항목의 _MBSC를 _UNICODE로 바꿔준다.

Link탭으로 이동한후 Category DropDown List에서 Output을 선택한후 Entry-point Symbol의 값에wWinMainCRTStartup를 입력하면된다.

Shared DLL을 사용할경우 DLL파일을 열수 없다는 에러가 뜨네요.. Static linked libary를 사용하는게 편할것 같네요..
신고

멜론 스나이퍼를 만들면서~~ 한참 고민했던문제 중에 하니이다..

기존에 쓰던 코드를 그대로 가져와서 VC++ 2007 붙혀 넣었더니 예전까지만해도 잘 작동하던 코드가 에러를 발생하였다.. 왠지 찾아보니 2007버젼은 유니코드를 기본으로 쓰기 때문이였다.

VC++로 프로그래밍을 그래도 가끔씩 사용하는데 항상 어려운것이 문자열을 어떻게 형변환해서 원하는 형으로 쓰는가이다.. 아직도 이거 잘모르겠다..

이것 한번 해보고 저거한번 두어번 바꿔봐도 에러로 안되면.. 검색을 해보는데 설명해놓은 글도 쉽사리 이해가 안되고.. 이해되더라도 금방까먹어버린다.

우리학교 과정이 JAVA위주로 되어있어 자주 쓰지 않기때문에~~ 지금도 VC++로 작업하면 언제나 변수 타입에서 골머리를 싸매게 된다..

멜론 스나이퍼를 만들면서 알게된사실~~ 함수뒤에 W가 붙어 있는 함수는 유니코드용 함수라는 사실~~.. 그런데~~ W가 붙어 있지 않는 함수도
#define FindWindowEx FindWindowExW

이렇게~~ 해놔서~~ 기본옵션은 항상 유니코드를 쓰도록 되어있었다.

유니코드를 사용해 윈도우의 창을 찾는 FindWindowExW함수를 이용할때
 LPCWSTR로 창의 클래스명이나 캡션명을 넘겨줘야 한다..
FindWindowExW(HWND, HWND, LPCWSTR, LPCWSTR);
 
이때..
FindWindowExW(hWnd,null, "문자열","문자열");
이런식으로 넘겨 주면 문자열이 깨져서~~ 못찾는다..

이때는
FindWindowExW(hWnd,null, TEXT("문자열"),TEXT("문자열"));
로 문자열을 유니코드로 변경해줘야 한다.

CString 변수를 넘겨 줄때는 별다른 형변환 없이 자동으로 casting 되는것 같았다.

다음 찾은 텍스트 박스의 CWnd를 통해 원하는 문자열을 찍는 방법이다.

CString str = m_strFileList.GetAt(m_nTime);
m_wndText->SendMessage(WM_SETTEXT,NULL,(LPARAM)(char *)(LPCTSTR)(str) );

(LPARAM)(char *)(LPCTSTR)
이렇게 형변환을 해주어야 했다..

아직도 잘모르겠다..~~ 형변환이 너무 어렵다~~ 이거 잘할 수 있고 완벽하게 이해될려면 얼마나 걸릴려나~~

학교에서는 전혀~~ VC++로 하는 수업을 들어본적이 없고.. 근냥 프로그램 만들때 필요한 거만 검색으로 찾아서 작업하다보니~~ 이해하고 있는게 없다.. 그렇다고 책을 한권 본것도아니고~~

인터넷에 있는 강의 한번본게 다라서~~ 위 내용이 확실히 맞는지도 모르겠다.. VC++어렵다~~

조금더 체계적으로 배워야 될텐데....

신고


티스토리 툴바