Perl call 규약 - C에서 Perl 서브루틴 호출하기

Perl call 규약 - C에서 Perl 서브루틴 호출하기 (perlcall)

이 문서의 목적은 C에서 Perl 서브루틴을 직접 호출하는 방법, 즉 **콜백(callback)**을 작성하는 방법을 보여주는 것이에요.

콜백 작성용 C 인터페이스 논의 외에도, 이 문서는 일련의 예제로 인터페이스가 실제로 어떻게 동작하는지 보여줘요. 그리고 콜백 코딩 기법 몇 가지도 다뤄요.

출처: perldoc - perlcall 사전 필독: perlxs, perlguts

콜백이 필요한 예

  • 오류 핸들러 (Error Handler): 애플리케이션의 C API에 대한 XSUB 인터페이스를 만들었는데, 애플리케이션에서 흔히 어떤 나쁜 일이 생겼을 때 호출될 C 함수를 정의할 수 있게 해줘요. 우리가 원하는 것은 대신 호출될 Perl 서브루틴을 지정할 수 있게 하는 것이에요.
  • 이벤트 구동 프로그램 (Event-Driven Program): 콜백의 전형적인 예는 X11 애플리케이션 같은 이벤트 구동 프로그램 작성이에요. 특정 이벤트(마우스 버튼 누름, 커서가 창 안으로 이동, 메뉴 항목 선택 등)가 발생할 때 호출될 함수를 등록해요.

여기서 설명하는 기법은 C 프로그램에 Perl을 임베드할 때도 적용되지만, 이 문서의 주요 목적은 아니에요. 임베딩에는 별도로 고려할 세부 사항이 많아요. C에 Perl 임베딩에 대한 자세한 내용은 perlembed를 참고해요.

THE CALL_ FUNCTIONS

먼저 몇 가지 중요한 정의를 알아야 해요.

Perl에는 Perl 서브루틴을 호출하게 해주는 C 함수들이 몇 개 있어요.

I32 call_sv(SV* sv, I32 flags);
I32 call_pv(char *subname, I32 flags);
I32 call_method(char *methname, I32 flags);
I32 call_argv(char *subname, I32 flags, char **argv);

핵심 함수는 call_sv예요. 다른 모든 함수는 특별한 경우에 Perl 서브루틴을 더 쉽게 호출하게 해주는 단순 래퍼예요. 결국 모두 Perl 서브루틴을 호출하기 위해 call_sv를 호출해요.

모든 call_* 함수에는 Perl에 옵션 비트 마스크를 전달하는 flags 파라미터가 있어요. 이 비트 마스크는 각 함수에서 동일하게 동작해요. 마스크에서 사용 가능한 설정은 FLAG VALUES 섹션에서 논의해요.

각 함수를 차례로 살펴볼게요.

  • call_sv: 두 파라미터를 받아요. 첫 번째 sv는 SV*예요. 호출할 Perl 서브루틴을 (먼저 SV로 변환된) C 문자열이나 서브루틴 참조로 지정할 수 있게 해줘요. Using call_sv 섹션에서 call_sv 사용법을 보여줘요.
  • call_pv: call_sv와 비슷하지만 첫 파라미터가 호출하려는 Perl 서브루틴을 식별하는 C char*이어야 해요. 예: call_pv("fred", 0). 다른 패키지의 서브루틴이면 문자열에 패키지 이름을 포함해요. 예: "pkg::fred".
  • call_method: Perl 클래스에서 메서드를 호출하는 데 쓰여요. methname 파라미터가 호출할 메서드의 이름이에요. 메서드가 속한 클래스는 파라미터 목록이 아니라 Perl 스택으로 전달된다는 점을 주의해요. 이 클래스는 (정적 메서드의) 클래스 이름이거나 (가상 메서드의) 객체 참조일 수 있어요. 정적·가상 메서드에 대한 정보는 perlobj, 예제는 Using call_method 섹션을 참고해요.
  • call_argv: subname 파라미터의 C 문자열로 지정된 Perl 서브루틴을 호출해요. 평소의 flags 파라미터도 받아요. 마지막 파라미터 argv는 Perl 서브루틴에 인자로 전달될 NULL로 끝나는 C 문자열 리스트예요. Using call_argv 섹션 참고.

모든 함수는 정수를 반환해요. 이것은 Perl 서브루틴이 반환한 항목의 개수예요. 실제 반환된 항목은 Perl 스택에 저장돼요.

일반 규칙으로 항상 이 함수들의 반환값을 확인해야 해요. Perl 서브루틴에서 특정 개수의 값만 반환될 거라 기대해도, 누군가 예상 밖의 일을 하는 것을 막을 수는 없어요 — 경고했다는 걸 잊지 마세요.

FLAG VALUES

모든 call_* 함수의 flags 파라미터는 호출 컨텍스트를 나타내는 G_VOID, G_SCALAR, G_LIST 중 하나에, 아래 정의된 다른 G_* 기호들의 어떤 조합이라도 OR된 비트 마스크예요.

