Ruby 개발자라면 한 번쯤 이런 궁금증이 생기곤 합니다.
Ruby에서 FFI란 무엇일까요?
FFI는 "Foreign Function Interface(외부 함수 인터페이스)"의 약자입니다.
쉽게 말해 다른 프로그래밍 언어로 작성된 함수를 사용할 수 있게 해주는 통로입니다.
Ruby의 FFI 모듈을 활용하면 평소에는 접근하기 어려운 외부 라이브러리와 코드를 자유롭게 사용할 수 있습니다.
특히 C 코드와 함께 작업할 때 널리 활용됩니다.
FFI를 사용하는 대표적인 젬(gem)은 다음과 같습니다:
- rb-inotify — 파일 시스템 이벤트 감지
- ruby-vips — 고속 이미지 처리
- ruby-nfc — NFC 태그 읽기
물론 FFI 모듈의 도움을 받아 직접 FFI 기반 프로그램을 만들 수도 있습니다.
어떻게 할 수 있을까요?
지금부터 몇 가지 코드 예제를 통해 차근차근 살펴보겠습니다!
FFI 기본기: 라이브러리 로딩과 함수 임포트
FFI는 직접 생성한 모듈 안에 FFI의 기능들을 포함시키는 방식으로 사용합니다.
다음과 같이 말이죠:
require 'ffi' module A extend FFI::Library end
그다음, 모듈 내부에서 ffi_lib 메서드를 사용해 라이브러리를 불러옵니다.
C 라이브러리는 Windows의 "DLL"이나 Linux의 "SO" 파일처럼, 프로그램이 외부에 공개하는 함수 정의 목록, 즉 "심볼(symbol)"을 내보냅니다.
여기서는 C의 표준 라이브러리인 "libc"를 불러와 보겠습니다.
방법은 다음과 같습니다:
module A extend FFI::Library ffi_lib 'c' end
이제:
attach_function 메서드를 사용하면 이 라이브러리의 특정 함수를 Ruby 메서드처럼 가져올 수 있습니다.
예제를 보겠습니다:
module A
extend FFI::Library
ffi_lib 'c'
attach_function :strlen, [:string], :int
end
A.strlen("abc")
# 3
이 코드는 어떻게 동작할까요?
먼저, C에는 strlen이라는 함수가 있고, 이 함수는 char * 타입의 인자를 받습니다.
char *는 Ruby에서 String에 해당합니다.
그리고…
strlen 함수는 문자열의 길이를 반환값으로 돌려줍니다.
정리하면, attach_function은 3개의 매개변수를 받습니다:
- C 함수의 이름
- 인자 타입들의 배열
- 반환 타입
각 함수가 어떤 타입을 사용하는지는 해당 라이브러리의 문서를 참고해서 확인해야 합니다.
이제 다른 예제로 넘어가 볼까요?
나만의 미디어 플레이어 만들기
FFI로 활용할 수 있는 흥미로운 라이브러리는 무궁무진합니다.
Linux에서는 대부분 /usr/lib/ 디렉터리 아래에 있으며, 이름이 "lib"으로 시작하고 확장자가 "so"로 끝납니다.
다음 명령어로 목록을 확인할 수 있습니다:
ls /usr/lib/lib*.so
이번 예제에서는 VLC의 미디어 플레이어 라이브러리를 사용해 보겠습니다:
/usr/lib/libvlc.so
목표는 MP3 파일을 불러와서 재생하는 것입니다!
먼저 여기서부터 시작해 봅시다:
require 'ffi' module VLC extend FFI::Library ffi_lib 'vlc' attach_function :libvlc_get_version, [], :string end
이렇게 하면 라이브러리가 제대로 로드되었는지 테스트하고 버전을 출력해 볼 수 있습니다.
실행 결과:
VLC.libvlc_get_version # "3.0.6 Vetinari"
네 번째 매개변수를 추가하면 메서드에 원하는 이름을 붙일 수도 있습니다.
예를 들면:
attach_function :get_version, :libvlc_get_version, [], :string
이제 이렇게 호출할 수 있습니다:
VLC.get_version
그럼 MP3를 재생하려면 무엇이 필요할까요?
문서를 살펴보면 libvlc_media_player_play 함수가 바로 그 역할을 하는 것 같습니다.
이 함수는 미디어 플레이어 "객체"가 필요합니다.
미디어 플레이어는 미디어를 필요로 하고, 미디어는 다시 VLC 인스턴스를 필요로 합니다.
즉, 전체 과정은 다음과 같습니다:
VLC instance -> media -> media_player -> play
이 구조는 libVLC 공식 문서를 읽으며 파악한 것입니다.
Ruby에서 메모리 포인터를 다룬다고?
C 언어에는 객체라는 개념이 없습니다. 모든 것은 메모리 포인터로 처리됩니다.
메모리 포인터란 데이터가 담겨 있는 메모리 주소를 의미합니다.
코드 예제를 통해 확인해 보겠습니다:
module VLC extend FFI::Library ffi_lib 'vlc' attach_function :get_version, :libvlc_get_version, [], :string attach_function :new, :libvlc_new, [:int, :int], :pointer end VLC.new(0, 0) # FFI::Pointer address=0x000055c6f04ae7b0
VLC.new를 호출하면 포인터가 반환됩니다.
이 포인터는 이후 포인터를 필요로 하는 다른 C 함수들에 계속 전달해야 합니다.
자, 이제:
전체 예제와 실제 사용 방법을 살펴보겠습니다.
module VLC extend FFI::Library ffi_lib 'vlc' attach_function :version, :libvlc_get_version, [], :string attach_function :new, :libvlc_new, [:int, :int], :pointer attach_function :libvlc_media_new_path, [:pointer, :string], :pointer attach_function :libvlc_media_player_new_from_media, [:pointer], :pointer attach_function :play, :libvlc_media_player_play, [:pointer], :int attach_function :stop, :libvlc_media_player_stop, [:pointer], :int attach_function :pause, :libvlc_media_player_pause, [:pointer], :int end
이 정도면 음악을 재생할 준비가 끝났습니다:
vlc = VLC.new(0, 0) media = VLC.libvlc_media_new_path(vlc, "/home/jesus/Downloads/meditation.mp3") player = VLC.libvlc_media_player_new_from_media(media) VLC.play(player)
이 코드는 어떤 시각적 인터페이스도 열지 않고 음악을 재생합니다.
여기에 웹 인터페이스를 얹으면 나만의 음악 플레이어 서비스를 만들 수도 있습니다.
직접 한번 도전해 보세요!
마치며
지금까지 FFI의 놀라운 힘에 대해 알아보았습니다! 마치 Ruby 언어 자체의 일부인 것처럼 외부 라이브러리를 자연스럽게 사용할 수 있게 해주는 강력한 모듈입니다.
더 많은 사람들이 이 글을 즐길 수 있도록 공유 부탁드립니다 🙂
읽어주셔서 감사합니다!