Hatena::ブログ(Diary)

Kazzzの日記 このページをアンテナに追加 RSSフィード

2013-05-05

[][][][]意外な落とし穴 (Windows XPでのアイコンサイズ制限)

マルチOSで動くWPFアプリケーションを書いているのだが、動作テストをしていると

Windows 8
Windows 7
Windows Vista
Windows XP ×

という結果。
Windows XPだけがどうやっても動かない。仕掛けたNLogでのログを見てみると例外が発生しているようだ。

2013-05-04 13:11:06.3789 [Xxxxx] Xxxxx.App.PerformStartup [Error] PerformStartup error  System.Reflection.TargetInvocationException: 呼び出しのターゲットが例外をスローしました。 ---> System.Windows.Markup.XamlParseException: ''System.Windows.Baml2006.TypeConverterMarkupExtension' の値の指定時に例外がスローされました。' 行番号 '59'、行位置 '4'。 ---> System.IO.FileFormatException: イメージ形式を認識できません。 ---> System.Runtime.InteropServices.COMException: HRESULT からの例外: 0x88982F07
   --- 内部例外スタック トレースの終わり ---
   場所 System.Windows.Media.PixelFormat.GetPixelFormat(SafeMILHandle bitmapSource)
   場所 System.Windows.Media.Imaging.BitmapSource.UpdateCachedSettings()
   場所 System.Windows.Media.Imaging.BitmapSource.set_WicSourceHandle(BitmapSourceSafeMILHandle value)
   場所 System.Windows.Media.Imaging.BitmapFrameDecode.FinalizeCreation()
   場所 System.Windows.Media.Imaging.BitmapSource.CompleteDelayedCreation()
   場所 System.Windows.Media.Imaging.BitmapSource.get_WicSourceHandle()
   場所 System.Windows.Media.Imaging.BitmapFrameDecode..ctor(Int32 frameNumber, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption, BitmapFrameDecode frameDecode)
:
略
:
   --- 内部例外スタック トレースの終わり ---
   場所 System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   場所 System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache)
   場所 System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache)
   場所 System.Activator.CreateInstance(Type type, Boolean nonPublic)
   場所 System.Activator.CreateInstance(Type type)
   場所 Mandarine.MVVM.Controller.AppController.ShowMainWindow(Type type)
   場所 Xxxxx.App.PerformStartup(Object sender, StartupEventArgs e) | Application.<.ctor>b__1 => Application.OnStartup => App.Pe

どうやらXAMLの読み込み時、イメージの読み込みに失敗しているらしい。
該当のXAML行は、以下のようにプロジェクト中のアイコン(.ico)を読み込んでいる部分だった。

    <Grid>
        <Tb:TaskbarIcon	x:Name="_notifyIcon" IconSource="/Xxxxx;component/Icons/xxxxx.ico"
	ToolTipText="Xxxxx for Windows" ContextMenu="{StaticResource ContextMenu}"/> 
    </Grid>

Windows XPということでpng形式は使えないので予めアイコンをBMPエンコードしておく等、すでに対策は打ってあるはずなのにどうしてアイコンファイルの読み込みで「イメージ形式を認識できません。」エラーなんだろうと思ったのだが、以下を読んでわかった。

Windows XP 用アイコン作成法

WindowsXPで使用できるアイコンはBMP形式である上に、サイズの上限は48x48ピクセルであり、それ以上のサイズの形式はアイコンとして扱うことができないらしい。 ガーン
ということでリソースエディタで256x256のアイコンをリソース中から削除して配布することでWindowsXPでも動作することを確認できた。

にしても....これは中々分からないし、Visual Studio側でWindows XPを対象としているプラットホームを選択した時点で警告位出してほしいところ。

2013-05-04

[][][]モジュール依存したコードを探す

参照を追加したは良いがアセンブリを配布するためにできるだけ依存したアセンブリを少なくしたい、そんな用途にぴったりなのが表題のReSharperの機能だ。
f:id:Kazzz:20130503192340p:image

コマンド実行後、何も依存がないとこの参照は削除できるということになる。依存がある場合は以下のようにアセンブリのネームスペース毎にダイアログが表示されるので、コードを詳しく調べることができる。
なお、内容を確認する必要がないのであれば"Safe Delete"コマンドにより即参照を削除することもできる。

f:id:Kazzz:20130503192341p:image


これでアセンブリを4つ減らして、Client Profileにすることができたよ。

2013-04-28

[][][]カスタム列挙型をリソース記述する

