2021/08/16(月) [n年前の日記]
#1 [ruby] Ruby の sfml-audio-fiddle が動かない
Rubyでogg再生ができるらしい拡張ライブラリ、sfml-audio-fiddle が正常動作しなくて悩んでいるところ。
_sfml-audio-fiddleの紹介 - Qiita
_sfml-audio-fiddle | RubyGems.org | コミュニティのGemホスティングサービス
_bggd/sfml-audio-fiddle: fiddle(ruby's FFI) binding for SFML2 Audio functions.
環境は以下。
_sfml-audio-fiddleの紹介 - Qiita
_sfml-audio-fiddle | RubyGems.org | コミュニティのGemホスティングサービス
_bggd/sfml-audio-fiddle: fiddle(ruby's FFI) binding for SFML2 Audio functions.
環境は以下。
- Windows10 x64 21H1
- Ruby 2.3.3 p222 x86 (i386-mingw32)
- Ruby 2.6.8 p205 x86 (i386-mingw32)
- Ruby 2.7.4 p191 x86 (i386-mingw32)
- Ruby 3.0.2 p107 x86 (i386-mingw32)
- sfml-audio-fiddle 0.1.0
◎ インストール方法。 :
念のためにインストール方法をメモしておく。
gem でインストールできる。
動作には CSFML の DLL も必要。以下のサイトから入手する。32bit版と64bit版があるので、使ってる Ruby に合わせる。
_CSFML (SFML / Download / Bindings)
_SFML/CSFML: Official binding of SFML for C
zipをダウンロードして解凍。
bin/ の中に入っている .dll を、スクリプトと同じ場所に置くらしい。
gem でインストールできる。
gem install sfml-audio-fiddle
動作には CSFML の DLL も必要。以下のサイトから入手する。32bit版と64bit版があるので、使ってる Ruby に合わせる。
_CSFML (SFML / Download / Bindings)
_SFML/CSFML: Official binding of SFML for C
zipをダウンロードして解凍。
bin/ の中に入っている .dll を、スクリプトと同じ場所に置くらしい。
- csfml-audio-2.dll
- openal32.dll
- libsndfile-1.dll ... 解説ページには記載が無いが、ファイル名からして必要になりそうな…。
◎ requireしただけでエラーを出す問題。 :
require "sfml/audio" をしてから使うライブラリだけど、require しただけでエラーを出してしまう。
問題点は2つありそう。
一つは、DLL検索パスの問題。どうやら今時の Ruby の fiddle は、スクリプトが置いてあるディレクトリ/カレントディレクトリに置いてある .dll を探してくれない感じで…。Ruby 2.3.3 ならエラーが出ないけど、Ruby 2.6 以降はエラーになる。
もう一つは、CSFML の .dll のバージョンによってエラーが出る問題。DLL検索パスの問題を解決した状況下で試すと、以下のような結果になった。
DLL検索パスの問題は、以下のような記述をすることで解決できそう。
一つは、RubyInstaller::Runtime.add_dll_directory() を使ってDLL検索パスを指定する方法。
一つは、kernel32.dll の SetDllDirectory() を使ってDLL検索パスを指定する方法。
あるいは、そもそも、ruby.exe が置いてある場所(C:\Ruby\bin\ 等)に関連DLLをコピーすることでもエラーは出なくなる。
ただ、こういう対策をしても、CSFML 2.2 以降の .dll を使うとエラーが出るのだけど…。
> irb irb(main):001:0> RUBY_VERSION => "2.6.8" irb(main):002:0> require "sfml/audio" Traceback (most recent call last): 21: from C:/Ruby/Ruby26-x86/bin/irb:23:in `<main>' 20: from C:/Ruby/Ruby26-x86/bin/irb:23:in `load' 19: from C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/irb-1.3.6/exe/irb:11:in `<top (required)>' 2: from (irb):2:in `<main>' 1: from C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:85:in `require' C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:85:in `require': cannot load such file -- sfml/audio (LoadError) 30: from C:/Ruby/Ruby26-x86/bin/irb:23:in `<main>' 29: from C:/Ruby/Ruby26-x86/bin/irb:23:in `load' 28: from C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/irb-1.3.6/exe/irb:11:in `<top (required)>' 11: from (irb):2:in `<main>' 10: from C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:149:in `require' 9: from C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:160:in `rescue in require' 8: from C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:160:in `require' 7: from C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:4:in `<top (required)>' 6: from C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:6:in `<module:SFMLImporter>' 5: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle/import.rb:77:in `dlload' 4: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle/import.rb:77:in `collect' 3: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle/import.rb:87:in `block in dlload' 2: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle.rb:47:in `dlopen' 1: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle.rb:47:in `new' C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle.rb:47:in `initialize': No such file or directory (Fiddle::DLError) 28: from C:/Ruby/Ruby26-x86/bin/irb:23:in `<main>' 27: from C:/Ruby/Ruby26-x86/bin/irb:23:in `load' 26: from C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/irb-1.3.6/exe/irb:11:in `<top (required)>' 9: from (irb):2:in `<main>' 8: from C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:149:in `require' 7: from C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:160:in `rescue in require' 6: from C:/Ruby/Ruby26-x86/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:160:in `require' 5: from C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:4:in `<top (required)>' 4: from C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:6:in `<module:SFMLImporter>' 3: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle/import.rb:77:in `dlload' 2: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle/import.rb:77:in `collect' 1: from C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle/import.rb:86:in `block in dlload' C:/Ruby/Ruby26-x86/lib/ruby/2.6.0/fiddle/import.rb:89:in `rescue in block in dlload': can't load csfml-audio-2 (Fiddle::DLError) irb(main):003:0>
問題点は2つありそう。
一つは、DLL検索パスの問題。どうやら今時の Ruby の fiddle は、スクリプトが置いてあるディレクトリ/カレントディレクトリに置いてある .dll を探してくれない感じで…。Ruby 2.3.3 ならエラーが出ないけど、Ruby 2.6 以降はエラーになる。
もう一つは、CSFML の .dll のバージョンによってエラーが出る問題。DLL検索パスの問題を解決した状況下で試すと、以下のような結果になった。
- CSFML 2.0, 2.1 の .dll は、require した際にエラーを出さなかった。
- CSFML 2.2, 2.3, 2.4, 2.5.1 の .dll はエラーを出した。
DLL検索パスの問題は、以下のような記述をすることで解決できそう。
一つは、RubyInstaller::Runtime.add_dll_directory() を使ってDLL検索パスを指定する方法。
my_dll_dir_path = File.expand_path("..", __FILE__) RubyInstaller::Runtime.add_dll_directory(my_dll_dir_path) require "sfml/audio"
一つは、kernel32.dll の SetDllDirectory() を使ってDLL検索パスを指定する方法。
require 'fiddle/import' require 'fiddle/types' module WinAPI extend Fiddle::Importer dlload 'kernel32.dll' include Fiddle::Win32Types extern 'int SetDllDirectory(LPCSTR)' end my_dll_dir_path = File.expand_path("..", __FILE__) WinAPI.SetDllDirectory(my_dll_dir_path) require "sfml/audio"
あるいは、そもそも、ruby.exe が置いてある場所(C:\Ruby\bin\ 等)に関連DLLをコピーすることでもエラーは出なくなる。
ただ、こういう対策をしても、CSFML 2.2 以降の .dll を使うとエラーが出るのだけど…。
◎ サウンドファイルを再生するとクラッシュする問題。 :
前述のDLL検索パスの問題を対策して require が通るようになったとしても、サウンドファイルを再生すると、再生が終わったタイミングで Segmentation fault が出てしまう。
例えば、以下のようなスクリプトを実行してみると…。
_04_ogg_play_music.rb
_loop_bgm.ogg
実行すると、再生が終わったあたりのタイミングで Segmentation fault になる。
Music ではなく SoundBuffer を使ってみても…。
_05_ogg_play_soundbuffer.rb
結果は同じ。再生が終わったタイミングで Segmentation fault になる。
_stderr05.txt
Ruby 2.3.3, 2.6.8, 2.7.4, 3.0.2、どのバージョンで試しても Segmentation fault になる…。
例えば、以下のようなスクリプトを実行してみると…。
_04_ogg_play_music.rb
begin require 'sfml/audio' rescue => e require 'fiddle/import' require 'fiddle/types' module WinAPI extend Fiddle::Importer dlload 'kernel32.dll' include Fiddle::Win32Types extern 'int SetDllDirectory(LPCSTR)' end my_dll_dir_path = File.expand_path("..", __FILE__) WinAPI.SetDllDirectory(my_dll_dir_path) require 'sfml/audio' end music = SFML::Music.new("loop_bgm.ogg") puts "Song length : #{music.get_duration} sec" music.play while music.get_status == :playing puts "playing" sleep 1 end
_loop_bgm.ogg
実行すると、再生が終わったあたりのタイミングで Segmentation fault になる。
C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:41: [BUG] Segmentation fault ruby 2.6.8p205 (2021-07-07 revision 67951) [i386-mingw32] -- Control frame information ----------------------------------------------- c:0004 p:---- s:0018 e:000017 CFUNC :call c:0003 p:0018 s:0013 e:000012 METHOD C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:41 c:0002 p:0013 s:0007 e:000006 BLOCK C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:154 [FINISH] c:0001 p:0000 s:0003 E:001d48 (none) [FINISH] -- Ruby level backtrace information ---------------------------------------- C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:154:in `block in dtor' C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:41:in `sfMusic_stop' C:/Ruby/Ruby26-x86/lib/ruby/gems/2.6.0/gems/sfml-audio-fiddle-0.1.0/lib/sfml/audio.rb:41:in `call' -- C level backtrace information ------------------------------------------- C:\Windows\SYSTEM32\ntdll.dll(NtWaitForSingleObject+0xc) [0x76ff29dc] C:\Windows\System32\KERNELBASE.dll(WaitForSingleObject+0x12) [0x75031072] C:\Ruby\Ruby26-x86\bin\msvcrt-ruby260.dll(rb_vm_bugreport+0x301) [0x665407a1] C:\Windows\SYSTEM32\ntdll.dll(RtlCaptureStackContext+0x1bc43) [0x77024883] -- Other runtime information ----------------------------------------------- * Loaded script: 04_ogg_play_music.rb ..._stderr04.txt(全てのエラーメッセージ)
Music ではなく SoundBuffer を使ってみても…。
_05_ogg_play_soundbuffer.rb
begin require 'sfml/audio' rescue => e require 'fiddle/import' require 'fiddle/types' module WinAPI extend Fiddle::Importer dlload 'kernel32.dll' include Fiddle::Win32Types extern 'int SetDllDirectory(LPCSTR)' end my_dll_dir_path = File.expand_path("..", __FILE__) WinAPI.SetDllDirectory(my_dll_dir_path) require 'sfml/audio' end buffer = SFML::SoundBuffer.new 'loop_bgm.ogg' sound = SFML::Sound.new buffer sound.play sleep buffer.get_duration + 1
結果は同じ。再生が終わったタイミングで Segmentation fault になる。
_stderr05.txt
Ruby 2.3.3, 2.6.8, 2.7.4, 3.0.2、どのバージョンで試しても Segmentation fault になる…。
◎ 雑感。 :
Ruby の fiddle を使うことで、Ruby のバージョンが変わるたびにバイナリをビルドしなくても済むメリットがありそう、などと思ってたけど、そもそも正常動作してくれないのではなあ、みたいな…。
[ ツッコむ ]
以上です。