Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

C++빌더를 사용하여, 정적 라이브러리 (.Lib)와 동적 (런-타임) DLL을 만들고 사용하는 방법


Recommended Posts

Al Mannarino"Create and use Static Library (.Lib) and Dynamic DLLs in C++ Builder" 를 번역했습니다. (원문 작성: 2023년 9월, 최종 번역: 2023년 9월)

목차


개요

이 글은 정적 라이브러리 (.Lib)와 동적 (런-타임) DLL을 C++빌더을 사용하여 만드는 방법과 사용하는 방법을 설명한다. 라이브러리(library)란 미리-컴파일된(pre-compiled) 코드의 집합체이며, 프로그램에서 재사용(re-use)할 수 있는 것을 말한다. 라이브러리에는 2 가지 유형 즉, 정적(static) 라이브러리와 동적(dynamic) 라이브러리가 있다.

정적 라이브러리(.lib)와  동적 라이브러리(.dll) 비교

정적 라이브러리(.LIB 파일)(또는 아카이브)에는 사용자의 프로그램에 연결이 컴파일 시점(compile time)에 되는 코드가 들어있다. 생성된 실행 파일은 해당 라이브러리 코드의 복사복을 보관한다.

동적 라이브러리(.dll)(또는 공유 라이브러리)에는 설계 단계에서 이미 여러 프로그램들이 공유할 수 있도록 된 코드가 들어있다. 이 라이브러리의 내용은 실행 시점(runtime)에 메모리에 적재(load)된다. 해당 라이브러리는 실행 파일 안에 복제되어 들어가지 않는다.

정적 라이브러리와  동적 라이브러리의 차이점은?

정적 라이브러리(.lib)는 컴파일 시점에 특정 프로그램에게 고정된다. (여러 프로그램에서 재사용될 수 있지만, 그 경우에도 해당 프로그램은 각각 정적 라이브러리를 복제하여 자신만의 것으로 만든다)

동적 라이브러리(.dll)는 공유 라이브러리이다. 따라서 독립 파일이며, 실행 파일 바깥에 존재한다.

C++빌더로, 동적 라이브러리(.DLL)와 정적 라이브러리(.LIB)를 만들고 사용하는 방법 튜토리얼

C++빌더에서  동적 라이브러리(.DLL)를 만들고 사용해보자.
참고로, C++빌더 11.3을 사용하여 작업한다.

C++빌더로, 동적 라이브러리(.DLL) 만들기

1. File | New | Other | C++ Builder | Dynamic Library를 선택한다. 그리고, Source type = C++, Multi-Threaded = 체크(활성화), Target Framework = No Framework로 선택한다.

spacer.png

2. 프로젝트가 생성되면 새 폴더에 저장하기 위해, File | Save Project As...를 선택한다. CppMyDLL 라는 새 폴더를 만들어 다음 경로를 완성한다(C:\Users\amannarino\Documents\Embarcadero\Studio\Projects\CppMyDLL), 이 폴더 안에 파일을 저장할 때, 프로젝트 이름을 CppMyDLL.cbproj로 변경하고, Unit1.cpp 이름을 CppMyDLL.cpp로 변경하여 저장한다.

3. 생성된 CppMyDLL.cpp 파일 안에는 아래 코드가 들어 있을 것이다:

extern C int _libmain(unsigned long reason)
{
  return 1;
}

_libmain 함수는 이 동적 라이브러리의 진입점(entry point)이다. 즉, 이 동적 라이브러리가 적재(load) 될 때, 이 _libmain 함수가 호출된다. 이 함수에는 보통 초기화(initialization) 코드가 들어간다.

4. 이 DLL과 이것을 사용하는 클라이언트인 .EXE를 어떤 컴퓨터에서든 (C++빌더가 설치되지 않아도) 사용할 수 있으려면, Project | Options로 가서, C++ Linker를 선택하고, Link with Dynamic RTL = False로 설정한다. 그리고 Packages | RunTime Packages를 선택하고, Link with Run Time Packages = False로 선택한다

5. 애플리케이션을 빌드한다(Project | Build). 빌드와 링크 모두 성공할 것이다.

6. 소스 코드 출력(output) 폴더 (C:Users/amannarino/Documents/Embarcadero/Studio/Projects/CppMyDLL/Win32/Debug) 안에 CppMyDLL.dll (동적 라이브러리)와 CppMyDLL.lib (정적 라이브러리)가 들어 있을 것이다. 정적 라이브러리는 (.lib) 동적 라이브러리(.dll)로부터 자동으로 생성된 것이다.
 
