2022/08/19(金) [n年前の日記]
#1 [ruby][windows] Ruby/SDLでスクリーンセーバを作成
Windows10 x64 21H2上で、Ruby 1.8.7, 1.9.3 + Ruby/SDL (rubysdl) 2.1.1.1 を使って、Windows用のスクリーンセーバ(の雛形)を作成してみた。
動作させている様子は以下。解像度が低いけど雰囲気ぐらいは伝わるかと。
Windows用スクリーンセーバに求められる仕様については、pygame でスクリーンセーバを作った際にメモしたソレが多少は参考になるかと。
_Windows用スクリーンセーバについてのおさらい。
動作させている様子は以下。解像度が低いけど雰囲気ぐらいは伝わるかと。
Windows用スクリーンセーバに求められる仕様については、pygame でスクリーンセーバを作った際にメモしたソレが多少は参考になるかと。
_Windows用スクリーンセーバについてのおさらい。
◎ 動作確認環境。 :
動作確認環境は以下。
セットアップファイル名も一応列挙。
_ActiveScriptRuby and Other packages
_Download Archives - RubyInstaller
Ruby は32bit版を利用。
Ruby は32bit版を利用。
大事なことなので2回言いました。
Ruby/SDLのバイナリが32bitなので、64bit版のRubyに入れて使おうとしても動かないです。たぶん。
ちなみに、Windows用のRuby/SDLは Ruby 1.8 / 1.9用しか存在しないので、Ruby 2.0以降でこういったことはできないと思われます。
利用できるRubyのバージョンや、Ruby/SDL のインストール方法は、以下を参照のこと。
_Ruby/SDLでスクリーンセーバを作れないものか
_Ruby/SDLをインストールしようとしてハマった
- Windows10 x64 21H2
- RubyInstaller 1.9.3 p551 i386-mingw32 + rubysdl 2.1.1.1 + ocra 1.3.10
- ActiveScriptRuby 1.8.7 p330 i386-mswin32 + rubysdl 2.1.1.1 + ocra 1.3.1
- RyubyInstaller 1.8.7 p330 i386-mingw32 + rubysdl 1.3.1.1 + ocra 1.3.1
セットアップファイル名も一応列挙。
- rubyinstaller-1.9.3-p551.exe
- rubysdl-2.1.1.1-mswin32-1.9.1-p243.zip
- ActiveRuby.msi
- rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip
- rubyinstaller-1.8.7-p330.exe
- rubysdl-1.3.1.1-mswin32-1.8.6-p36.zip
_ActiveScriptRuby and Other packages
_Download Archives - RubyInstaller
Ruby は32bit版を利用。
Ruby は32bit版を利用。
大事なことなので2回言いました。
Ruby/SDLのバイナリが32bitなので、64bit版のRubyに入れて使おうとしても動かないです。たぶん。
ちなみに、Windows用のRuby/SDLは Ruby 1.8 / 1.9用しか存在しないので、Ruby 2.0以降でこういったことはできないと思われます。
利用できるRubyのバージョンや、Ruby/SDL のインストール方法は、以下を参照のこと。
_Ruby/SDLでスクリーンセーバを作れないものか
_Ruby/SDLをインストールしようとしてハマった
◎ ソース。 :
ソースは以下。処理内容は、画面内をRubyのロゴ画像が跳ね回るだけの処理。
_rbsdlsav.rb
使用画像は以下。.rb と同じ階層に res というフォルダを作って、その中に入れておく。
_ruby_logo.png
_rbsdlsav.rb
require "sdl"
require "Win32API"
APPLI_NAME = "Screensaver sample by using Ruby/SDL"
APPLIT_VER = "0.0.1"
IMG_FILE = "res/ruby_logo.png"
MUTEX_NAME = {
"None" => "screensaver_rubysdl_sample_fullscreen",
"fullscreen" => "screensaver_rubysdl_sample_fullscreen",
"config" => "screensaver_rubysdl_sample_config",
"preview" => "screensaver_rubysdl_sample_preview",
}
ERROR_ALREADY_EXISTS = 183
# Draw fullscreen
def draw_fullscreen
SDL.init(SDL::INIT_VIDEO)
screen = SDL::Screen.open(0, 0, 0, SDL::FULLSCREEN | SDL::HWSURFACE | SDL::DOUBLEBUF)
img = SDL::Surface.load(get_resource_path(IMG_FILE)).display_format
x, y = screen.w / 2, screen.h / 2
dx, dy = screen.w / (60.0 * 3), screen.h / (60.0 * 4)
count = 0
looping = true
SDL::Mouse.hide # hide mouse cursor
# main loop
while looping
while event = SDL::Event2.poll
case event
when SDL::Event2::Quit
looping = false
when SDL::Event2::KeyUp
looping = false
when SDL::Event2::MouseMotion
if count >= 120
rx = event.xrel
ry = event.yrel
r = 10
looping = false if (rx * rx + ry * ry >= r * r)
end
end
end
# update sprite position
x += dx
y += dy
dx *= -1 if (x <= 0 or x >= screen.w - img.w)
dy *= -1 if (y <= 0 or y >= screen.h - img.h)
count += 1
screen.fill_rect(0, 0, screen.w, screen.h, [0, 0, 0]) # fill bg
screen.put(img, x, y) # draw sprite
screen.flip # update screen
SDL.delay(16) # wait
end
SDL::Mouse.show # show mouse cursor
end
# Draw preview
def draw_preview(hwnd)
if hwnd > 0
# /p xxxxxx
scrw, scrh = get_window_size(hwnd)
SDL.putenv("SDL_VIDEODRIVER=windib")
SDL.putenv("SDL_WINDOWID=#{hwnd}")
else
# /p 0
scrw, scrh = 152, 112
end
SDL.init(SDL::INIT_VIDEO)
screen = SDL::Screen.open(scrw, scrh, 0, SDL::SWSURFACE)
img = SDL::Surface.load(get_resource_path(IMG_FILE)).display_format
# main loop
frames = 180 * 5
frames.times do |t|
while event = SDL::Event2.poll
case event
when SDL::Event2::Quit
return
when SDL::Event2::KeyDown
return if event.sym == SDL::Key::ESCAPE
end
end
x = (screen.w - img.w) / 2
y = 12 * Math.sin((t * 4) * Math::PI / 180.0) + ((screen.h - img.h) / 2)
screen.fill_rect(0, 0, screen.w, screen.h, [235, 108, 0]) # fill bg
screen.fill_rect(8, 8, screen.w - 16, screen.h - 16, [255, 128, 0]) # fill bg
screen.put(img, x, y)
screen.flip
SDL.delay(16)
end
end
def draw_config
s = "#{APPLI_NAME}\nVer. #{APPLIT_VER}"
title = "Information"
msg_box(s, title)
end
def get_resource_path(filepath)
# return File.dirname(__FILE__) + "/" + filepath
return File.join(File.dirname(__FILE__), filepath)
end
# Display messagebox dialog
def msg_box(msg, title)
msgbox = Win32API.new("user32", "MessageBoxA", %w(p p p i), "i")
msgbox.call(0, msg, title, 0x40)
end
# Get window size
def get_window_size(hwnd)
get_window_rect = Win32API.new("user32", "GetWindowRect", "lp", "i")
buf = "\0" * 256
get_window_rect.call(hwnd, buf)
x0, y0, x1, y1 = buf.unpack("l4")
w, h = (x1 - x0), (y1 - y0)
return w, h
end
# parse command line option
kind = "None"
hwnd = 0
if ARGV.size >= 1
s = ARGV[0].downcase.slice(0..1)
case s
when "/s"
kind = "fullscreen"
when "/c"
kind = "config"
when "/p"
kind = "preview"
hwnd = ARGV[1].to_i
end
end
# Mutex
create_mutex = Win32API.new("kernel32", "CreateMutex", "llp", "l")
get_last_error = Win32API.new("kernel32", "GetLastError", "", "l")
release_mutex = Win32API.new("kernel32", "ReleaseMutex", "l", "l")
close_handle = Win32API.new("kernel32", "CloseHandle", "l", "l")
mutex = create_mutex.call(0, 1, MUTEX_NAME[kind])
err = get_last_error.call()
exit if (mutex == 0 or err == ERROR_ALREADY_EXISTS)
# main job
case kind
when "config"
draw_config
when "preview"
draw_preview(hwnd)
else
# fullscreen
draw_fullscreen
end
if mutex != 0
release_mutex.call(mutex)
close_handle.call(mutex)
end
exit
- draw_fullscreen() が、フルスクリーン表示モードの処理。
- draw_config() が、設定画面モードの処理。
- draw_preview() が、プレビューモードの処理。
- Mutex 云々が、多重起動抑止処理。
使用画像は以下。.rb と同じ階層に res というフォルダを作って、その中に入れておく。
_ruby_logo.png
◎ テストで実行。 :
実行は以下。
ruby rbsdlsav.rb /s ruby rbsdlsav.rb /c ruby rbsdlsav.rb /p 0 ruby rbsdlsav.rb
- /s : フルスクリーン表示で実行。
- /c : 設定画面を表示。ここではアプリ名をメッセージボックスで表示するだけ。
- /p 0 : プレビューモード。スクリーンセーバ設定画面を表示した際に、プレビュー窓内に表示されるモード。ウインドウハンドルとして 0 を指定すると、小さいウインドウを表示してテスト描画する。本番では0より大きい数値(10進数)が渡されるはず。
- コマンドラインオプション無し : フルスクリーン表示で実行するようにしておいた。
◎ exe化する。 :
Windows用スクリーンセーバのファイル拡張子は .scr だけど、実体は .exe ファイル。.exe を .scr にリネームして、スクリーンセーバとして利用する。
今回、Rubyスクリプトのexe化には ocra を利用した。
ocra のインストールは以下。
以下で exe ファイルが生成される。
今回、Rubyスクリプトのexe化には ocra を利用した。
- Ruby 1.8.7 の場合、ocra 1.3.1 が利用できる。ocra 1.3.2 以降はエラーが出る。
- RUby 1.9.3 の場合、ocra 1.3.10 が利用できる。ocra 1.3.11 はエラーが出る。
ocra のインストールは以下。
# Ruby 1.8.7 gem install ocra -v 1.3.1 # Ruby 1.9.3 gem install ocra -v 1.3.10
以下で exe ファイルが生成される。
ocra --windows rbsdlsav.rb res
- --windows : GUIアプリとして実行する指定。
- *.rb : 変換したいスクリプトファイル名。
- res : 画像ファイル等が入ってるフォルダ。*.rb と一緒にフォルダ名を列挙すると exe に同梱される。
◎ スクリーンセーバとして利用する。 :
.exe が生成されたら、.scr にリネームする。以下の場所に .scr をコピーすれば、スクリーンセーバとして利用できるようになる。
今回作ったスクリーンセーバは、32bit版 Ruby で作ったので、C:\Windows\SysWOW64\ 以下にコピーして動作確認した。
- Windows が64bit版、スクリーンセーバが32bit版のプログラムなら、C:\Windows\SysWOW64\ 以下に .scr をコピーする。
- Windows が64bit版、スクリーンセーバが64bit版のプログラムなら、C:\Windows\System32\ 以下に .scr をコピーする。
- Windows が32bit版なら、C:\Windows\System32\ 以下に .scr をコピーする。
今回作ったスクリーンセーバは、32bit版 Ruby で作ったので、C:\Windows\SysWOW64\ 以下にコピーして動作確認した。
◎ exerbではexe化できない。 :
[ ツッコむ ]
#2 [ruby][windows] exerbでRuby/SDLのスクリプトをexe化したかったけど無理だった
Ruby/SDL 1.3.1.1 を使ったRubyスクリプトを exerb 5.3.0 でexeに変換したかったけど、ちょっと無理っぽい気配になってきた。いや、無理。これたぶん無理。
環境は、Windows10 x64 21H2 + Ruby 1.8.7 p330 i386-mingw32 + rubysdl 1.3.1.1 + exerb 5.3.0。ちなみに、動作確認に使ってる別PCも Windows10 x64 21H2。
環境は、Windows10 x64 21H2 + Ruby 1.8.7 p330 i386-mingw32 + rubysdl 1.3.1.1 + exerb 5.3.0。ちなみに、動作確認に使ってる別PCも Windows10 x64 21H2。
◎ 症状。 :
exerbで生成されたexeは、変換作業に用いたPC上では動作するけれど、別PC上では動作しなかった。


