정적라이브러리 .lib : 빌드과정도중 링크단계에서(런타임 이전) 실행파일과 연결이된다.

동적라이브러리 .dll : 프로그램 실행도중(런타임 이후) 참조

 

정적라이브러리

-장점-

- 런타임 과정에서 변경되는 부분이 없기 때문에 빠른 실행 속도를 보장

 

-단점-

- 매번 재빌드를 해야하기 때문에 메모리를 더 차지하게된다.

- 컨텍스트 스위칭이 빈번하여 메인 메모리를 더 사용하게된다.

 

동적라이브러리

-장점-

- 컨텍스트 스위칭이 정적라이브러리에 비해 비교적 적게 일어나기 때문에 메모리 비중이 적다.

 

-단점-

- 외부 의존도가 발생하기 때문에 이식성이 어렵다.

- 성능저하로 인해 속도가 느려질수있다.

- 동적 라이브러리는 정적 라이브러리에 비해 사용이 복잡하기 때문에

  공유 라이브러리를 메모리에 올리려면 해당 라이브러리를 찾고 올리는데까지 시간이 걸린다.

'Programming > DLL' 카테고리의 다른 글

DLL 파일 내보내기  (0) 2021.01.27

DirectX SDK를 설치하고 <d3dx9.h>, <d3d9.h> 헤더파일을 include하고 빌드를 하면

위와 같이 빌드는 되지만 Warning c4005 매크로 재정의 경고가 나올 것이다.

 

이유는 WIndowsSDK와의 충돌 떄문이다.

 

해결 방법은 다음과 같이 프로젝트 속성 -> VC++ 디렉터리 -> 포함디렉터리에

WindowsSDK를 DirectX SDK 보다 먼저 포함시키면 된다.

 

이렇게 설정을 하고나서 빌드를 하면 매크로 재정의 경고가 사라지게 된다.

MFC로 프로젝트를 만들 때는 다음과 같이 프로젝트 템플릿에서 MFC 앱을 클릭해야한다.

그리고 프로젝트 이름, 경로 설정 후에 다음을 누르면 애플리케이션의 종류가 보일 것이다.

 

애플리케이션의 종류는 다음과 같다.

단일 문서 : 뷰 클래스를 기반으로 응용 프로그램에 대 한 단일 문서 인터페이스 (SDI) 아키텍처를 만듦

여러 문서 : 뷰 클래스를 기반으로 응용 프로그램에 대 한 여러 문서 MDI (인터페이스) 아키텍처를 만듦

대화 상자 기반 : 대화 상자 클래스를 기반으로 하는 응용 프로그램에 대 한 대화 상자 기반 아키텍처를 만듦

여러 최상위 문서 : 뷰 클래스를 기반으로 응용 프로그램에 대 한 여러 최상위 아키텍처를 만듦

 

그 다음 프로젝트 스타일을 설정해야한다.

일반적으로는 MFC 기본 스타일로 만든다.

 

현재 자신이 만들 프로젝트가 어떤 것인지 확인하고,

그것에 맞게 애플리케이션 종류와 프로젝트 스타일을 선택하고 만들면된다.

이전에 UnitTool Part2를 이어서 ListBox에 추가된 정보를 저장 및 불러오기를 진행하려고한다.

 

먼저 다음과 같이 버튼을 추가하고 이벤트 처리 함수를 추가해준다.

 

void CUnitTool::OnBnClickedSave()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
}

void CUnitTool::OnBnClickedLoad()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
}

 

#1. 저장

 

MFC에서는 정보를 저장할 때 CFileDialog()라는 클래스를 사용한다.

docs.microsoft.com/ko-kr/cpp/mfc/reference/cfiledialog-class?f1url=%3FappId%3DDev14IDEF1%26l%3DKO-KR%26k%3Dk(AFXDLGS%252FCFileDialog);k(CFileDialog);k(DevLang-C%252B%252B);k(TargetOS-Windows)%26rd%3Dtrue&view=msvc-160

 

CFile Dialog 클래스

CFile Dialog 클래스CFileDialog Class 이 문서의 내용 --> 파일 열기 또는 파일 저장 작업에 사용되는 공통 대화 상자를 캡슐화합니다.Encapsulates the common dialog box that is used for file open or file save operations. 구

docs.microsoft.com

  CFileDialog Dlg(FALSE,       
	L"dat",
	L"*.dat",
	OFN_OVERWRITEPROMPT,
	L"Data File(*.dat) | *.dat||",
	this);

해당 클래스를 사용하면 기본 확장자를 따로 지정해줄 수 있다.

 

첫 번째 인자는 FALSE이면 저장 TRUE이면 불러온다는 의미이다.

 

