iOSに設定されているプロキシの自動構成スクリプトを使ってみたい

.NETのHttpWebRequestクラスに対してiOSWiFi接続に設定されている自動構成スクリプトの設定を適用するにはどうしたらいいかの調査。

.NET本来の方法

System.Net.WebRequest.GetSystemWebProxy()関数で取得すればあとは勝手にやってくれる模様。この方式をXamarin.iOSで使うと環境変数HTTP_PROXYをごにょごにょしているだけだった。これでは使えない。

iOS本来の方法

CFNetworkCopyProxiesForAutoConfigurationScript関数に対して自動構成スクリプトの内容とURLを渡せば適切なプロキシのアドレスを返してくれる模様。プロキシアドレスが取得できればそれをSystem.Net.WebProxyクラスに渡してやればよい。
これをXamarin.iOSで書いてみた。

string url = "http://www.google.co.jp";

// 自動構成スクリプトの内容は本来なら別の手段で取得するべき
string script = @"function FindProxyForURL(url,host) {
 if(isPlainHostName(host)||
    isInNet(host,""127.0.0.1"",""255.255.255.255""))
    return ""DIRECT"";
 else if(isPlainHostName(host) ||
    isInNet(host,""192.0.0.0"",""255.0.0.0""))
    return ""DIRECT"";
 else
    return ""PROXY myproxy:8888; DIRECT"";
}
";

MonoTouch.CoreFoundation.CFProxy[] proxy = MonoTouch.CoreFoundation.CFNetwork.GetProxiesForAutoConfigurationScript(new NSString(script), NSUrl.FromString(url));

この場合何故かCFNetwork.GetProxiesForAutoConfigurationScriptの内部のObjective-Cの段階で例外が発生してしまう。リフレクションで調べるとCFNetworkCopyProxiesForAutoConfigurationScript関数は引数が3つだが、Xamarin.iOSは引数を2つしか渡していない。これが原因だろうか(バグ?)。

ならばと強引に呼んでみた。

[DllImport("/System/Library/Frameworks/CFNetwork.framework/CFNetwork")]
static extern IntPtr CFNetworkCopyProxiesForAutoConfigurationScript(IntPtr proxyAutoConfigurationScript, IntPtr targetURL, IntPtr error);

// 自動構成スクリプトの内容は本来なら別の手段で取得するべき
string script = @"function FindProxyForURL(url,host) {
 if(isPlainHostName(host)||
    isInNet(host,""127.0.0.1"",""255.255.255.255""))
    return ""DIRECT"";
 else if(isPlainHostName(host) ||
    isInNet(host,""192.0.0.0"",""255.0.0.0""))
    return ""DIRECT"";
 else
    return ""PROXY myproxy:8888; DIRECT"";
}
";

NSObject error = new NSObject();
NSString aaa = new NSString(script);
NSUrl u = NSUrl.FromString(url);
IntPtr ret = CFNetworkCopyProxiesForAutoConfigurationScript(aaa.Handle, u.Handle, error.Handle);
NSArray array = new NSArray(ret);
NSDictionary[] dictionaryArray = NSArray.ArrayFromHandle<NSDictionary>(array.Handle);
array.Dispose();
System.Console.WriteLine("p={0}",dictionaryArray[0]); // => myproxy:8888

とりあえずプロキシアドレスを取得することができた。あとはこのコードを使用に耐えられるようにしなければならないが、

等を考えるとこのままではコードが足りなさすぎる。ここで時間切れ。

Windows7 64bit + Logitech LBT-UAN04C1BK + Sony DRC-BTN40

今まで使っていたSony VGP-BRM1のバッテリーがヘタってきたのでワイヤレスオーディオ環境を一新した。BluetoothドングルはLogitec LBT-UA200C1を使っていた。

新しい環境はLogitech LBT-UAN04C1BK + Sony DRC-BTN40の組み合わせ。何故こうしたのかは割愛するが、これが音が出るまでの作業が一筋縄ではなかった。