7. DLL 안에 있는 내용을 보는 무료 유틸리티로는 Dependency Walker가 있다. Dependency Walker는 무료 유틸리티이며, 32-비트 또는 64-비트 윈도우 모듈(exe, dll, ocx, sys, 등등)을 스캔하고, 계층형 트리 도표를 만들어, 의존하고 있는 모든 모듈을 보여준다. Dependency Walker를 다운로드하려면 https://www.dependencywalker.com/로 가면 된다.

8. 이제, CppMyDLL.cpp 안에 함수를 하나 추가해보자.

double Sum(double a, double b) {
  return a +b;
}

dllFunction-5759209.PNG?w=481&ssl=1

참고: DLL 안에는 클라이언트에게 숨겨지는 함수와 클라이언트에 노출되는 함수가 들어 있다.

9. 이 Sum 함수를 클라이언트가 볼 수 있도록 하기 위해, __declspec(dllexport)를 추가한다. 다음과 같다.

double __declspec(dllexport) Sum(double a, double b) {
  return a + b;
}


10. Dependency Walker를 사용하여 Sum 함수를 보면, @Sum$add라고 보인다.

2-4377835.png?resize=1024,384&ssl=1

 @와 (이 함수에 대한 설명인)$add를 제거하기 위해, extern “C”를 이 함수에 추가한다. 다음과 같다.

extern "C" double __declspec(dllexport) Sum(double a, double b) {
  return a + b;
}

11. extern "C"가 추가 된 프로젝트를 다시 빌드한다. 그리고 Dependency Walker를 사용하여 Sum 함수를 보면, _Sum라고 표시된다.

3-7086264.png?resize=1024,383&ssl=1

12. 함수 이름에서 밑줄(_)을 제거하기 위해, 함수에 __stdcall을 추가한다. 그 함수가 표준 호출 규약(standard calling convention)을 사용한다고 말해주는 것이다. 다음과 같다.

extern "C" double __declspec(dllexport) __stdcall Sum(double a, double b) {
  return a + b;
}

13. __stdcall이 추가된 프로젝트를 다시 빌드한다. 그리고 Dependency Walker를 사용하여 Sum 함수를 보면, Sum이라고 표시된다.

4-5016827.png?resize=500,238&ssl=1

이제, 동적 링크 또는 정적 링크를 하면, Sum 이라는 함수를 사용할 수 있다. @Sum$add처럼 복잡한 이름을 쓰지 않아도 된다.

 

C++빌더로, 이 DLL을 호출(사용)하는 VCL 애플리케이션을 C++빌더로 만들기

14. 이제, C++빌더로  VCL 애플리케이션을 만들어서, 이 DLL을 호출(사용)할 수 있다. 프로젝트 매니저 창에서 ProjectGroup1 위에서 오른쪽-클릭을 하고,  Add New Project를 선택하고, C++ Builder, Windows VCL Application을 선택한다.

15. 새 프로젝트가 생성되면, File | Save Project As를 통해 DLL 프로젝트와 동일한 폴더(C:\Users\amannarino\Documents\Embarcadero\Studio\Projects\CppMyDLL)에 새 프로젝트를 저장하는데, 프로젝트 이름은 ClientDLL.cbproj로 바꾸고, Unit1.cpp는 uClient.cpp로 바꾸고 저장한다.

Client-7755089.PNG?resize=257,250&ssl=1

16. 이 텅빈 VCL 폼 애플리케이션을 빌드한다. 그러면 /Win32/Debug 폴더에 파일이 생성된다.(프로젝트의 출력 디렉토리의 기본 설정이  .$(Platform)$(Config)이기 때문이다. 참고로, 이 경로는 Project | Options에서 변경할 수도 있다).
spacer.png

이제, 이 폴더 안에는 ClientDLL.exe, CppMyDLL.dll, CppMyDLL.LIB 파일이 생겼을 것이다. 눈여겨 볼 점은 .dll (동적)과 .lib (정적) 파일이 둘 다 생겼다는 점이다.

17. VCL 애플리케이션 프로젝트 (ClientDLL.cbproj)에서, Project | Options로 가서 C++ Shared Options의 Final output directory = .$(Platform)$(Config)로 설정되어 있는지 확인하고 SAVE를 클릭한다. (기본 설정이 그렇게 되어 있다) 