G_VOID

void 컨텍스트에서 Perl 서브루틴을 호출해요. 두 가지 효과가 있어요:

  1. 호출되는 서브루틴에게 void 컨텍스트에서 실행 중임을 알림 (wantarray 실행 시 결과는 undef).
  2. 서브루틴에서 실제로 아무것도 반환되지 않도록 보장.

call_* 함수가 반환하는 값은 Perl 서브루틴이 반환한 항목 수를 나타내는데, 이 경우 0이에요.

G_SCALAR

스칼라 컨텍스트에서 Perl 서브루틴을 호출해요. 모든 call_* 함수의 기본 컨텍스트 플래그 설정이에요. 두 가지 효과:

  1. 서브루틴에게 스칼라 컨텍스트에서 실행 중임을 알림 (wantarray 결과는 false).
  2. 서브루틴에서 스칼라 하나만 실제로 반환되도록 보장. 물론 서브루틴은 wantarray를 무시하고 리스트를 반환할 수 있어요. 그러면 리스트의 마지막 요소만 반환돼요.

call_* 함수가 반환하는 값은 0 또는 1이에요. 0이면 G_DISCARD 플래그를 지정한 것이고, 1이면 Perl 서브루틴이 실제로 반환한 항목이 Perl 스택에 저장돼요 (Returning a Scalar 섹션이 스택에서 이 값에 접근하는 방법을 보여줘요). 서브루틴이 몇 개를 반환하든 마지막 것만 스택에서 접근 가능하다는 것을 기억하세요 — 하나의 값만 반환되는 경우를 원소 하나짜리 리스트로 생각하세요. 반환된 다른 항목들은 call_* 함수에서 제어가 돌아올 때쯤에는 존재하지 않을 거예요.

G_LIST

리스트 컨텍스트에서 Perl 서브루틴을 호출해요. Perl 5.35.1 이전에는 G_ARRAY라고 불렸어요. G_SCALAR와 마찬가지로 두 가지 효과:

  1. 서브루틴에게 리스트 컨텍스트에서 실행 중임을 알림 (wantarray 결과는 true).
  2. 서브루틴에서 반환된 모든 항목이 call_* 함수에서 제어가 돌아올 때 접근 가능하도록 보장.

call_* 함수가 반환하는 값은 Perl 서브루틴이 반환한 항목 수예요. 0이면 G_DISCARD를 지정한 것이고, 0이 아니면 서브루틴이 반환한 항목 수이며 이 항목들은 Perl 스택에 저장돼요.

G_DISCARD

기본적으로 call_* 함수는 Perl 서브루틴이 반환한 항목을 스택에 놓아요. 이 항목들에 관심이 없다면 이 플래그를 설정하면 Perl이 자동으로 제거해줘요. G_SCALAR나 G_LIST로 서브루틴에 컨텍스트를 나타내는 것은 여전히 가능해요.

이 플래그를 설정하지 않으면 임시값들(Perl 서브루틴에 전달된 파라미터와 서브루틴이 반환한 값)을 직접 처분해야 한다는 것이 매우 중요해요. Returning a Scalar 섹션이 임시값 명시 처분 방법을, Using Perl to Dispose of Temporaries 섹션이 문제를 무시하고 Perl에게 맡겨도 되는 특정 상황을 논의해요.

G_NOARGS

call_* 함수 중 하나로 Perl 서브루틴을 호출할 때마다 기본적으로 파라미터가 전달된다고 가정돼요. Perl 서브루틴에 어떤 파라미터도 전달하지 않는다면 이 플래그를 설정해 약간의 시간을 절약할 수 있어요. 이 플래그는 Perl 서브루틴의 @_ 배열을 만들지 않는 효과가 있어요.

이 플래그의 기능은 단순해 보이지만, 정당한 이유가 있을 때만 사용해야 해요. 조심해야 하는 이유는 G_NOARGS를 지정했더라도 호출된 Perl 서브루틴이 파라미터를 받았다고 생각할 수 있기 때문이에요.

실제로 일어날 수 있는 일은, 호출된 Perl 서브루틴이 이전 Perl 서브루틴의 @_ 배열에 접근할 수 있다는 것이에요. 이것은 call_* 함수를 실행하는 코드 자체가 다른 Perl 서브루틴에서 호출됐을 때 발생해요. 아래 코드가 이를 보여줘요:

sub fred
  { print "@_\n"  }

sub joe
  { &fred }

&joe(1,2,3);

이것은 1 2 3을 출력해요. 일어난 일은 fredjoe 소유의 @_ 배열에 접근한 것이에요.

G_EVAL

호출하는 Perl 서브루틴이 비정상적으로 종료할 수 있어요 — 예를 들어 명시적으로 die를 호출하거나 실제로 존재하지 않는 경우예요. 기본적으로 이런 사건이 발생하면 프로세스가 즉시 종료돼요. 이런 유형의 이벤트를 잡으려면 G_EVAL 플래그를 지정해요. 이것은 서브루틴 호출 주위에 eval { }를 놓아요.