CSR社製スタックの出来の悪さは事前調査で知っていたので、スタックのインストールは特に問題なく終わった。
しかしいざDRC-BTN40とペアリングしようとするとBluetoothステレオオーディオドライバとBluetoothオーディオコントローラドライバのインストールに失敗してしまう。
画面に表示されるのは「デバイスインストール中にエラーが発生しました。その名前はサービス名 またはサービス表示として既に使われています。」。名前とはいったい何なんだ!?不親切すぎてなにがなんだか全くわからない。
調べると"C:\Windows\inf\setupapi.dev.log"ファイルに詳細な情報がでていた。

     inf:                               {Install Inf Section [AVFilter.NT.Services]}
     inf:                                    AddService=csr_bthav,0x00000002,csr_bthav_Service_Inst  (csrbthav.inf line 108)
     inf:                                    ServiceType=1  (csrbthav.inf line 112)
     inf:                                    StartType=3  (csrbthav.inf line 113)
     inf:                                    ErrorControl=1  (csrbthav.inf line 114)
     inf:                                    ServiceBinary=C:\Windows\system32\drivers\csrbthav.sys  (csrbthav.inf line 115)
     inf:                                    DisplayName="Bluetooth AVプロファイル"  (csrbthav.inf line 111)
!!!  dvi:                                    Add Service: Failed to create service 'csr_bthav'.
!!!  dvi:                                    Error 1078: The name is already in use as either a service name or a service display name.
!!!  inf:                               {Install Inf Section [AVFilter.NT.Services] exit(0x00000436)}
!!!  inf:                               Error 1078: The name is already in use as either a service name or a service display name.
!!!  dvi:                               Error while installing services.
!!!  dvi:                               Error 1078: The name is already in use as either a service name or a service display name.
!!!  dvi:                               Cleaning up failed installation
!!!  dvi:                               Error 1078: The name is already in use as either a service name or a service display name.

(略)

     inf:                               {Install Inf Section [csravrcp_DDI.NT.Services]}
     inf:                                    Addservice=csravrcp,0x00000002,csravrcp_Service  (csravrcp.inf line 57)
     inf:                                    ServiceType=0x00000001  (csravrcp.inf line 63)
     inf:                                    StartType=0x3  (csravrcp.inf line 64)
     inf:                                    ErrorControl=0x00000001  (csravrcp.inf line 65)
     inf:                                    ServiceBinary=C:\Windows\system32\DRIVERS\csravrcp.sys  (csravrcp.inf line 66)
     inf:                                    DisplayName="Bluetooth AVRCPプロファイル"  (csravrcp.inf line 62)
     inf:                                    LoadOrderGroup="Extended Base"  (csravrcp.inf line 67)
!!!  dvi:                                    Add Service: Failed to create service 'csravrcp'.
!!!  dvi:                                    Error 1078: The name is already in use as either a service name or a service display name.
!!!  inf:                               {Install Inf Section [csravrcp_DDI.NT.Services] exit(0x00000436)}
!!!  inf:                               Error 1078: The name is already in use as either a service name or a service display name.
!!!  dvi:                               Error while installing services.
!!!  dvi:                               Error 1078: The name is already in use as either a service name or a service display name.
!!!  dvi:                               Cleaning up failed installation
!!!  dvi:                               Error 1078: The name is already in use as either a service name or a service display name.

どうやらcsr_bthavとcsravrcpという名前の2つのドライバ登録に失敗している模様。
というわけで音が出るまでに行った作業は以下の通り。

1.作業を失敗した時のために「システムイメージの作成」でWindowsをバックアップ
2.今まで通りVGP-BRM1を接続
3.管理者権限でコマンドプロンプトを起動して以下のコマンドを実行

set devmgr_show_nonpresent_devices=1
start devmgmt.msc

