最後のご奉公?

Macが事実上のメイン開発機になっていることもあり、自宅のWindowsPCにはもう金を掛けないことにしたはずだったのだが、ひょんなことからWindowsアプリについて調べることが必要になったこともあり、考え直して、今度こそ最後のつもりでアップグレードをすることにした。

去年から緊縮財政が続いており、掛けられるコストはできるだけ低くしたいが、それなりに今後数年は使えるスペックにはしたい。

のでまずは32bitプラットホームにしがみつく理由になってきたメモリとシステムストレージを刷新することにした。

メモリ

Kingston 12GB 1600MHz DDR3 Non-ECC CL9 DIMM (Kit of 3) XMP KHX1600C9D3K3/12GX

Kingston 12GB 1600MHz DDR3 Non-ECC CL9 DIMM (Kit of 3) XMP KHX1600C9D3K3/12GX

ストレージ

インテル Boxed SSD 335 Series 240GB MLC 2.5inch 9.5mm Jay Crest Reseller BOX SSDSC2CT240A4K5

インテル Boxed SSD 335 Series 240GB MLC 2.5inch 9.5mm Jay Crest Reseller BOX SSDSC2CT240A4K5


メモリは既存の6GBと合わせて18GB(同時挿し可能ならだが)。これだけ増やしたからには64bitプラットホームに移行するしかないだろう。それにしても12GB(4GBx3)で1万しないんだな。
ストレージに関してはMLCといえどこの性能(Read500MB/s、write450MB/s)で240GBの高速SSDが2万しないのだから、本当に凄い時代だなと思う。

今考えるWindowsアプリケーションの作り方

ふとしたきっかけでWindowsアプリケーションを作ることになりそうだ。
それはいいんだが、昔と違って今Windowsアプリケーションを作る方法はたくさんある。

XAMLは共通するマークアップなので方法には含めていない。
しかし...Microsoft謹製だけでもこれだけあるのに、どれを選べばいいんだろう。(苦笑)

要件としてはWindowsXP※、WindowsVista、Windows7、ひょっとするとWindows8でも動くものにしなければならない。今後消えゆく物をターゲットにするのも精神的に嫌なので、1.3.は除外する。WinRTもWindows8以降だろうか。なので5.も無い。

そうすると残ったのはWPFとHTMLなんだが、どちらを選べば良いんだろう。HTMLアプリケーションはWindows8では標準の方法の一つとなるようだが、それ以前ではHTA等の手段をとる必要があるし、制約も多い。

ということで、現在動いているOSのバージョンを鑑みると、WPFが無難というか、漸くWPFが主役の時代が来るのだろうか。

※いい加減こいつだけは使うのを止めなさいというべきだと思うのだが...

FMDBExtensionsを公開しました

Kazzz/FMDBExtensions · GitHub
FMDBExtensionsはiOSのSQLite用ライブラリィであるFMDBとandroidのSQLiteDatabaseクラスにインスパイアされて書いた、iOSのSQLiteデータベース用ライブラリィです。

経緯

iOSのアプリケーションを開発する際にSQLiteデーターペースを扱うための高次のライブラリィが提供されていない所に、FMDBというオープンソースライブラリィがあることを知り、大変に便利に使わせて頂きました。

ccgus/fmdb · GitHub

一方でandroidSQLiteにアクセスする際にはSDK標準で提供されているSQLiteDatabaseクラスを使用してました。SQLをあまり意識せずに使える一連のAPI群は非常に便利であり、生のSQLを書くのが嫌いな私はandroidと同様の使い勝手をFMDBに求めました。それがFMDBExtensionsのきっかけです。

前提条件

FMDBを使える環境とFMDB本体(Xcode、ARC下での開発を前提にしています。)
私はARC,Blocksが有効になった以降にiOSのプログラミングを開始しているためにARCを前提にしています。

提供するもの

