Hatena::ブログ(Diary)

yukkeのノート

2009-12-05

Javaで使用するファイルを保存するディレクトリどこに置きますか?

Javaで作ったアプリケーションを公開しようと考えていたのですが、アプリケーションで読み書きするファイルをどこに保存したらいいのか悩んでしまいました。

読むだけの場合はJarファイルに一緒に固めてしまえばいいのですが、書き込みも行う場合はそうもいきません。

このようなファイルの保存先を選択するには大きく分けて

  1. アプリケーションが勝手に決める
  2. ユーザに決めさせる

の二つがあります。

1. アプリケーションが保存先を勝手に決める

Jarファイルと同じディレクトリでもいいのですが、そこがProgram FilesなどだとWindows Vista以降ではUACのせいで色々と面倒になります。

ここで挙げるのはホームディレクトリのパスを取得し、そこにOSごとの保存先パスを付け足してやる方法です。

String homePath = System.getProperty("user.home");
String osName = System.getProperty("os.name");
String subPath = "";
if (osName.contains("Windows")) {
  if (osName.contains("Vista") || osName.contains("7")) {
    subPath = "Documents";
  } else {
    subPath = "My Documents";
  }
} else if (osName.contains("Mac OS X")) {
  subPath = "Library/Preferences";
}
java.io.File prefDirParent = new java.io.File(homePath, subPath);
java.io.File prefDir = new java.io.File(prefDirParent, "ProjectName");

OSごとの設定が必要にはなりますが、ユーザに手間をかけさせずに決めることができます。

2. ユーザに保存先を決めさせる

アプリケーションが勝手に決めるのではなくどこそこに保存することをユーザに操作させる方法です。

特に巨大なファイルを操作するときに別のディスクに保存しておきたいというユーザの希望にも応えることができます。

javax.swing.JFileChooser dirChooser = new javax.swing.JFileChooser(".");
dirChooser.setDialogTitle("保存先を指定してください");
dirChooser.setFileSelectionMode(javax.swing.JFileChooser.DIRECTORIES_ONLY);
int ret = dirChooser.showOpenDialog(null); 
if (ret != javax.swing.JFileChooser.APPROVE_OPTION) return;
prefDir = dirChooser.getSelectedFile();