18. VCL 애플리케이션 프로젝트 (ClientDLL.cbproj)에서, Project | Options로 가서 C++ Linker를 선택하고, Link with Dynamic RTL = False로 선택하고 Save를 클릭한다.

19. VCL 애플리케이션 프로젝트 (ClientDLL.cbproj)에서, Project | Options로 가서 Packages | Runtime Packages를 선택하고, Link with runtime packages = False로 선택하고 Save를 클릭한다.

설정을 이렇게 선택하면, 필요한 모든 종속성을 실행 파일(.EXE)에 추가한다. 따라서 어떤 컴퓨터에서도 실행할 수 있다. C++빌더가 설치되지 않은 컴퓨터라도 말이다. 🙂

20. 이제, CppMyDLL.LIB 파일에 대한 정적 링크하기를 시도해 보자. 정적 링크하기는 애플리케이션이 처음 기동(start-up)할 때 DLL이 필요한 경우에 사용하는 방식이다. 정적 링크를 하려면, CppMyDLL.LIB 파일을 실행 파일(.EXT)의 검색 경로(search path)에 추가해야 한다. 이 .LIB 파일에는 해당 .DLL 파일 안에 있는 모든 정보가 담겨있다.

VCL 애플리케이션 프로젝트(ClientDLL)에서 Project | Add to Project로 가서,  CppMyDLL.LIB 파일을 찾아 선택한 다음, Open을 클릭한다.

LIB-4817007.PNG?resize=768,289&ssl=1

프로젝트 매니저 창을 보면, VCL 애플리케이션 프로젝트(ClientDLL) 안에 CppMyDLL.LIB 파일이 추가된 것을 볼 수 있다.

Lib_Add-7888187.PNG?resize=249,199&ssl=1

참고: .LIB 파일에는 해당 .DLL 파일 안에 있는 함수들에 대한 정보가 들어 있다. 즉, 해당 .DLL에 대한 설명인 것이다.

21. 이제 VCL 애플리케이션 프로젝트(ClientDLL) 안에 .LIB 파일을 추가했으니, 이 Client 애플리케이션에 그 함수 프로토타입(Function prototype)을 넣어주어야 한다. uClient.cpp에서 다음 코드를 넣는다.

extern "C" double __declspec(dllexport) __stdcall Sum(double a, double b);

이 함수 프로토타입은 CppMyDLL.cpp에서 복사할 수 있다. 복사한 코드를 Client 애플리케이션에서 붙여 넣으면 된다. 

또는, 위 함수 프로토타입을 담은 헤더 파일(.h)을 만들고, 그 헤더 파일을 Client 애플리케이션에서 인클루드(include)하면 된다.

22. 이제, Client 애플리케이션에는 이 함수의 프로토타입이 들어갔고, 해당 .LIB 파일도 추가되었으니, SUM 함수를 호출 할 수 있다.

23. uClient.cpp (Client 애플리케이션) 안에서 버튼을 하나 추가하고, Caption에 Call Sum Function이라고 지정한다.

ClientBtn-8900740.PNG?resize=192,125&ssl

24. 이 버튼의 OnClick 이벤트의 핸들러에서, 아래 코드를 추가한다.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  ShowMessage(Sum(4,7));
}

25. 이 Client 애플리케이션을 빌드하고 실행한다. 그리고, 버튼을 클릭하면, 다음 화면을 보게 된다.

CallSum-2414721.PNG?resize=177,183&ssl=1

축하한다. 방금, 우리가 만든 애플리케이션에서 정적 링크하기를 수행하여 CppMyDLL.LIB를 사용했다!
Sum 함수를 호출한 결과는 4 + 7 = 11이다.

참고: 이 Client 애플리케이션에는 Sum 함수의 본문이 없다. 그저 그 함수의 프로토타입만 있을 뿐이다. 그 본문은 .DLL 파일 안에 들어있다. 따라서, Client 애플리케이션이 기동(start-up)할 때, 이 .DLL 파일을 사용할 수 있어야만 한다. 그래야 이 정적 링크하기가 작동하기 때문이다. 

이 Client 애플리케이션이 해당 .DLL을 찾을 수 없다면, 다음과 같은 에러가 발생한다.

MissDll-8008947.PNG?w=419&ssl=1