自分定義した列挙型(enum)があって、

namespace Mandarine.Common
{
    public enum Category
    {
        Orange = 00,
        Lemon  = 01,
        Mikan  = 02,
        Lime   = 03
    }
}

これをXAMLコマンド引数(CammandParameter)に設定したいのでResource Dictionaryに書きたいんだけど、流石にダメだろ思ってたら

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:common="clr-namespace:Mandarine.Common"
    >
    <Application.Resources>
        
        <common:Category x:Key="Orange">Orange</common:Category>
        <common:Category x:Key="Lemon">Lemon</common:Category>
        <common:Category x:Key="Mikan">Mikan</common:Category>
        <common:Category x:Key="Lime">Lime</common:Category>

あっさり普通に書けてしまった。#XAMLよくできてんな。

いかんいかん。こんなの使ってたら他のプラットホーム/言語で書けなくなるわ。

2013-04-24

[][]PInvokeStackImbalanceの呪い

PaSoRi等のNFCリーダー/ライターにC#からアクセスするためのオルタナティブな手段として有名なのがFeliCaLib※1だが、アプリケーションに組み込んでみると、今までに見たことのない例外が発生する。※2

PInvokeStackImbalance が検出されました。
Message: PInvoke 関数 'Mandarine!FelicaLib.BindDLL::LoadLibrary' がスタックを不安定にしています。PInvoke シグネチャがアンマネージ ターゲット シグネチャに一致していないことが原因として考えられます。呼び出し規約、および PInvoke シグネチャのパラメーターがターゲットのアンマネージ シグネチャに一致していることを確認してください。

どうやらアンマネジなライブラリをC#から呼び出す、いわゆる"P/Invoke"操作のチェックが厳格になったようで、仮に今までコンパイル時にエラーにならなくても、エラー無く動作していても、この例外がMDA(Managed Debugging Assistants)で検出されるらしい。
f:id:Kazzz:20130424121608p:image

例外が出ているものの引き続きコードをデバッグすることができるので、MDAでオフにすれば良いのだが、それだと何か負けた感が否めないのでなんとか消してやろうと試みた。