◎ 検証に使ったソース。 :
以下は、exerb で exe化できないか試した際のRubyスクリプトソース。及び、exerb に渡す .exyファイル。Ruby/SDLを使ったWindows用スクリーンセーバ(の雛形)で、画面の中をRubyロゴ画像が跳ね回る処理をしている。
_rbsdlsav2.rb
_rbsdlsav2.exy
ちなみに、上記のスクリプト rbsdlsav2.rb は画面に画像を描画しているけれど、画像ファイルをbase64に変換してスクリプト内に含めてある版なので、別途画像ファイルを必要としない。
また、中に含めた画像ファイルは png ではなく bmp にしてある。png にすると、exe実行時、何故か以下のエラーが出てしまうので…。
作業手順その他については、以下も参照のこと。
_試行錯誤の記録。その2 - exerbと格闘中 - mieki256's diary
_rbsdlsav2.rb
_rbsdlsav2.exy
ちなみに、上記のスクリプト rbsdlsav2.rb は画面に画像を描画しているけれど、画像ファイルをbase64に変換してスクリプト内に含めてある版なので、別途画像ファイルを必要としない。
また、中に含めた画像ファイルは png ではなく bmp にしてある。png にすると、exe実行時、何故か以下のエラーが出てしまうので…。
> rbsdlsav2.exe
rbsdlsav2.rb:197:in `loadFromIO': Couldn't load image from IO: Failed loading png_create_info_struct: 指定されたモジュールが見つかりません。
(SDL::Error)
from rbsdlsav2.rb:197:in `get_surface_image_from_base64'
from rbsdlsav2.rb:75:in `draw_fullscreen'
from rbsdlsav2.rb:235
作業手順その他については、以下も参照のこと。
_試行錯誤の記録。その2 - exerbと格闘中 - mieki256's diary
◎ 調査に使ったツール。 :
ググったところ、Dependencies というツールを使えば、*.so や *.dll が必要としている *.dll 等を調べることができると知った。
_lucasg/Dependencies: A rewrite of the old legacy software "depends.exe" in C# for Windows devs to troubleshoot dll load dependencies issues.
このツールを導入して、sdl.so や sdl*.dll を開いて、どの .dll が要求されているのか調べていった。
_lucasg/Dependencies: A rewrite of the old legacy software "depends.exe" in C# for Windows devs to troubleshoot dll load dependencies issues.
このツールを導入して、sdl.so や sdl*.dll を開いて、どの .dll が要求されているのか調べていった。
◎ 原因その1。 :
不具合が発生した原因は2点ある。
原因その1。変換作業に使ったWindows10機には、C:\Windows\SysWOW64\ 以下に、何故かSDL関連のDLLがごっそりほとんど入っていた。おそらく、何かしらのアプリをインストールした際、知らない間に、SysWOW64\以下にSDL関連DLLがコピーされてしまったのだろう…。
そのため、Rubyスクリプトをexe化したソレを実行した際、そのファイルに足りてないDLLがあったとしても、SysWOW64\ 以下から足りてないDLLを呼び出してしまって、故に exe が動いてしまっていた。
しかし、別PCの SysWOW64\ 以下には、SDL関連DLLが全く入ってなかったので、足りてないDLLに遭遇したらどこからも呼び出すことができず、「指定されたモジュールが見つかりません」とエラーを出していた。
原因その1。変換作業に使ったWindows10機には、C:\Windows\SysWOW64\ 以下に、何故かSDL関連のDLLがごっそりほとんど入っていた。おそらく、何かしらのアプリをインストールした際、知らない間に、SysWOW64\以下にSDL関連DLLがコピーされてしまったのだろう…。
そのため、Rubyスクリプトをexe化したソレを実行した際、そのファイルに足りてないDLLがあったとしても、SysWOW64\ 以下から足りてないDLLを呼び出してしまって、故に exe が動いてしまっていた。
しかし、別PCの SysWOW64\ 以下には、SDL関連DLLが全く入ってなかったので、足りてないDLLに遭遇したらどこからも呼び出すことができず、「指定されたモジュールが見つかりません」とエラーを出していた。
◎ 原因その2。 :
原因その2.今回足りてなかったファイルは、libfreetype-6.dll だった。sdl.so が SDL_ttf.dll を要求して、その SDL_ttf.dll が libfreetype-6.dll を要求していた。
ところが、そもそも、Ruby/SDL 1.3.1.1 (rubysdl-1.3.1.1-mswin32-1.8.6-p36.zip) には、libfreetype-6.dll が同梱されていなかった…。当然、Ruby 1.8.7 のインストールフォルダにもコピーされていないので、フォルダを眺めてSDL関連DLLを全て列挙したつもりが、libfreetype-6.dll だけ指定が抜けている状態になってしまった。
これがまた困ったことに、libfreetype-6.dll について .exy 内で記述を追記して exe を生成しても、正常に動作する exe にはならない。
と言うのも、exerb には、「各種ファイル名は17文字まで」という謎の制限があるようで、libfreetype-6.dll はその制限に引っ掛かってしまう。
生成した exe を実行すると、「Couldn't modify DLL's name in the import table. The name of the executable file is too long.」とエラーメッセージを出す。「ファイル名が長過ぎて扱えない」と言われているのだと思う。
余談。libfreetype-6.dll は、Ruby/SDL 2.1.1.1 の zip (rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip) の中にあるものを利用してみた。
まあ、Ruby/SDL 1.3.1.1 と 2.1.1.1 のどちらを使うかと言えば、フツーは 2.1.1.1 を使うだろうから…。「Ruby/SDL 1.3.1.1 の zip はファイルが不足している」という罠にハマることは無いだろうなと…。
ところが、そもそも、Ruby/SDL 1.3.1.1 (rubysdl-1.3.1.1-mswin32-1.8.6-p36.zip) には、libfreetype-6.dll が同梱されていなかった…。当然、Ruby 1.8.7 のインストールフォルダにもコピーされていないので、フォルダを眺めてSDL関連DLLを全て列挙したつもりが、libfreetype-6.dll だけ指定が抜けている状態になってしまった。
これがまた困ったことに、libfreetype-6.dll について .exy 内で記述を追記して exe を生成しても、正常に動作する exe にはならない。
と言うのも、exerb には、「各種ファイル名は17文字まで」という謎の制限があるようで、libfreetype-6.dll はその制限に引っ掛かってしまう。
生成した exe を実行すると、「Couldn't modify DLL's name in the import table. The name of the executable file is too long.」とエラーメッセージを出す。「ファイル名が長過ぎて扱えない」と言われているのだと思う。
余談。libfreetype-6.dll は、Ruby/SDL 2.1.1.1 の zip (rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip) の中にあるものを利用してみた。
まあ、Ruby/SDL 1.3.1.1 と 2.1.1.1 のどちらを使うかと言えば、フツーは 2.1.1.1 を使うだろうから…。「Ruby/SDL 1.3.1.1 の zip はファイルが不足している」という罠にハマることは無いだろうなと…。
◎ 対策。 :
exerb に .exy を通じて、「各DLLを内包しろ」と指定すると、17文字を超えるファイル名を扱えなくてエラーを出すわけだけど。
しかし、それらのDLLが、exeファイルとは別ファイルとして同梱されている状態なら、17文字制限は絡んでこないので、exeもちゃんと動作してくれる。
つまり、対策としては…。
ただ、今回は、スクリーンセーバを作りたいという話なので…。できれば、1つのexeファイルで動いてくれるほうがありがたいわけで…。
しかし、それらのDLLが、exeファイルとは別ファイルとして同梱されている状態なら、17文字制限は絡んでこないので、exeもちゃんと動作してくれる。
つまり、対策としては…。
- mkexy で生成した .exy に対して、DLL関連を追記して、どうにか1つのexeにしよう、などと考えてはいけない。
- 必要な DLL は、exe と同階層にまとめて置いておくことを心掛ける。
ただ、今回は、スクリーンセーバを作りたいという話なので…。できれば、1つのexeファイルで動いてくれるほうがありがたいわけで…。
◎ 結論。 :
他の用途ならともかく、Ruby/SDLを使ってWindows用スクリーンセーバを作りたいなら、exerbは諦めてocraを使いましょう。という話になるのかなと。ocraで作ったexeは起動が遅いけど、その分簡単にexe化できるメリットもあるし…。
そもそも、exerb は Ruby 1.8 までしか対応してないので…。今時使われる Ruby は、Ruby 2.x、もしかすると Ruby 3.x だったりするはず。フツーは exerb を使いたくても使えないだろうから、今更な話ではあるなと…。今回、Ruby/SDL を Windows上で使いたいが故に、あえて Ruby 1.8 を使っただけなので…。「せっかく Ruby 1.8 を使うなら exerb もイケるかな」と欲を出しちゃって大変なことになったという…。
そもそも、exerb は Ruby 1.8 までしか対応してないので…。今時使われる Ruby は、Ruby 2.x、もしかすると Ruby 3.x だったりするはず。フツーは exerb を使いたくても使えないだろうから、今更な話ではあるなと…。今回、Ruby/SDL を Windows上で使いたいが故に、あえて Ruby 1.8 を使っただけなので…。「せっかく Ruby 1.8 を使うなら exerb もイケるかな」と欲を出しちゃって大変なことになったという…。
◎ 2022/08/20追記。 :
Windows上で Ruby/SDL 1.3.1.1 を利用する際に必要になるdllを一応列挙しておきます。
Ruby 2.1.1.1 の場合は、またちょっと違ってくるかと…。rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip 内の dll/ の中には以下が入っているようだけど…。
Ruby/SDL 1.3.1.1 と比べて、以下のファイルがリネーム or 追加されている模様。
jpeg.dll libcharset-1.dll libfreetype-6.dll libiconv-2.dll libpng12.dll libtiff.dll ogg.dll SDL.dll SDL_image.dll SDL_mixer.dll SDL_ttf.dll SGE.dll smpeg.dll vorbis.dll vorbisfile.dll zlib1.dll
Ruby 2.1.1.1 の場合は、またちょっと違ってくるかと…。rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip 内の dll/ の中には以下が入っているようだけど…。
jpeg.dll libcharset-1.dll libfreetype-6.dll libiconv-2.dll libogg-0.dll libpng12-0.dll libtiff-3.dll libvorbis-0.dll libvorbisfile-3.dll mikmod.dll SDL.dll SDL_image.dll SDL_mixer.dll SDL_ttf.dll SGE.dll smpeg.dll zlib1.dll
Ruby/SDL 1.3.1.1 と比べて、以下のファイルがリネーム or 追加されている模様。
libogg-0.dll libpng12-0.dll libtiff-3.dll libvorbis-0.dll libvorbisfile-3.dll mikmod.dll
[ ツッコむ ]
以上、1 日分です。