FMDBExtensionsはFMDatabaseクラスとFMResultSetのカテゴリとして拡張機能を提供します。

  • FMDBExtensions

FMDBExtensionsを使用する場合はこのヘッダをimportします

  • FMDatabase+FMDBExtensions

FMDatabaseの機能を拡張します

- (BOOL)isOpen;
- (BOOL)isClose;
- (int)rowCount:(NSString*)tableName columnName:(NSString*)columnName whereClause:(NSString*)whereClause whereArgs:(NSArray*)whereArgs;
- (BOOL)deleteAllRows:(NSString*)tableName;
- (long)insert:(NSString*)tableName nullColumnHack:(NSString*)nullColumnHack values:(NSMutableDictionary*)values;
- (int)delete:(NSString*)tableName whereClause:(NSString*)whereClause whereArgs:(NSArray*)whereArgs;
- (int)update:(NSString*)tableName values:(NSMutableDictionary*)values whereClause:(NSString*)whereClause whereArgs:(NSArray*)whereArgs;
- (FMResultSet*)query:(NSString*)tableName columns:(NSArray*)columns selection:(NSString*)selection selectionArgs:(NSArray*)selectionArgs groupBy:(NSString*)groupBy having:(NSString*)having orderBy:(NSString*)orderBy;
- (FMResultSet*)query:(NSString*)tableName distinct:(BOOL)distinct columns:(NSArray*)columns selection:(NSString*)selection selectionArgs:(NSArray*)selectionArgs groupBy:(NSString*)groupBy having:(NSString*)having orderBy:(NSString*)orderBy limit:(NSString*)limit offset:(NSString*)offset;
- (void)logDBError:(NSString*)sql;
- (void)open:(void (^)(void))contextBlock;
- (void)open:(void (^)(void))contextBlock flags:(int)flags;
- (void)openWithTransaction:(void (^)(void))contextBlock;
- (void)openWithTransaction:(void (^)(void))contextBlock flags:(int)flags;
- (int)getVersion;
- (void)setVersion:(int)version;

ヘッダに書かれているコメントとテスト(FMDBExtensionsTests)をみれば見れば、FMDBを知っているプログラマの方であれば何をしているかは大体分かると思います。

  • FMResultSet+FMDBExtensions

FMResultSetを拡張します

- (id)objectForColumnNameNotNull:(NSString*)columnName nullHack:(id)nullHack;
- (NSString*)stringForColumnNotNull:(NSString*)columnName nullHack:(id)nullHack;

同上です。

  • BBSQLiteOpenHelper

androidのSQLiteOpenHelperと同様のヘルパクラスを提供します。イニシャライザで指定された名前のデータベースがパッケージバンドルにあれば、それをDocumentディレクトリにコピーするという前処理を行います。邪魔な場合は消すかオーバライドして使います。

androidに存在しているデータベースのロック機能は未だ実装していません。必要であれば書くかもしれません。

使い方の例

Blocksを使ってデータベースのコンテキストブロックを構成できるようになっていますので、以下のようにcloseを省略したイディオムを利用できます。(もちろん各自で明示的にオープン、クローズを行うこともできます)

[db open:
 ^{
     NSString* sql = [[NSString alloc] initWithFormat:@"SELECT * from %@ WHERE %@ = ? and %@ = ?", TBL_NAME, COL_USER_ID, COL_YEAR_MONTH];
     FMResultSet* rs = [db executeQuery:sql, @"Kazzz", @"201303"];

     BOOL rsResult = (rs.next);
     STAssertTrue(rsResult, nil);

     STAssertEqualObjects([rs stringForColumn:COL_USER_ID], @"Kazzz", nil);
     STAssertEqualObjects([rs stringForColumn:COL_YEAR_MONTH], @"201303", nil);
     STAssertEqualObjects([rs stringForColumn:COL_DAY], @"02", nil);
 }];

Blocksの制限や癖(循環参照)の問題はそのまま残っていますので、理解されている方が使用してください。