初期起動時に決めさせるかインストーラで管理者権限のあるうちにやってしまうかなどいくつか方法があります。ちなみに「ユーザが決めた設定(ここでは保存先のパス)をどこに保存するか?」という疑問に対しては"java.util.prefs.Preferences"を使うのがいいと思います。Windowsの場合レジストリを使用するようなのでそれが嫌な場合には使えませんが:(

まとめ

  • 読み込みしかしないのであればJarに入れてClass#getResourceAsStream
  • 自分しか使わないのであれば固定パス(orカレントディレクトリ)
  • 文字列しか設定しないのであればPreferences
  • それ以外は上記の方法くらいしか思いつきません。
  • もっと上手な方法があればコメントにて教えてくださいませ

2009-11-16

D言語からFTGLでも使ってみるデモ

先日OpenGLからフォントを使えるライブラリFTGLをビルドしてそのまま放置していたのですが、重い腰を上げてD言語から使ってみました。

FreeType関係の定義をD言語で書き直すのが面倒なので、まずはC言語用のデモを動かしてみるところまでやってみました。

C言語用のデモ

C言語からFTGLを使うデモのソース
"$(FTGL)/demo/c-demo.c"がそうです。これをD言語に書き換えてみます。

用意するもの

opengl.d, openglu.d, glut.d
D言語からOpenGL, GLUTを使うためのソースコードです。shinhさんのサイトからダウンロードして"$(DMD)/import"に入れておくと楽です。
opengl32.lib, glu32.lib
上記のサイトのopengl.zipの中に入っています。"$(DMD)/lib"に入れておくと楽です。
glut32.dll, ftgl.dll
先日の記事参考。パスの通るところ*1に置く必要があります。
glut32.lib, ftgl.lib
先日の記事参考。そのままでは使えないので下記の作業をしないといけません。

glut32.lib, ftgl.libの変換

コンパイラdmdを使う場合、今回使用するglut32.libとftgl.libをdmd用に変換しないと使えません。

変換方法はやねうらおさんのサイトを参考にBorland C++ Compilerを使えば無料で変換できます。

ダウンロード先は今確認したところembarcaderoならインストーラを登録なしでダウンロードできるようです。

implib(bccなら"$(BCC)/Bin/implib.exe")を用意できたら、

$ implib -c -a glut32.lib glut32.dll

のようにglut32.libとftgl.libを変換したものをdmdのlibフォルダ("$(DMD)/lib")に入れておきましょう。

D言語に変換(写経とも)

c-demo.cがincludeしているヘッダファイルを見ながら手直しします。

私が書いたソースコードhttp://yukkepc.web.fc2.com/src/ftgl-demo.dに上げておきます*2

c-demo.cとの違いは次の通りです。

  • ftglCreateCustomFontは面倒だったので省略しました。
  • 終了の仕方が分からなかったので例外を投げています。
  • フォントをMSゴシックにして"こんにちは FTGL!"と表示します。

実行結果

$ dmd ftgl-demo.d opengl32.lib glu32.lib glut32.lib ftgl.lib

f:id:yukkepc:20091115201929p:image

D言語からデモが動きました!

FTGL全体をD言語から使えるようにするのは大変*3なので使いたい部分だけ書くのがいいのかなと思います。

*1:exeファイルと同じところか"/Windows/System"に置くのが簡単です

*2:ソースの再利用はFTGLのMITライセンスを守ってください

*3:htodを使って機械変換できればいいのですが、FreeTypeのヘッダファイルをincludeしているせいか上手くいかないのであきらめました

2009-09-13

WindowsでFTGLを使ってOpenGLで日本語フォントを表示する

非力な環境では遅いらしいSDL_ttfの代用としてOpenGLでTrueTypeフォントを FTGL というライブラリで扱えるかどうかを試してみます。

今回使用する環境

全て無料で手に入る物で揃えてみました。

Visual C++ 2008 Express Edition (SP1) のインストール

VS2003以降ならコンパイルできるようですが、ここでは無料で手に入るVC2008 SP1を使います。Visual Studioをインストールしていない人はVisual Studio 2008 Express Editionからインストールしておきましょう。

名称が長いので以下ではVisual C++ 2008 Express EditionをVCと省略することにします。

FreeType のダウンロード

SourceForgeのFreeTypeダウンロードページからft***.zipをもらってきて展開します。私が取得したときの最新はft239.zipでした。

次にFTGLのビルドに必要なので展開したディレクトリのパスを環境変数*1"FREETYPE"に記述します。私は"C:\Library\"以下にまとめて置くことにします。

f:id:yukkepc:20090913095504p:image

FreeType のビルド

VCから"$(FREETYPE)/builds/win32/vc2008/freetype.sln"を開きます*2

ターゲットを"LIB RELEASE"にしてソリューションのビルドを行います(必要なら"LIB DEBUG"でもビルドします)。

うまくビルドできると"$(FREETYPE)/objs/win32/vc2008"に"freetype***.lib"ができている*3ので、二階層上の"$(FREETYPE)/objs"にコピーしておきます。

FTGL のビルド

SourceForgeのFTGLダウンロードページからftgl-*.tar.gzをもらってきて展開します。私が取得したときの最新はftgl-2.1.3-rc5.tar.gzでした。

以下では展開したディレクトリパスを$(FTGL)と記述することにします。環境変数にする必要はありません。

VCから"$(FTGL)/msvc/vc8/ftgl.sln"を開きます。ソリューションファイルの変換をウィザードに沿って行います。

プロジェクトftgl_dllのプロパティを開き、構成プロパティ→リンカ→入力→追加の依存ファイルの中の"freetype234.lib"を先ほど作成したファイル名に書き換えます。

準備が整ったのでftgl_dllのビルドを行います。成功すれば"$(FTGL)/msvc/Build"にftgl.dll, ftgl.libができているはずです。

GLUT のダウンロード

下記のデモで使うためにGLUT (OpenGL Utility Toolkit)を用意しておきます。GLUT はビルド済みファイルが公開されているので、GLUT for Windowsからバイナリをダウンロードして展開します。

README-win32.txtにしたがい、glut32.dllを"/Windows/System"に、glut32.libを"(MSVC9のインストール先)/lib"に、glut.hを"((MSVC9のインストール先)/include/GL"に置きます。

FTGL のデモをビルド

ここまでうまくいっていることを確かめるために FTGL についているデモを実行してみます。

VC から"$(FTGL)/msvc/vc8/ftgl_demo.sln"を開きます。

プロジェクトFTGLDemo のプロパティを開き、構成プロパティ→リンカ→全般→追加のライブラリ ディレクトリに"$(FREETYPE)/objs"を追加します。

FTGLDemo.cppを開き、

#       define FONT_FILE "C:\\WINNT\\Fonts\\arial.ttf"

このファイルパスを使用したいフォントのパスに書き換えてビルドします。

FTGL のデモで日本語文字を表示する

上記のデモの文章を修正して適当な日本語の文章(後半はWikipediaOpenGLのページからのコピー)をMSゴシックで表示させてみました。

f:id:yukkepc:20090913145600p:image

プログラムをバイナリエディタで開いてみると、コンパイルされた状態で既に文字化けしているのでVC++側の問題なのだと思います。試しにソースコード中の文字化けした文字列をエスケープ文字列にしたらちゃんと表示されます。

f:id:yukkepc:20090913191252p:image

ソースコードがエスケープ文字列だらけになってしまうのが困りものですが:-(

この問題を検索してみると、VC++.NET と UTF-8 -C++ で書いたソースコードに UTF-8 エンコーディン- C言語・C++・C# | 教えて!gooのようにUTF-8のファイルを使うとおかしくなるようです。OSのロケールを変えなければならない、って単なるIDE(or コンパイラ)のバグなのでは??

D言語からは使えないのが困った

FTGLはC++言語で記述されているため、DLLを変更せずに直接使うことはできません(参考:C++とのインターフェイス)。ということで私はFTGLは使わないことにします。ここまで書いておいてひどいオチだ;-)

(追記) FTGL付随のデモを見てみたところ、C言語インターフェイスも用意されているようです。早とちりしてごめんなさい。

(さらに追記) D言語から使ってみました

*1Windows 7ではコントロールパネル -> システム -> システムの詳細設定 -> 環境変数

*2:私の環境ではExplorerから直接開けなかったので、VCから開きます

*3デバッグビルドしたなら"freetype***_D.lib"もできています

2009-08-12

Intel MacでSDLを使ったプログラムをDMDでコンパイルしapp形式にする

Mac OS X 上でSDLを使ったプログラムをDMDでコンパイルしてもそのままでは起動できなくて苦労しました。

試しに以前作成したソフトウェアMac OS X 10.4 Intel上で使えるようにしてみたのでその記録を残しておきたいと思います。

1. はじめに

ビルドに使用した環境は次の通りです。

DMDと後述のsdlbootがPowerPCに対応していないため、IntelMac OS X向けのみの説明です。

2. SDL.frameworkを使ったビルド

Mac OS XSDLを使用するには大きく分けて

の三つの方法があります。

ここでは作成したプログラムを配布する時のことを考えまして、三番目の「フレームワークを使う」方法をとることにします。

dmdでリンクするときには、リンカオプションを"-Lリンカオプション"のようにする必要があります。たとえばSDL.frameworkを使ってリンクするときには次のようになります。

$ dmd -L-framework -LSDL obj1.o obj2.o -ofprogram

otoolを使って使用しているライブラリを調べると、私のプログラムではSDL, SDL_image, SDL_mixerを使っていることが確認できます。

$ otool -L program
program:
        @executable_path/../Frameworks/SDL.framework/Versions/A/SDL (compatibility version 1.0.0, current version 1.0.0)
        @executable_path/../Frameworks/SDL_mixer.framework/Versions/A/SDL_mixer (compatibility version 1.0.0, current version 1.0.0)
        @executable_path/../Frameworks/SDL_image.framework/Versions/A/SDL_image (compatibility version 1.0.0, current version 1.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.3.11)

フレームワークはコンピュータ全体にインストールしてもいいですし、自分だけが使えるようにインストールしてもかまいません。

前者は"/Library/Frameworks/", 後者は"$(HOME)/Library/Frameworks/"にSDL.frameworkを配置すれば(自分の環境では)使用することができます。

3. sdlbootを使って起動する

さてビルドに成功したプログラムを実行してみると、次のようなエラーが出て起動できません。

$ ./program
****-**-** **:**:**.***  program[593] *** _NSAutoreleaseNoPool(): Object 0x******** of class NSLock autoreleased with no pool in place - just leaking
(中略)
****-**-** **:**:**.*** program[***] *** Uncaught exception: <NSInternalInconsistencyException> Error (1002) creating CGSWindow
Trace/BPT trap

SDLを使ったプログラムをMac OS X用にビルドするとCocoaの初期化フェーズを飛ばしてしまうのが原因のようです。詳しくはsdlbootのページを見てください。

main_hook.tgzを上記リンク先からダウンロードし展開します。

main_hook/sdlboot内のビルド済みファイルはFinkのSDLパッケージを使っているようなので、フレームワークを使ってビルドし直します。

私が修正したMakefileの内容を記述しておきます。

all: sdlboot.dylib

sdlboot.dylib: sdlboot.o
#	$(CC) -dynamiclib -fPIC -o $@ $< `sdl-config --libs | sed s/-lSDLmain//`
#	$(CC) -flat_namespace -dynamiclib -init _sdlboot_init_ -single_module -fPIC -o $@ $< `sdl-config --libs | sed s/-lSDLmain//`
	$(CC) -dynamiclib -fPIC -o $@ $< -framework SDL -framework Cocoa

sdlboot.o: sdlboot.m ../main_hook.c
#	$(CC) -c -fPIC $< `sdl-config --cflags`
	$(CC) -c -fPIC $< -framework SDL -I/Library/Frameworks/SDL.framework/Headers -framework Cocoa

clean:
	rm -f *.o *.dylib

ビルドしたプログラムと同じ場所にsdlboot, sdlboot.dylibをコピーし、

$ ./sdlboot ./program

のように使うとやっと起動することができるようになりました。

(おまけ) gdcを使ってビルドする場合

Mac OS X用のgdcを使っている場合はSDLのDevelopment用のアーカイブの中にあるSDLMain.mを使うことで起動できない問題を回避できます。

D言語プログラムをgdcでビルドするときには、

// via http://gamehell2000.googlecode.com/svn/trunk/omega/koke/boot.d
version (darwin) {
  extern (C) int _d_run_Dmain(int argc, char* argv[]);
  extern (C) int SDL_main(int argc, char* argv[]) {
    return _d_run_Dmain(argc, argv);
  }
}

というコードを追加しておき、SDL-devel-1.2.X-extras.dmgの中のSDLMain.mと一緒にコンパイルすることで正しい順番でCocoaアプリケーションの初期化作業が行われるようになります。

この方法ですとsdlbootを使わずに起動できるようになるのでdmdと比べると簡単です。

4. .app形式の作成

最後にせっかくですのでパッケージ化してみます。MacでSDLアプリケーションの作り方を見ると、Info.plistを作らなくても実行できることがわかりました。簡単なのでその方法にすることにします。

4.1. 必要なファイル構造を作る
$ mkdir -p test.app/Contents/MacOS
$ cp program sdlboot sdlboot.dylib test.app/Contents/MacOS

この状態でtest.appを開くとtest.app/Contents/MacOS/testを実行してくれるようです。

4.2. sdlbootでprogramを起動するスクリプトを作る

上述のようにtest.appを開くとtest.app/Contents/MacOS/testを実行してくれますが、今回は引数を渡す必要があるのでシェルスクリプトを使います。WindowsやLinuxなどで動くプログラムと同じソースコードが使えるようにするため、カレントディレクトリをプログラムと同じ場所に変更するスクリプトを用意します。

$ echo -e '#!/bin/sh\ncd (dirname $0)\n./sdlboot ./program' > test.app/Contents/MacOS/test
$ chmod +x test.app/Contents/MacOS/test
4.3. アイコン画像

アイコン画像は、Finderからアプリケーションの「情報を見る」→アイコンのペースト、で貼り付けることができます。

パッケージの作成に必要な作業はこれだけですが、

  • PROGRAM.app/Contents/MacOS/ にPROGRAMを置けばそのファイルが実行される
  • sdlbootを使った場合は、メニューバーに表示されるプログラム名は実際に実行しているファイル名になる
  • アプリケーションの情報を見ても著作権情報やバージョンが表示されない
  • アイコンを付けるとPROGRAM.app/ContentsにIconという名前の不可視ファイルが作成される

という制限があります。ちゃんとInfo.plistを書いてPROGRAM.app/Contents/に置いた方がユーザフレンドリーなプログラムになるでしょう。

5. 配布するとき

開発環境のみで起動するにはこのままでいいですが、使用しているフレームワーク(今回はSDLフレームワーク)を

PROGRAM.app/Contents/Frameworks

におくことで、フレームワークがインストールされてない環境でもSDLを使うことができます。

またアイコンをFinderから貼り付けた場合にはアイコンファイルがパッケージ内に保存されているので、dmg(ディスクイメージ)やFinderからzip書庫にまとめた方がいいでしょう。

6. 参考にしたページ

MacOSX版Ruby/SDLバイナリの作成方法
Info.plistの書き方にも触れられています。
MacOSX+SDLでの配布物作成法
SDL.frameworkを使ってビルドする方法など。

2009-04-26

標準エラーに出力すると勝手に終了してしまう

Windows (XP) 環境で標準エラーに出力するとアプリケーションが勝手に終了してしまいました。

ただし終了するのはコンソールを非表示にしたときに標準エラーに出力する場合のみで、コンソールが見えている状態や標準出力への出力なら問題がないようです。

stderr.d のソースコード(ウィンドウを出してループするだけ)

ウィンドウを出しておくのにSDLを使用しています。

// Using dmd v1.043, Tango 0.99.8, Windows XP Home SP3
import SDL;
import tango.io.Stdout;

void main() {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_SetVideoMode(640, 480, 8, SDL_SWSURFACE);

  SDL_Event event;
  bool done = false;

  while (!done) {
    while (SDL_PollEvent(&event)) {
      switch (event.type) {
      case SDL_QUIT:
        done = true;
        break;
      default:
      }
    }

    Stderr("stderr").newline;
    SDL_Delay(33);
  }

  SDL_Quit();
}

コンソールが見えないようにしてDMDでコンパイルします。

$ dmd stderr.d -I../SDL ../SDL/*.lib -L/subsystem:windows:4.0

端末から実行すると"stderr"という文字列が沢山流れますが、ダブルクリックで実行するとすぐに終了してしまいます。

また"-L/subsystem:windows:4.0"を取ってやれば、(実行時にコンソールが表示されますが)終了しなくなります。

原因はよく分かってないので調査してみます(もしくは諦めます;-P)。

追記

Windows 7に環境を移行してみました。相変わらず原因はわかって(調べて)いないのですが、

$ dmd -L/subsystem:windows

というオプションをつけてコンパイルすると標準出力すら出せなくなります。

ですので、

デバッグ
"-L/subsystem:windows"オプションをつけない。標準(エラー)出力は出してよい。
リリース時
"-L/subsystem:windows"オプションをつける。標準(エラー)出力は出さない。

このように切り替えてビルドすることにしました。

Connection: close