26. 이제는 런-타임 (동적) 링크하기를 구현해보자.

동적 링크하기를 할 때는, .LIB 파일이 필요없다. 즉 함수 프로토타입이 필요없다. 하지만, 해당 함수의 프로토타입을 주석 안에 남겨두는 것이 좋다. 그러면 이 함수가 어떻게 호출되는 지를 다른 사람도 알 수 있기 때문이다.

동적 링크를 하려면, Button Click 이벤트 핸들러에서 다음과 같은 코드를 사용해야 한다.

void __fastcall TForm1::Button1Click(TObject* Sender)
{
// 정적 링크하기 (Static Linking):
// 함수 프로토타입과 해당.LIB 파일을 쓰면, Sum 함수를 호출할 수 있다:
    //    ShowMessage(Sum(4,7));
 
// 동적 링크하기 (Dynamic Linking):
// 아래와 같은 코드가 있어야, 런-타임(실행 시점)에 링크를 할 수 있다
// 런-타임 (동적, Dynamic) 링크를 하려면,
// 해당 DLL을 손수 적재(load) 하기 위해 LoadLibrary를 사용한다. 그리고,
// 호출하고자 하는 함수를 찾기 위해 GetProcAddress를 사용한다.
    HINSTANCE CppMyDll;
    typedef double(__stdcall * pfSum)(double, double);
    pfSum Sum;
 
    if ((CppMyDll = LoadLibraryW(L"CppMyDLL.dll")) == NULL) {
        ShowMessage(L"DLL을 적재할 수 없습니다!");
        return;
    }
    if ((Sum = (pfSum)GetProcAddress(CppMyDll, "Sum")) == NULL) {
        ShowMessage(L"DLL 함수를 찾을 수 없습니다!");
        return;
    }
        ShowMessage(Sum(25, 75));
        FreeLibrary(CppMyDll);
    }
}

동적 링크하기에서는, 위와 같은 코드를 사용하여 Run-Time Linking(실행 시점 링크하기)를 할 수 있다. 런-타임 (동적, Dynamic) 링크를 사용하면, 해당 DLL을 손수 적재(load) 하기 위해 LoadLibrary를 사용한다. 그리고, 호출하고자 하는 함수를 찾기 위해 GetProcAddress를 사용한다. 그리고 나면, Sum 함수를 호출할 수 있다. 이 함수를 사용하고 나서 더이상 이 DLL이 필요하지 않은 경우, FreeLibrary(MyDLL)를 호출하여 그 DLL을 해제(free)한다.

27. 동적 링크하기는 .LIB) 파일이 더 이상 필요하지 않으므로, 이 프로젝트에서 CppMyDll.LIB 파일을 제거하자.

프로젝트 매니저 창에서, CppMyDll.lib를 선택하고, 오른쪽-클릭을 한 후 Remove from Project를 선택한다.

RemoveLib-1686852.PNG?resize=275,257&ssl

RemoveLib1-1958183.PNG?resize=275,158&ss

Yes를 클릭하면 이 프로젝트에서 CppMyDll.Lib이 빠진다.

28. 이 애플리케이션을 빌드하고 실행한다. 그리고 DLL을 동적으로 링크하는 버튼을 클릭하면 다음과 같은 결과를 볼 수 있다.

spacer.png

축하한다! DLL 안에 있는  Sum 함수를 동적 링크하기 방식으로 호출했다!

29. 마지막으로, 프로잭트 매니저 창에서, ProjectGroup1 위를 오른쪽-클릭하고, 이 프로젝트 그룹을 CppDLL_PG라고 저장한다.

SavePG-1245928.PNG?w=457&ssl=1

 

요약

요약하면, C++빌더는 정적 라이브러리 링크와 동적(실행 시점) 라이브러리 링크를 모두 지원한다.

추가 자료, C++빌더에서 동적 패키지(.Lib)를 구축하는 방법에 대한 자세한 내용:
https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Building_Static_Packages

추가 자료, C++빌더에서 DLL(.dll)을 생성하는 방법에 대한 자세한 내용:
https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Creating_DLLs_in_C%2B%2BBuilder

무료 30일 평가판을 받아서 직접 동적 라이브러리와 정적 라이브러리를 만들고 사용해 보기 바란다.
C++빌더 평가판 다운로드: https://www.embarcadero.com/products/cbuilder/start-for-free
 

이 댓글 링크
다른 사이트에 공유하기

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.