同様にコンテキストブロックの先頭でトランザクションを開始して、終了時にコミットを実行するコンテキストブロックも書けます。

    [db openWithTransaction:
     ^{
         [db delete:TBL_NAME
           whereClause:[[NSString alloc] initWithFormat: @"%@ = ? and %@ = ? and %@ = ?"
                        , COL_USER_ID, COL_YEAR_MONTH, COL_DAY]
             whereArgs:@[@"Kazzz", @"201303", @"02"]];

     }];

例外が発生した場合は自動的にロールバックするようになっていますので、気に入らない場合は自分でブロックを定義してみてください。カテゴリによる拡張は自由ですから。

テスト

FMDBExtensionsTests.mにより、データベースのCRUDに関しての簡単なテストをしています。全然足りないと思いますが、必要な方は自分でテストを書いてください。

ライセンス

FMDBと同様にMITライセンスで配布します

謝辞

FMDBを書かれたAugust Mueller氏とgithub、及びコミュニティの皆さんに感謝します。

いなばのタイカレー

[いなば食品] ツナとタイカレー(レッド)125g

[いなば食品] ツナとタイカレー(レッド)125g


話題の「いなばのタイカレー」だが、食べてみた。

実際に食べたのはこの「レッド」ともう一つ「イエロー」。(Amazonにイエローが無かった)
はっきり言おう。これは美味しい。

お店によっては100円程度で売られているらしいが、150円そこそこの価格でこの味にはびっくりだ。 缶詰にありがちな臭さもないので、別容器に入れて温める必要もなく冷たいままで暖かいご飯の上に掛ければおいしいカレーの出来上がり。一缶で茶碗2杯はいける。
実際にタイ現地で作っているらしいが、ココナツミルクが強すぎるでもなく、スパイスも弱めで日本人の口に合うように作られている。

名前からしてレッドの方が辛いイメージがあったのだが、レッドの方がマイルドであり、イエローの方が辛い。私はカレーなどの唐辛子の辛さには非常に弱く、レトルトでも中辛以上は食べたことが無いが、「イエロー」はその私が食べて顔が汗だく位。

辛いのが好きな方には「レッド」「イエロー」の他に「グリーン」という更に辛めのがあるらしいので、そちらをどうぞ。

JmDNSによるBonjourのサービス解決

android.net.nsdパッケージが現状バグで使えなさげだが、Bonjourを扱うには元々実績のあるJavaのライブラリィが提供されており、有り難いことにandroidからでも使えるのだ。

JmDNS

JmDNSはmDNS(Multicast DNS)とDNS-SD(DNS based Service Discovery)をサポートし、Bonjourとの完全な互換を歌うオープンソースのライブラリィであり、Apache License, Version 2.0ライセンス下で使用することができる。

Javaを使ったサービス探索〜解決のためのコード
static JmDNS jmdns;

jmdns = JmDNS.create();
jmdns.addServiceListener("_ipp._tcp.local.", new ServiceListener(){
    @Override
    public void serviceAdded(ServiceEvent event) {
        System.out.println("Service added   : " + event.getName() + "." + event.getType());
        jmdns.requestServiceInfo(event.getType(), event.getName());
    }
    @Override
    public void serviceRemoved(ServiceEvent event) {
        System.out.println("Service removed : " + event.getName() + "." + event.getType());
    }
    @Override
    public void serviceResolved(ServiceEvent event) {
        System.out.println("Service resolved: " + event.getInfo());
    }
});

JmDNSクラスのファクトリを呼びインスタンスを取得した後に、対象となるサービスタイプに対してリスナを登録する。android.net.nsd同様に簡単だ。ポイントは、serviceAddedメソッド中でrequestServiceInfoメソッドを呼び出していること。これをやらないとserviceResolvedが飛んでこないのである。 ※