두 번째 인자(dat)는 기본 확장자 설명 문자열이다.

 

세 번째 인자(*.dat)는 Save 버튼을 눌렀을 때 뜨는 저장 화면에서 파일 이름에 기본적으로 *.dat 파일로 지정해준다.

 

OFN_OVERWRITEPROMPT는 저장하는 대화상자에서 선택한 파일이 이미 있을 경우 파일을 덮어씌울 것인지 물어본다.

 

L"Data File(*.dat) | *.dat||"는 파일 형식 부분에 나올 텍스트와 실제로 사용될 필터이다.

여기서 | 과 ||이 있는데, 이것은 |는 caption, ||는 사용될 확장자를 의미한다.

만약 마지막에 ||가 아닌 |가 붙을 경우 화면에는 *.dat이라고 써져있지만 실제로는 default 값으로 지정되어

다른 파일 형식으로 저장 될 수가 있다. 그렇기 때문에 필터의 경우 가장 마지막에는 ||로 표시를 해줘야한다.

 

마지막 this는 부모 윈도우를 정해주는 부분이다.

 

위와 같이 코드를 정의해주면 결과는 다음과 같이 나오게된다.

이제 현재 작업을 하고 있는 경로를 가져와서

TCHAR szPath[MAX_PATH] = L"";
GetCurrentDirectory(MAX_PATH, szPath);
PathRemoveFileSpec(szPath);
lstrcat(szPath, L"\\Data");
Dlg.m_ofn.lpstrInitialDir = szPath;

GetCurrentDirectory를 통해 MAX_PATH의 사이즈만큼, szPath라는 변수에 현재 작업중인 경로를 저장한다.

그리고 PathRemoveFileSpec을 통해서 파일명이 포함된 경로에서 파일명을 제외한 경로만 남겨준다.

여기서 저장을 할 폴더가 따로 있을 경우 lstrcat을 통해서 해당 폴더 경로를 넣어주면 다음과 같이 나오게된다.

마지막 Dlg.m_ofn.lpstrInitialDir는 Save 버튼을 눌렀을 때 처음에 나오는 시작 경로를 변경해줄 때 사용한다.

기본적으로는 문서 폴더로 되어있지만 lpstrInitialDir을 통해서 다음과 같이 시작 경로를 변경 해줄 수 있다.

여기까지 진행이 완료되었으면, 이제 Tool에서 저장한 정보들을 따로 파일로 저장을 해야한다.

if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName(); 
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패"); 
			return; 
		}
		DWORD dwByte = 0; 
		DWORD dwStringSize = 0; 
		for (auto& rPair : m_mapUnitInfo)
		{
			dwStringSize = sizeof(wchar_t) * (rPair.second->strName.GetLength() + 1); 
			WriteFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			WriteFile(hFile, rPair.second->strName.GetString(), dwStringSize, &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iAtt, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iDef, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byItem, sizeof(BYTE), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byJop, sizeof(BYTE), &dwByte, nullptr);
		}
                
		CloseHandle(hFile); 
	}

Dlg.Domodal() 함수는 대화 상자를 열어주는 함수, 즉 우리가 Save 버튼을 눌렀을 때 나오는 창을 열어준다.

 

대화 상자를 닫아줄 때는 EndDialog()라는 함수가 있지만 저장 or 취소 버튼을 누르면 자동으로 닫히기 때문에

특별한 경우를 제외하고는 사용하지 않는다.

 

해당 함수 내에서 파일의 통로를 개방하여 저장하고 싶은 정보들을 저장하고,

대화 상자에서 저장하기를 누르면 정보가 저장이 되면서 파일의 통로가 닫히고 해당 함수가 종료된다.

 

정리된 코드는 다음과 같다.

void CUnitTool::OnBnClickedSave()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    CFileDialog Dlg(FALSE,       
	L"dat",
	L"*.dat",
	OFN_OVERWRITEPROMPT,
	L"Data File(*.dat) | *.dat||",
	this);

	TCHAR szPath[MAX_PATH] = L"";
	GetCurrentDirectory(MAX_PATH, szPath);
	PathRemoveFileSpec(szPath);
	lstrcat(szPath, L"\\Data");
	Dlg.m_ofn.lpstrInitialDir = szPath;
	if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName(); 
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패."); 
			return; 
		}
		DWORD dwByte = 0; 
		DWORD dwStringSize = 0; 
		for (auto& rPair : m_mapUnitInfo)
		{
			dwStringSize = sizeof(wchar_t) * (rPair.second->strName.GetLength() + 1); 
			WriteFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			WriteFile(hFile, rPair.second->strName.GetString(), dwStringSize, &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iAtt, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iDef, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byItem, sizeof(BYTE), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byJop, sizeof(BYTE), &dwByte, nullptr);
		}
		CloseHandle(hFile); 
	}
}

 