call_* 함수에서 제어가 돌아올 때마다 일반 Perl 스크립트에서처럼 $@ 변수를 확인해야 해요.

반환값은 어떤 다른 플래그를 지정했는지와 오류가 발생했는지에 따라 달라져요. 가능한 경우는:

  • call_* 함수가 정상적으로 반환하면 이전 섹션에서처럼 반환.
  • G_DISCARD를 지정하면 반환값은 항상 0.
  • G_LIST를 지정하고 그리고 오류가 발생하면 반환값은 항상 0.
  • G_SCALAR를 지정하고 그리고 오류가 발생하면 반환값은 1이고 스택 맨 위 값은 undef가 되어요. 즉 $@를 확인해 오류를 이미 감지했고 프로그램을 계속하고 싶다면 스택에서 undef를 pop하는 것을 기억해야 해요.

G_KEEPERR

위의 G_EVAL 플래그는 항상 $@를 설정해요 (오류 없으면 지우고, 오류 있으면 설명으로 설정). 오류를 처리하려는 의도라면 원하는 동작이지만, 때로는 오류를 잡아 나머지 프로그램을 방해하지 않게 하고 싶을 때가 있어요.

이 시나리오는 대부분 소멸자(destructor), 비동기 콜백, 시그널 핸들러 안에서 호출되도록 만들어진 코드에 적용돼요. 호출되는 코드가 주변 동적 컨텍스트와 거의 관련이 없는 상황에서, 지능적으로 처리할 수 없더라도 메인 프로그램은 호출된 코드의 오류로부터 격리되어야 해요. __DIE____WARN__ 훅, tie 함수 코드에도 유용할 수 있어요.

G_KEEPERR 플래그는 이런 코드를 구현하는 데 사용되는 call_* 함수에서 G_EVAL과 함께 쓰거나, eval_sv와 함께 쓰도록 만들어졌어요. G_EVAL을 사용하지 않으면 이 플래그는 call_* 함수에 아무 효과가 없어요.

G_KEEPERR을 사용하면 호출된 코드의 어떤 오류도 평소처럼 호출을 종료하고 (G_EVAL에서 평소처럼) 오류가 호출 너머로 전파되지 않지만, $@에는 들어가지 않아요. 대신 오류가 "\t(in cleanup)" 문자열이 붙은 경고로 변환돼요. 이것은 no warnings 'misc'로 끌 수 있어요. 오류가 없으면 $@는 지워지지 않아요.

G_KEEPERR 플래그는 내부 eval로 전파되지 않는다는 점을 주의해요; 이것들은 여전히 $@를 설정할 수 있어요.

G_KEEPERR 플래그는 Perl 5.002에서 도입됐어요.

컨텍스트 결정 (Determining the Context)

위에서 언급했듯, 현재 실행 중인 서브루틴의 컨텍스트는 Perl에서 wantarray로 결정할 수 있어요. C에서 동등한 테스트는 GIMME_V 매크로로 하는데, 리스트 컨텍스트면 G_LIST, 스칼라 컨텍스트면 G_SCALAR, void 컨텍스트면 G_VOID(반환값이 사용되지 않음)를 반환해요. deprecated 된 이 매크로의 옛 버전은 GIMME라고 불러요; void 컨텍스트에서 G_VOID 대신 G_SCALAR를 반환해요. GIMME_V 매크로 사용 예는 Using GIMME_V 섹션에 있어요.

EXAMPLES

정의 얘기는 이쯤 하고 몇 가지 예제를 볼게요.

Perl은 Perl 스택 접근을 돕는 많은 매크로를 제공해요. 가능하면 Perl 내부와 인터페이스할 때 항상 이 매크로를 사용해야 해요. 이렇게 하면 미래의 Perl 변경에 코드가 덜 취약해지길 바라요.

또 하나 주목할 점은 첫 번째 예제 시리즈에서 call_pv 함수만 사용했다는 것이에요. 코드를 단순하게 유지하고 주제에 천천히 익숙해지도록 한 것이에요. 가능하면 call_pvcall_sv 중에서 고를 때 항상 call_sv를 사용해야 해요. Using call_sv 섹션 참고.

파라미터 없음, 반환 없음 (No Parameters, Nothing Returned)

이 첫 번째 예제는 프로세스의 UID를 출력하는 Perl 서브루틴 PrintUID를 호출할 거예요.

sub PrintUID
{
    print "UID is $<\n";
}

그리고 호출하는 C 함수는:

static void
call_PrintUID()
{
    dSP;

    PUSHMARK(SP);
    call_pv("PrintUID", G_DISCARD|G_NOARGS);
}

단순하죠?

SEE ALSO

perlxs, perlguts, perlembed 문서를 함께 읽어 보세요.