4.デバイスマネージャが表示されるのでメニューから「非表示のデバイスの表示」を選択
5.表示されるデバイスの中からBluetoothと名の付く物を片っ端から削除
6.VGP-BRM1の電源Off、LBT-UAN04C1BKを抜く
7.Windows再起動
8.LBT-UAN04C1BK付属のCDからCSR社製スタックをインストール
9.DRC-BTN40をペアリング→ドライバのインストールに失敗→無視
10.レジストリから

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\csr_a2dp
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\BthAvrcp

をキーごと削除
11.以下のレジストリを読み込ませて手動でドライバ登録

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\csr_bthav]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,\
  72,00,69,00,76,00,65,00,72,00,73,00,5c,00,63,00,73,00,72,00,62,00,74,00,68,\
  00,61,00,76,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Bluetooth AVプロファイル"

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\csravrcp]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,44,00,\
  52,00,49,00,56,00,45,00,52,00,53,00,5c,00,63,00,73,00,72,00,61,00,76,00,72,\
  00,63,00,70,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Bluetooth AVRCPプロファイル"
"Group"="Extended Base"

12.DRC-BTN40電源断
13.Windows再起動
14.デバイスマネージャからびっくりマークの付いているデバイスのドライバを再インストール(自動)させる
15.DRC-BTN40電源入

音が出るようになった後は、勝手に登録されたアドインが原因でOutlook等のMS Officeがクラッシュするようになったのでアドインを無効にしが、その程度では効果が無いので削除した。

追記 2013/05/13 15:36
Outlookがまたしてもクラッシュするようになったので調べたら原因はBluetoothによるセキュリティ解除機能だった。レジストリ

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{5355DA8C-FE32-49b4-A567-A67535C86592}

を削除することで対応。

※この記事をみてデバイスの削除やらレジストリの操作をする方は自己責任で。

TimeZoneInfo.ConvertTimeFromUtcの戻り値のKindがおかしい

Xamarin.iOS v6.3.0.255と.NET v4.5で以下のコードの戻り値が異なっていた。

DateTime t1 = new DateTime(2013, 4, 19, 9, 0, 0, DateTimeKind.Utc);
DateTime t2 = TimeZoneInfo.ConvertTimeFromUtc(t1, TimeZoneInfo.Local);

t2.Kindは、

  • Xamarin.iOSはUnspecified
  • .NETはLocal

になる。
.NETの方が正しいと思うのだが。Xamarin.iOSのバグだろうか。

bug #9150

Xamarin Studio v4.0.3*1とXamarin.iOS v6.3.0.255の組み合わせで突然デバッグできなくなってしまった。
症状としてはブレークポイントのところでブレークする瞬間にSystem.NotImplementedException例外が起きるようになった。
この組み合わせで数日間まったく問題がなかったのでまったく原因が推測できない。
しかしググるとまさしくこの問題がBugzillaに登録されていた。
幸いパッチが添付されていたのでこれを適用したMonoDevelopをビルドして該当DLLをXamarin Studioに導入してみることにした。

Monodevelop自体のビルド方法はここを参考にした。

既に最新のMonoFrameworkとXcodeはインストールしてあるので、まずgitからソースコードを取得。

cd ~
mkdir temp
cd temp
git clone git://github.com/mono/monodevelop.git
pushd monodevelop
<||
ソースコードが取得できたらパッチを適用。次に以下のコマンドでビルド。
>||
export ACLOCAL_FLAGS="-I /Library/Frameworks/Mono.framework/Versions/Current/share/aclocal"
export PATH="/Library/Frameworks/Mono.framework/Versions/Current/bin:$PATH"
export DYLD_FALLBACK_LIBRARY_PATH=/Library/Frameworks/Mono.framework/Versions/Current/lib:/lib:/usr/lib
./configure --profile=mac
make