#2. 불러오기

 

저장한 파일을 불러오기 전에 먼저 map 컨테이너를 비워주고,

ResetContent()함수를 통해ListBox를 초기화 시켜준다.

for (auto&  rPair: m_mapUnitInfo)
	Safe_Delete(rPair.second); 
m_mapUnitInfo.clear(); 
m_ListBox.ResetContent(); 

저장할 때와 같이 Load 버튼을 눌렀을 때 불러올 폴더 경로를 지정해준다.

불러오기 이므로 CFileDialog의 첫 번째 인자는 TRUE로 해준다.

CFileDialog Dlg(TRUE,
	L"dat",
	L"*.dat",
	OFN_OVERWRITEPROMPT,
	L"Data File(*.dat) | *.dat||",
	this);
    
TCHAR szPath[MAX_PATH] = L"";
GetCurrentDirectory(MAX_PATH, szPath);
PathRemoveFileSpec(szPath);
lstrcat(szPath, L"\\Data");
Dlg.m_ofn.lpstrInitialDir = szPath;

Dlg.Domodal() 함수를 통해 불러오기 창을 열어주고 해당 .dat 파일을 통해 얻어오는 정보를 빈 공간에 담아준다.

여기서 중요한 부분은 무조건 정보를 저장 했을 때와 똑같은 순서대로 불러와야한다. 

 

예를 들어

이름 -> 공격력 -> 방어력 -> 아이템 소지 여부 -> 직업

이 순서대로 저장을 하였으면 그 순서와 똑같이 불러와야 한다.

이유는 만약 저장한 순서대로 불러오지 않으면 저장한 값이 서로 바뀌기 때문이다.

 

if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName();
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패.");
			return;
		}
		DWORD dwByte = 0;
		DWORD dwStringSize = 0;
		UNITINFO* pUnitInfo = nullptr; 
		while (true)
		{
			pUnitInfo = new UNITINFO;
			ReadFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			TCHAR* pTemp = new TCHAR[dwStringSize]; 
			ReadFile(hFile, pTemp, dwStringSize, &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iAtt, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iDef, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byItem, sizeof(BYTE), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byJop, sizeof(BYTE), &dwByte, nullptr);

			if (0 == dwByte)
			{
				Safe_Delete(pUnitInfo);
				if (pTemp)
				{
					delete[] pTemp; 
					pTemp = nullptr; 
				}
				break; 
			}
			pUnitInfo->strName = pTemp;
			m_mapUnitInfo.emplace(pUnitInfo->strName, pUnitInfo); 
			m_ListBox.AddString(pUnitInfo->strName);
		}
		CloseHandle(hFile);
	}

 

정리된 코드는 다음과 같다.

void CUnitTool::OnBnClickedLoad()
{
	for (auto&  rPair: m_mapUnitInfo)
		Safe_Delete(rPair.second); 
	m_mapUnitInfo.clear(); 
	m_ListBox.ResetContent(); 

	
	CFileDialog Dlg(TRUE,
		L"dat",
		L"*.dat",
		OFN_OVERWRITEPROMPT,
		L"Data File(*.dat) | *.dat||",
		this);
	
	TCHAR szPath[MAX_PATH] = L"";
	GetCurrentDirectory(MAX_PATH, szPath);
	PathRemoveFileSpec(szPath);
	lstrcat(szPath, L"\\Data");
	Dlg.m_ofn.lpstrInitialDir = szPath;
	if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName();
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패.");
			return;
		}
		DWORD dwByte = 0;
		DWORD dwStringSize = 0;
		UNITINFO* pUnitInfo = nullptr; 
		while (true)
		{
			pUnitInfo = new UNITINFO;
			ReadFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			TCHAR* pTemp = new TCHAR[dwStringSize]; 
			ReadFile(hFile, pTemp, dwStringSize, &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iAtt, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iDef, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byItem, sizeof(BYTE), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byJop, sizeof(BYTE), &dwByte, nullptr);

			if (0 == dwByte)
			{
				Safe_Delete(pUnitInfo);
				if (pTemp)
				{
					delete[] pTemp; 
					pTemp = nullptr; 
				}
				break; 
			}
			pUnitInfo->strName = pTemp;
			m_mapUnitInfo.emplace(pUnitInfo->strName, pUnitInfo); 
			m_ListBox.AddString(pUnitInfo->strName);
		}
		CloseHandle(hFile);
	}
}

+ Recent posts