Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-17 00:00
관리 메뉴

nomad-programmer

[Programming/C] 콜백 함수 본문

Programming/C

[Programming/C] 콜백 함수

scii 2020. 6. 16. 03:22

모든 프로그래머가 완제품 형식의 프로그램을 만들지는 않는다.

프로그래머는 자신의 코드가 노출되면 안되기 때문에 해당 코드를 컴파일해서 라이브러리(*.lib) 형식의 파일로 제공한다. 그리고 라이브러리 안에 있는 함수들이 어떤 형태로 선언된 함수인지 알아야 코드를 자세히 볼 수 없는 사용자들도 사용할 수 있기 때문에 함수의 원형들을 헤더(*.h) 파일에 적어서 함께 제공한다.

예를 들어 두 개의 정수 값을 넘겨 받아서 합산하는 Sum 함수를 라이브러리 형태로 제공한다고 가정하면, 라이브러리 사용자에게는 파일 내부를 볼 수 없는 라이브러리 파일 sum.lib와 라이브러리 파일을 설명하는 헤더 파일 sum.h를 모두 제공해야 한다.

// 헤더 파일 sum.h
// sum 함수의 원형
int sum(int a, int b);
// 라이브러리 파일 sum.lib
int sum(int a, int b) {
    return a + b;
}
// 사용자가 사용하는 형태
#include "sum.h"
#pragma comment(lib, "sum.lib")

void main() {
    int result = sum(2, 3);
}
#pragma 전처리기를 사용하면 컴파일러의 여러 가지 설정 값을 수정할 수 있다. 위와 같이 사용하면 sum.lib 파일을 이 프로그램에서 사용하겠다는 의미

만약 사용자가 매개변수 값이 음수이면 양수로 변환하는 기능을 추가 요청하면 또 다시 작업을 해야 하고 컴파일을 진행해야 한다.

허나 라이브러리에 포함된 함수는 본래의 기능을 유지하고 사용자가 원하는 경우에 스스로 함수의 기능을 일부 수정할 수 있도록 제공한다면 이러한 수고는 없어진다.

이럴때 사용하는 것이 함수 포인터를 이용한 "콜백 함수" 이다.

더보기

변수 이름을 지을 때 변수의 특징을 드러내 주면 다른 사람이 코드를 이해하기 훨씬 수월해진다.

예를 들어 접두사로 p_ 가 붙으면 1차원 포인터, pp_ 가 붙으면 2차원 포인터, fp_ 가 붙으면 함수 포인터

// sum.h
int sum(int a, int b, void (*p_a)(int *), void (*p_b)(int *));
// sum.lib
int sum(int a, int b, void (*p_a)(int *), void (*p_b)(int *)) {
    if(NULL != p_a) {
    	(*p_a)(&a);
    }
    if(NULL != p_b) {
    	(*p_b)(&b):
    }
}
// 사용자
#include "sum.h"
#pragma comment(lib, "sum.lib")

void main() {
    // result에 -6이 저장됨
    int result = sum(-1, -5, NULL, NULL);
}

/* 혹은 */

#include "sum.h"
pragma comment(lib, "sum.lib")

void abs_val(int *ptr) {
	if(*ptr < 0) {
    	*ptr = (*ptr) * -1;
    }
}

void main() {
    // result에 4가 저장됨
	int result = sum(-1, -5, NULL, abs_val);
}

 

함수의 암시적 호출

이렇게 함수 포인터를 사용하면 sum함수는 a, b변수에 대한 어떤 요구 조건이 생겨도 다 처리할 수 있다. 라이브러리에 포함된 sum 함수에 새로운 기능이 필요하더라도 사용자가 라이브러리 프로그래머에게 기능을 추가해 달라는 요구를 하지 않아도 되는 것이다. 왜냐하면 자신이 직접 필요한 함수를 만들어 이 함수의 주소 값을 sum 함수에 매개변수로 전달하면 되기 때문이다.

이런식으로 자신이 사용할 함수가 명시적으로 호출되지 않고 함수 포인터에 의해서 호출되는 방식을 암시적 호출, 즉 "콜백 (Callback)" 이라고 한다. 그리고 암시적으로 호출되는 abs_val 함수를 "콜백 함수" 라고 한다.

암시적 호출이란 구체적으로 함수를 명시하지는 않지만 주어진 상황을 통해 판단하여 함수를 호출하는 방식

 

콜백 함수는 미래를 준비하는 함수

정말 필요한 기능만 함수에 구현하고 나머지 예상되는 조건은 함수 포인터를 사용하여 이 함수를 사용할 사용자들에게 도움을 요청하는 것이 바로 콜백 구조이다.

콜백 구조를 사용하면 이후 어떤 문제가 발생하든 콜백 함수를 사용해서 대처할 수 있다. 그리고 이것이 운영체제가 콜백 구조를 많이 사용하는 이유이다.

 

정적 라이브러리(*.lib) 를 만들어서 콜백 함수를 활용해보는 예제

Create a new project를 클릭하여 정적 라이브러리를 위한 새로운 프로젝트를 만든다.

 

Windows Desktop Wizar를 클릭하고 "Next"를 누른다.

 

적당한 프로젝트와 경로를 입력하고 "Next"를 누른다.

 

Application type을 "Static Library (.lib)" 로 선택한다.

Additinal options는 위와 같은 상태로 설정한다. Empty project를 꼭 해제해야 한다.

Precompiled header를 선택하면 귀찮아지는 것이 있어서 선택을 해제했다.

 

매개 변수로 int형 2개를 받고 함수 포인터 2개를 선언하여 콜백 함수를 받아들일 수 있도록 하였다.

함수 포인터가 NULL이 아니면 함수가 실행되도록 하였다.

[NOTE] 사용자가 *.c 파일로 사용한다면 *.cpp -> *.c 로 확장자를 변경하여 빌드해야 한다. 그렇지 않으면 링크 오류가 발생함

 

정적 라이브러리 파일을 위한 헤더 파일을 정의했다. 이 헤더 파일은 사용자가 라이브러리를 사용하기 위한 함수 도움말 파일로도 사용한다.

 

Release, Debug 중 둘 중 아무거나 해도 상관은 없다만 어떤 파일은 Debug고 어떤 파일은 Release가 되어버리면 오류가 발생할 수 있기 때문에 통일해줘야 한다. 

 

빌드를 완료했다. Release 디렉토리에 static_test.lib 파일이 생성된 것을 볼 수 있다.

 

*.lib 파일과 *.h 파일을 현재 작업중인 프로젝트에 넣는다. 즉 만약 당신이 라이브러리 사용자라면 당신의 작업 디렉토리에 라이브러리와 헤더파일을 옮긴다.

 

전처리기 명령 pragma를 이용해 정적 라이브러리를 사용하겠다고 선언한다.

이렇게 하면 사용자가 콜백 함수를 직접 만들고 정적 라이브러리를 사용할 수 있다.

Comments