これでMono.Debugging.Soft.dllとMono.Debugging.Soft.dll.mdbができあがる。
この2つのファイルをXamarin Studio.appの中の同名ファイルと置き換えて問題が起きないことを確認した。

*1:v4.0.4.2でも同様だった

LIONにVNCその2

以前の記事でたまに固まる問題があることを書いたがようやく原因が分かった。
iOSシミュレータ。これだった。
ログインウィンドウに行く前にiOSシミュレータを終了するようにしてから固まることが全くなくなった。

launchctlで定期的にMonoTouch製Appをリリースビルドするには

MonoTouchのリリースビルドはコンソールから以下のコマンドで可能。

/Applications/MonoDevelop.app/Contents/MacOS/mdtool build ソリューションファイル名 -c:'コンフィグ名'

コンフィグ名とは'Ad-Hoc|iPhone'や'Release|iPhone'のこと。

上記をふまえるとビルド用シェルスクリプトは以下のようになる。

#/bin/sh

ulimit -n 1024
security unlock-keychain -p パスワード /Users/myuser/Library/Keychains/login.keychain
/Applications/MonoDevelop.app/Contents/MacOS/mdtool build ソリューションファイル名 -c:'コンフィグ名'

mdtoolはAppへ署名するためにキーチェインにアクセスしようとする。GUIの場合はアクセス許可の旨のダイアログが表示されるが、コンソールからアクセスするときはアクセスが拒否されてエラーで終わってしまう。なのでmdtoolの前にsecurityコマンドでキーチェーンをアンロックする必要がある。
ulimitはmdtoolが1プロセスで大量のファイルをオープンすることがあるので制限を解除する必要がある。

次にビルド用シェルスクリプトをlaunchctlに登録する。
以下のようなplistファイルを/Library/LaunchDaemons/に作成する。
ここでplistファイルのパーミッションは644。オーナーはrootにする必要がある。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.myapp.autobuild</string>
  <key>ProgramArguments</key>
  <array>
    <string>起動するコマンドへのフルパス(シェルスクリプト)</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Minute</key>
    <integer>00</integer>
  </dict>
  <key>RunAtLoad</key>
  <false/>
  <key>ExitTimeout</key>
  <integer>600</integer>
  <key>UserName</key>
  <string>myuser</string>
  <key>GroupName</key>
  <string>staff</string>
  <key>OnDemand</key>
  <true/>
  <key>SessionCreate</key>
  <true/>
</dict>
</plist>

LabelとProgramArgumentsはlaunchctlの仕様上必ず必要だが、MonoTouchをビルドするために必要なものとしてさらにUserName、GroupName、SessionCreateが必要になる。

UserName、GroupNameはMonoTouchのライセンスファイルへのアクセスに必要となる。
MonoTouchはライセンスファイルがユーザー毎に登録されている。この値を省略した場合はmdtoolがライセンスファイルにアクセスできず正しくビルドできない。よってMonoTouchをアクティベートしたユーザーを明示的に指定しなければならない。

SessionCreateはキーチェーンにアクセスの為に必要となる。
シェルスクリプト内のsecurityコマンドでアンロックをしているがこれだけでは足りない模様。SessionCreateを明示的にtrueにすることでアクセスできるようになる。


追記 2013/03/21 10:25
ulimitを追加した。

App自身で強制終了するには

Appが自分自身で強制終了したい場合、MonoTouchでは以下のコードで強制終了が可能になる。

UIApplication.SharedApplication.PerformSelector(new Selector("terminateWithSuccess"), null, 0f);

試してはいないがもう一つ見つけたのがInfo.plistでUIApplicationExitsOnSuspendを有効にした上で、

Environment.Exit(0);

この場合はAppがマルチタスク非対応になってしまう。

アプリ審査において、App自身による強制終了はユーザーに判りやすいUIで終了させることが重要であってコード(方法)は不問のようだが、これらのコードを使って審査を通るかどうかは不明。