// 遅延ロード用Delegate定義
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr Pasori_open(String dummy);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int Pasori_close(IntPtr p);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int Pasori_init(IntPtr p);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr Felica_polling(IntPtr p, ushort systemcode, byte rfu, byte time_slot);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void Felica_free(IntPtr f);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void Felica_getidm(IntPtr f, byte[] data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void Felica_getpmm(IntPtr f, byte[] data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int Felica_read_without_encryption02(IntPtr f, int servicecode, int mode, byte addr, byte[] data);

本ライブラリは各関数へのポインタをデリゲートに格納しているがここの呼び出し規約をCdeclに明示的に変更することで、例外が止まった。

これでWindows8 64bit上からでもFeliCaLibが使えるようになった。

※1:FeliCaLib http://felicalib.tmurakam.org/
※2:FaliCaLibは現状x86(32bit)にしか対応していないので、AnyCPUではなくx86でコンパイルする必要がある。この場合当然だが、Wow上での動作となる。

2013-04-18

[][][]MVVM環境を見直す

以前Windows Phone7の開発環境を作る際に、かずきさんやうがやさん、MSDN等を初めとした様々なネット上の情報を収集してMVVM※環境を整えたのだが、かなり時間が経ってしまったのと、今回新たにWPFアプリケーションを書くに辺り、Silverlightベースとは違う面も多いということで、MVVM環境を見直すことにした。

見直す点としては、

  • ViewModelクラスの見直し
  • Commandクラスの見直し

大きくこの2点に絞って、アプリケーションのコーディングと共に考えている。

まずはViewModelの見直しだが、以下を実施する。

依存プロパティの導入

データバインドには依存プロパティの方が何かと有利だということなので、従来のCLRプロパティだけではなく依存プロパティを定義して、それをCLRプロパティからアクセスする構成に変更する。

依存プロパティの導入
    public static readonly DependencyProperty errorCodeProperty = DependencyProperty.Register( "errorCode", typeof(string), typeof( ErrorViewModel ), new UIPropertyMetadata(OnerrorCodeChanged));

T4 Templateの書き直し

ViewModelはよくあるように、アノテーション(カスタム属性)ベースで自動生成を行うようにしているが、上記依存プロパティの導入により自動生成するコードも依存プロパティを生成するように変更しなくてはならない。

クラス定義
    [PropertyDecl("UseGeoLocation", typeof(bool))]
    [PropertyDecl("UseCookie", typeof(bool))]
    [PropertyDecl("SelectLanguage", typeof(List<Language>))]
    public partial class SettingsViewModel : Mandarine.MVVM.ViewModel
    {}

T4 Templateで自動生成されるVideModel本体コード(抜粋)
    /// <summary>
    /// このクラスはE:\.Mandarine\MandarineWPF\CodeGen\GenerateViewModel_Dependency_property.ttにより
    /// 2013/04/17 14:38:59に自動生成されました。
    /// このファイルをエディタで直接編集しないでください
    /// </summary>
    public partial class SettingsViewModel : Mandarine.MVVM.ViewModel
    {
        #region 
        public static readonly DependencyProperty UseGeoLocationProperty = 
            DependencyProperty.Register( "UseGeoLocation", typeof(bool), typeof( SettingsViewModel ) 
                                            , new UIPropertyMetadata(OnUseGeoLocationChanged));
        static void  OnUseGeoLocationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
        {
            (obj as SettingsViewModel).NotifyPropertyChanged("UseGeoLocation");
        }
        public bool UseGeoLocation
        {
            get { return ( bool) GetValue( UseGeoLocationProperty ); }
            set { 
                SetValue( UseGeoLocationProperty , value ); 
                NotifyPropertyChanged(() => UseGeoLocation);
            }
        }
    :
    :

C#の特徴であるAutoBoxing、PartialClass、Lambda、依存プロパティそしてT4 Templateによる自動生成を行うことでViewModelの大部分をコーディングレスで実装できるのは素晴らしいものだ。

※MVVMとは"Model View ViewModel"パターンという、GUIアプリケーションの要素をModelとView、ViewModelの3つに分割して設計、実装する手法であり、主に.NET Framework C#(VB.NET)とXAML、それらを扱うツールを利用したプログラミングで使われている。

2013-04-16

[][]非同期処理3度

私の.NETにおける非同期処理のイディオムはC#2.0のAsyncDelegate、BackgroundWorker辺りで止まっているのだが、その後色々と方法が増えていた。

  • TAP(Task-based Asynchronous Pattern)
  • Rx(Reactive Extensions)
  • async/await

普段であれば一番新しいものを使えば良いやと思い、.NET4.5からサポートされたasync/awaitを採用するのだが、現在書いているアプリケーションWindows XP上で動かすことも想定しており、.NET4.5がインストールできない。

.NET Framework 4.5 のアプリケーションの互換性

.NET Framework 4.5 は Windows XP ではサポートされないことに注意してください。

なので、.NET4.5じゃないと使えないasync/awaitは使えない。Rxは基本的にはエクステンションなので、TAPを使わざるを得ないようだ。

2013-04-12

[][][]忘却の彼方

CreateWindowExの戻り値が0になってしまう件だが、コメントでご指摘頂いた通りパラメタの型が64bitプラットホームを考慮していないのが原因だった。
その後、シグネチャを以下のように修正して、Windows8 64bitのVisual Studio上でも動作している。

修正前
    [DllImport("USER32.DLL", EntryPoint = "CreateWindowExW", SetLastError = true)]
    public static extern IntPtr CreateWindowEx(int dwExStyle, [MarshalAs(UnmanagedType.LPWStr)] string lpClassName,
                           [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, int dwStyle, int x, int y,
                           int nWidth, int nHeight, uint hWndParent, int hMenu, int hInstance,
                           int lpParam);

修正後
    [DllImport("USER32.DLL", EntryPoint = "CreateWindowExW", SetLastError = true)]
    public static extern IntPtr CreateWindowEx(int dwExStyle, [MarshalAs(UnmanagedType.LPWStr)] string lpClassName,
                           [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, int dwStyle, int x, int y,
                           int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance,
                           IntPtr lpParam);

それにしても、Visual Studioではなく普通に実行する分においては修正前のシグネチャでも動くのとどうしてだろう。
上手くマーシャリングしてくれるのな。

それにしても情けないのは今回の問題、過去に自分で「32bit/64bitのどちらでも動作するように、パラメタの型には注意しなくてはならならい」という啓蒙のためのエントリを書いていることだ

2005-11-02 [.NET]64ビット時代 編集

このように書いておくと、64ビットプラットホームになっても正しく動作するのだそうだ。引数が本当にintを必要とするか、IntPtr(UIntPtr)を必要とするかは一見では解らないので、まずはAPIリファレンスを引く必要がありそうだな。

7年前ではあるが、自分の忘れっぷりがまったくもって情けない。