‘C#’ カテゴリーのアーカイブ

Encoding.GetString(Byte[])はSilverlightで使えない

今日のびっくり。

‘System.Text.Encoding.GetString(byte[])’ はアクセスできない保護レベルになっています。

Encoding.UTF8.GetString(data);

こんなコードを書くとビルド時エラーになります。

なぜか、Encoding.GetString(Byte[], Int32, Int32)は使えます。

Encoding.UTF8.GetString(data, 0, data.Length);

こうしておけばOK。何だろう、この仕様。


今日の微妙エラー

コントロールはひとつの case ラベル (‘default:’) から別のラベルへ流れ落ちることはできません。

switch (message)
{
    case "hoge":
        funcA();
        break;
    default:
        funcB();
        break; // これがないとエラー
}

他の言語のクセでbreak書かなかったら起きた。

「フォールスルー」ってことなんですね。

ちなみにcaseのあとのブロックにコードを書かなければフォールスルーはできるという話。


Windowsフォームへの、やたら簡単なSilverlightホスティング方法

  1. VisualStudioでフォームをひとつ作ります。
  2. WebBrowserコントロールを配置します。
  3. フォームのLoadイベントに以下を記述。
        private void Form1_Load(object sender, EventArgs e)
        {
            string html = @"<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Transitional//EN"" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"">
<!-- saved from url=(0014)about:internet -->

<html xmlns=""http://www.w3.org/1999/xhtml"" >
 <head>
  <title>Test</title>

  <script type=""text/xaml"" id=""testXaml"">
<?xml version=""1.0""?>
<Canvas xmlns=""http://schemas.microsoft.com/client/2007"">
 <Ellipse Fill=""Pink"" Width=""200"" Height=""100"" Canvas.Left=""40"" Canvas.Top=""30"" />
 <Rectangle Fill=""Orange"" Width=""100"" Height=""160"" Canvas.Left=""150"" Canvas.Top=""50"" />
 <TextBlock Text=""This is text on Silverlight"" Canvas.Left=""10"" Canvas.Top=""150"">
  <TextBlock.RenderTransform>
   <RotateTransform Angle=""-30"" />
  </TextBlock.RenderTransform>
 </TextBlock>
</Canvas>
  </script>
 </head>

 <body>
   <object type=""application/x-silverlight-2"" width=""800"" height=""600"">
   <param name=""source"" value=""#testXaml""/>
  </object>
 </body>
</html>";
            if (webBrowser1.Document == null)
            {
                webBrowser1.Navigate("about:blank");
            }
            webBrowser1.Document.OpenNew(true);
            webBrowser1.Document.Write(html);
        }

結果

20100127103949

XAMLソースはこちらから拝借。

【コラム】The Silverlight Times (16) 覚えておきたい「インラインXAML」 | ネット | マイコミジャーナル

WebBrowserの使い方はこちらを参考にしました。