なお、JmDNSのサービスと個々のソケット通信はIPマルチキャストがベースになっているのだが、そもそもandroidではデフォルトでマルチキャストが無効にされているために、このシンプルなコードを使うことはできない。

よってまずはandroid上でJmDNSを使うためにマルチキャストを有効にしなくてはならない。

androidでIPマルチキャストのパケットを受け取るために必要なコード
// マルチキャストアドレス宛てパケットを受け取る
WifiManager wifiManager = (WifiManager) context.getSystemService(android.content.Context.WIFI_SERVICE);
WifiManager.MulticastLock multicastLock = wifiManager.createMulticastLock("for JmDNS"); // デバッグのためのタグ
multicastLock.setReferenceCounted(true);
multicastLock.acquire();

なお、JmDNSはメインスレッドでネットワーク操作を行うため、android3.0以降のバージョンで動かすとNetworkOnMainThreadException例外が発生してしまう。

 00:00.267: W/dalvikvm(1553): threadid=1: thread exiting with uncaught exception (group=0xa6f5e288)
 00:00.267: E/AndroidRuntime(1553): FATAL EXCEPTION: main
 00:00.267: E/AndroidRuntime(1553): android.os.NetworkOnMainThreadException
 00:00.267: E/AndroidRuntime(1553): 	at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1117)
 00:00.267: E/AndroidRuntime(1553): 	at java.net.InetAddress.lookupHostByName(InetAddress.java:385)
 00:00.267: E/AndroidRuntime(1553): 	at java.net.InetAddress.getLocalHost(InetAddress.java:365)
 00:00.267: E/AndroidRuntime(1553): 	at javax.jmdns.impl.HostInfo.newHostInfo(HostInfo.java:76)
 00:00.267: E/AndroidRuntime(1553): 	at javax.jmdns.impl.JmDNSImpl.<init>(JmDNSImpl.java:408)
 00:00.267: E/AndroidRuntime(1553): 	at javax.jmdns.JmDNS.create(JmDNS.java:60)

この例外は以下のアドホックなコードをJmDNSクラスの生成より前に追加することで回避することが可能になるが

if (android.os.Build.VERSION.SDK_INT > 9) {
    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);
}

バックグラウンドスレッドやAsyncTask内ででJmDNSを生成すれば良いだけなので、そうすべきだろう。(今回は割愛する)

実行結果

前回も紹介したが、Xcode付属のプリンタシミュレータで公開されている仮想のプリンタはBonjourサービスとしてシステムに登録されているため、これをテストに使おう。

macにはdns-sdコマンドがインストールされており、mDNSホストつまりBonjourサービス名を解決することができるので、dns-sdコマンドでippプリンタのサービスをダンプしてみたが、Bonjourサービスとしてプリンタが登録されていることが解る。

androidではemulatorを使い、最初に説明したIPマルチキャスト、JmDNSクラスの生成とそのリスナのコールバックメソッドの内容をTextViewにappendするコードを組んだテストアプリケーションを実行した。

このようにdns-sdコマンドでダンプされたのと同じ、プリンタシミュレータで登録されたサービスの追加、解決が成功しているのが解るだろう。

※ ネット上で対象になっている version 3.4.1には、PRT(ipp)のサービスサブタイプを解決できないというバグがある。
JmDNS / Bugs / #112 JmDNS 3.4.1 does not answer to PTR requests on sub-types
このバグは最新版である3.4.2では修正されているので、サンプルソースコード等に同梱されているソースコードやjarではなく、リポジトリ上の最新ソースコードを使わなくてはならない。
http://jmdns.svn.sourceforge.net/viewvc/jmdns/trunk/

Audirvana 1.4.6