WebBrowserコントロールのコンテンツを文字列により設定するには?[2.0、C#、VB] - @IT


実はFiddlerがすごすぎたので、機能まとめ紹介

今までFiddlerをただのセッションの中身を確認できるLocal Proxyとしてしか見ていなかったのですが
改めて良く調べると色々できることが多すぎると判明。感動したので便利な機能をまとめてみました。

先に簡単に説明しておくと、FiddlerはMicrosoftが無料で配布しているWeb Debugging Proxyです。

Windows環境にインストールして、ブラウザとサーバの間の通信を読んだり操作したりできます。

配布サイトはこちら。

Fiddler Web Debugger – A free web debugging tool

動作環境は「Windows 2000 / XP / 2003 / Vista with Microsoft .NET Framework v2.0 or later」

今回使ったバージョンは、2009年9月10日時点で最新の安定版、2.2.4.6。

とりあえず簡単に目次。

  • セッションのリクエスト一覧を確認する
  • やり取りしているヘッダ情報を見る
  • リクエスト・レスポンスの実際のデータの中身を確認する
  • セッション一覧表示をフィルタリングして、特定のContent-Typeだけ表示するようにする
  • リクエストを自分で作る
  • AutoResponderで特定のURLに対して、ローカルのファイルを割り当てる
  • Fiddler起動時にProxyを自動的に切り替える
  • Webページに対する一連の自動テストを実行する
  • サンドボックス
  • JScript.NETで拡張できる
  • C#やVB.NETで拡張できる
  • UIに好きな項目を追加できる
  • ブレークポイントの設定

マークアップエンジニアにおすすめしたいのは

  • セッション一覧表示をフィルタリングして、特定のContent-Typeだけ表示するようにする
  • AutoResponderで特定のURLに対して、ローカルのファイルを割り当てる

あたり。

プログラマには全部おすすめ。

詳細は「続き」からどうぞ。

(さらに…)


NSISから使える、起動中のプロセスを削除するプラグイン

常駐アプリケーションを作ったはいいが、アンインストールしようとしたときにプロセスが起動していると、削除できなくて困る。

というわけで、プロセスを殺す目的のプラグインDLLを作成。コンパイル済みファイルはエントリの最後に。

taskkillコマンド使えという話ですが、Windows XP Home Editionにはtaskkillコマンドが含まれていないので。

今回もさっくりC#で。C#.NETで作ってしまうと、そのままではNSISで使えないのだけど、.NETで作ったDLLをNSISのプラグインとして使う方法は以下のエントリを参照。

NSISで、C#で書かれた自作のDLLを使う : blog.loadlimit – digital matter –

ソースは以下。C#でクラスライブラリとしてプロジェクトを作成。プロセス名を指定すると、マッチしたプロセスを全部終了してくれます。

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace TaskKill
{
    public class TaskKill
    {
        [DllImport("Psapi.dll", SetLastError = true)]
        static extern bool EnumProcesses(
           [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] [In][Out] UInt32[] processIds,
             UInt32 arraySizeBytes,
             [MarshalAs(UnmanagedType.U4)] out UInt32 bytesCopied
          );

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
           uint dwProcessId);

        [Flags]
        public enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VMOperation = 0x00000008,
            VMRead = 0x00000010,
            VMWrite = 0x00000020,
            DupHandle = 0x00000040,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            Synchronize = 0x00100000
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CloseHandle(IntPtr hObject);

        [StructLayout(LayoutKind.Sequential)]
        public struct StringBuffer
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string text;
        }

        [DllImport("psapi.dll")]
        private static extern bool EnumProcessModules(IntPtr hProcess, out IntPtr lphModule, int cb, out int lpcbNeeded);
        [DllImport("psapi.dll")]
        private static extern int GetModuleBaseName(IntPtr hProcess, IntPtr hModule, out StringBuffer lpBaseName, int nSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool TerminateProcess(IntPtr hProcess, int uExitCode);

        public static void KillByName(string pname)
        {

            UInt32 arraySize = 1024;
            UInt32 arrayBytesSize = arraySize * sizeof(UInt32);
            UInt32[] processIds = new UInt32[arraySize];
            UInt32 bytesCopied;

            bool success = EnumProcesses(processIds, arrayBytesSize, out bytesCopied);

            if (!success)
            {
                return;
            }
            if (0 == bytesCopied)
            {
                return;
            }

            UInt32 numIdsCopied = bytesCopied >> 2;

            if (0 != (bytesCopied & 3))
            {
                UInt32 partialDwordBytes = bytesCopied & 3;

                return;
            }

            for (UInt32 index = 0; index < numIdsCopied; index++)
            {
                IntPtr hProcess = OpenProcess(
                    ProcessAccessFlags.QueryInformation | ProcessAccessFlags.VMRead | ProcessAccessFlags.Terminate,
                    false,processIds[index]);
                if (hProcess.ToInt32() != 0)
                {
                    IntPtr hMod = IntPtr.Zero;
                    int cbNeeded;
                    StringBuffer szProcessName;
                    szProcessName.text = "<unknown>";

                    if (EnumProcessModules(hProcess, out hMod, Marshal.SizeOf(hMod), out cbNeeded))
                    {
                        GetModuleBaseName(hProcess, hMod, out szProcessName, Marshal.SizeOf(szProcessName));
                    }

                    if (szProcessName.text == pname)
                    {
                        TerminateProcess(hProcess, -1);
                    }

                    CloseHandle(hProcess);
                }
            }

        }

    }
}

で、これをNSISの配布ファイルに含めておいて、

; アンインストーラ
Section "Uninstall"
  ; アンインストールで使用するプラグインの準備
  InitPluginsDir
  SetOutPath $PLUGINSDIR
  File "TaskKill.dll"

  ; hogehoge.exeが起動していたら、それを落とす
  CLR::Call /NOUNLOAD "TaskKill.dll" "TaskKill.TaskKill" "KillByName" 1 "hogehoge.exe"

  ; インストールしたファイル群削除
  Delete $INSTDIR\*.*

  CLR::Destroy
SectionEnd

という感じでnsiファイルを記述。

ファイルは下記リンクからダウンロードしてお使いください。

download TaskKill.dll


スクリーンキーボード

Vistaでソフトウェアキーボードというか、スクリーンキーボードを作ろうとしてハマったのでメモ。ただし、結局解決には至っていません。アプローチ変えてしまったので。

スクリーンキーボードを作るにあたって検索すると、imm32関連の関数を使ったサンプルが諸々出てくるのだけど、どうやらこの関数がWindows XPまでのもので、Vista以降はTSF(Text Services Framework)というものに置き換わっているらしい。

これに気づかず、

IntPtr hIMC = ImmGetContext(AHwnd);
IntPtr hKL = GetKeyboardLayout(0);
int dwSize = ImmGetConversionList(hKL, hIMC, AText, IntPtr.Zero, 0, GCL_CONVERSION);

とかやっていたのだけど、ImmGetConversionListの結果が必ず0になってしまう。

imm32 を使ったプログラムの互換性について

試してないけど、TSFのドキュメントは以下。

Text Services Framework Reference (Windows)

で、ここまでわかったところで、結局Linuxでやることになりました。


NSISで、C#で書かれた自作のDLLを使う

NSISでインストーラ&アンインストーラを作っていて、インストール時に一緒にインストールしたAdobe AIRアプリケーションを、アンインストール時に同時にアンインストールしたいという要件。

Adobe AIRアプリケーションのアンインストールは、手動ならWindowsのコントロールパネルからできるので、ここの情報をレジストリから引っ張り出して実行すればOK。この辺は良くある手段。

で、レジストリからアンインストールに必要なプログラムの実行パス情報を持ってこようとしたら、Adobe AIRでは、Uninstall以下のサブキーの名前が毎回変わるので、検索する必要があることが発覚。

NSISにはレジストリの特定のキーを読む機能はあっても、キーを検索する機能はないので、DLLで拡張を作成。さっくり適当にC#でレジストリを検索して文字列を返すDLLを作成してみた。

ちなみにアンインストール情報はHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall以下にあります。

ということで、必要な動作はUninstall以下のサブキーを全部開いてDisplayNameが目的の名前と一致するサブキーを探すこと。ソースは以下。

SearchUninstall.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace SearchUninstall
{
    public class SearchUninstall
    {
        public static string Find(string name)
        {
            string keyPath = "";

            // レジストリからの読み取り
            Microsoft.Win32.RegistryKey regkey =
                Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false);
            // キーが存在しないときは null が返される
            if (regkey == null) return null;

            // 文字列を読み込む
            // 読み込む値が存在しないときは null が返される
            string stringValue = (string)regkey.GetValue("string");

            // sub以下のすべてのキー名を取得
            string[] keyNames = regkey.GetSubKeyNames();
            foreach (string k in keyNames)
            {
                string tmp_keyPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + k;
                Microsoft.Win32.RegistryKey regkey2 =
                    Microsoft.Win32.Registry.LocalMachine.OpenSubKey(tmp_keyPath, false);
                try
                {
                    string displayName = (string)regkey2.GetValue("DisplayName");
                    if (displayName == name)
                    {
                        keyPath = tmp_keyPath;
                        break;
                    }
                }
                catch (Exception e)
                {
                    ;
                }
                finally
                {
                    regkey2.Close();
                }
            }

            //閉じる
            regkey.Close();

            return keyPath;
        }
    }
}

で、出来上がったDLLをNSISに読み込ませようとしても、どうにも動かない。

Dependency walkerで他のDLLと見比べてみると、C#で作られたDLLはエントリポイントが見えない。

そう言えばそんな話もあったような。

参考:WINAMP.COM | Forums – How can I use a C# DLL in NSIS

NSISからでは.NETのDLLが読めないらしい。ということで、見つけたのが以下。.NETのDLLを使えるようにするプラグイン。

Call .NET DLL methods plug-in – NSIS

CLR.zipをダウンロードしてきて、NSISをインストールしたフォルダのPluginsフォルダに入れておけばOK。

先に作ったSearchUninstall.dllはnsiファイルと同じ場所に入れておく。

で、nsiファイルは以下の感じ。Uninstall部分の必要部分を抜粋。

; アンインストーラ
Section "Uninstall"
  ; アンインストールで使用するプラグインの準備
  InitPluginsDir
  SetOutPath $PLUGINSDIR
  File "SearchUninstall.dll"

  ; AIRアプリケーションのアンインストーラを探す
  CLR::Call /NOUNLOAD "SearchUninstall.dll" "SearchUninstall.SearchUninstall" "Find" 1 "HogeHogeAir"
  pop $0
  CLR::Destroy

  ReadRegStr $4 HKLM $0 "UninstallString"

  ; AIRアプリケーションのアンインストーラをPassiveモード(自動処理)で実行する
  ExecWait "$4 /passive"

  ; 後片付け
  Delete "$PLUGINSDIR\SearchUninstall.dll"
SectionEnd

これで、アンインストール時にAdobe AIRアプリケーションのアンインストーラも勝手に起動して削除できる。

CLR超便利。プラグインを作るのが相当楽に。