Release 1.4.6

  • インテジャーモード(Integer Mode)に音質の向上のための新しいメソッドを追加した。新しい"mode1"がデフォルトとなり、従来のメソッドを"mode 2"とする。
  • .audlicファイルを開くデフォルトのアプリケーションをAudirvana Plusとした。 ライセンスファイルを開くと起動する。
  • 各種BugFix
    • iTunes統合モードで再生がストールするバグを修正
    • 32bitアーキテクチャでクラッシュする可能性を修正
    • ドイツ語のシステム最適化パネルでテキストが崩れているのを修正
    • iTunes(11.0.2)で再生中にハングアップが発生していたのを修正

新たに追加された"Mode 1"がどんな修正なのかが気になるな。

NsdServiceは時期尚早?

androidからのBonjourをテストするのに最初JmDNSの実装を使う予定だったのだが、android 4.1以降サポートされたandroid.net.nsdバッケージがmDNS(Multicast DNS)とDNS-SD(DNS based Service Discovery)をサポートしたとのことなので、まずはそちらから試してみることにした。

NsdManagerによりサービスを探索する疑似コード
NsdManager nsdManager = (NsdManager) getSystemService(NSD_SERVICE);
nsdManager.discoverServices("_ipp._tcp.local.", NsdManager.PROTOCOL_DNS_SD, new NsdManager.DiscoveryListener() {
    @Override
    public void onStopDiscoveryFailed(String serviceType, int errorCode) {
    }
    @Override
    public void onStartDiscoveryFailed(String serviceType, int errorCode) {
    }
    @Override
    public void onServiceLost(NsdServiceInfo serviceInfo) {
    }
    @Override
    public void onServiceFound(NsdServiceInfo serviceInfo) {
    }
    @Override
    public void onDiscoveryStopped(String serviceType) {
    }
    @Override
    public void onDiscoveryStarted(String serviceType) {
    }
});

サービスを探索するコードはNsdManager.DiscoveryListenerをnsdManagerに登録することにより、各種イベントが発生した際にリスナのメソッドが呼ばれる仕組みであり、非常に明快だ。

しかしまずはEmulator(4.2と4.2)で実行して見ると、テストコードが全く動かない。それどころかEmulatorがリブートしているようだ。(GALAXY Nexus実機(android 4.2.2)でもテストしたが、本当にリブートする)

ログを拾ってみると、NsdManager#discoverServicesの実行後にFATAL EXCEPTIONを発生していることが分かった。

03-03 17:11:44.229: D/NsdService(9010): Discover services
03-03 17:11:44.229: W/dalvikvm(9010): threadid=44: thread exiting with uncaught exception (group=0xa6fb7288)
03-03 17:11:44.229: E/AndroidRuntime(9010): *** FATAL EXCEPTION IN SYSTEM PROCESS: NsdService
03-03 17:11:44.229: E/AndroidRuntime(9010): java.lang.NullPointerException
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at com.android.server.NsdService$ClientInfo.access$1200(NsdService.java:800)
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at com.android.server.NsdService$NsdStateMachine$EnabledState.requestLimitReached(NsdService.java:243)
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at com.android.server.NsdService$NsdStateMachine$EnabledState.processMessage(NsdService.java:291)
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at com.android.internal.util.StateMachine$SmHandler.processMsg(StateMachine.java:895)
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at com.android.internal.util.StateMachine$SmHandler.handleMessage(StateMachine.java:756)
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at android.os.Handler.dispatchMessage(Handler.java:99)
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at android.os.Looper.loop(Looper.java:137)
03-03 17:11:44.229: E/AndroidRuntime(9010): 	at android.os.HandlerThread.run(HandlerThread.java:60)

NsdManagerの取得は問題なく実行されているので内部の処理中に発生する例外だと思われるが、100%例外が発生する訳ではなくたまに問題無く動作したり、デバッグモードでは動いたりするのでバグの可能性が高い。

#当然既知だ。
Issue 39750 - android - NSD causes Nexus 7 device to spontaneously restart. - Android - An Open Handset Alliance Project - Google Project Hosting

Issue 35803 - android - NsdService causes fatal exception on discoverServices - Android - An Open Handset Alliance Project - Google Project Hosting