カタバミさんのプログラミングノート

日曜プログラマーがプログラミング関係のメモを記録するブログです。

JavaScriptで文字列をUTF-8文字コードとして処理する方法

JavaScriptで文字列をUTF-8文字コードとして処理する方法のメモです。動作確認環境はWindows 10、Google Chrome 79.0.3945.130です。

文字列を16進数UTF-8文字コードに変換して半角スペースで区切る

const string1 = "あいうえお"
Array.from((new TextEncoder()).encode(string1), x => x.toString(16).padStart(2, '0')).join(' ')
// "e3 81 82 e3 81 84 e3 81 86 e3 81 88 e3 81 8a"

文字列を文字毎に10進数UTF-8文字コードに変換した二次元配列を作成する

const encoder= new TextEncoder()
const string1 = "あいうえお"
Array.from(string1, s => Array.from(encoder.encode(Array.from(s))))
// [227, 129, 130], [227, 129, 132], [227, 129, 134], [227, 129, 136], [227, 129, 138]

文字列を文字毎に16進数UTF-8文字コードに変換した二次元配列を作成する

const encoder= new TextEncoder()
const string1 = "あいうえお"
Array.from(string1, s => Array.from(encoder.encode(Array.from(s)), code => code.toString(16)))
// ["e3", "81", "82"], ["e3", "81", "84"], ["e3", "81", "86"], ["e3", "81", "88"], ["e3", "81", "8a"]

文字列を文字毎に16進数UTF-8文字コードに変換して半角スペースと改行で区切る

const encoder= new TextEncoder()
const string1 = "あいうえお"
Array.from(string1).map(s => Array.from(encoder.encode(Array.from(s))).map(code => code.toString(16)).padStart(2, "0").join(" ")).join("\r\n")
// "e3 81 82
// e3 81 84
// e3 81 86
// e3 81 88
// e3 81 8a"

備考

この投稿ではArray.from関数の第2引数mapFnを使用していますが、Array.from(...).map(mapFn)と記載しても同様の結果が得られます。

Uint8Arrayのmap関数の注意あるいはTextEncoder.encodeの戻り値の注意

Uint8Array.of(0, 1, 2).map(x => x) // Uint8Array(3) [0, 1, 2]
Uint8Array.of(0, 1, 2).map(x => 0.1) // Uint8Array(3) [0, 0, 0] !?
Uint8Array.of(0, 1, 2).map(x => "a") // Uint8Array(3) [0, 0, 0] !?

現在の仕様ではUint8Array.map関数はUint8Array型を返します。Uint8Array.mapの結果は強制的にUint8Arrayの要素に変換されるため、Array.map関数と同じ感覚で使用すると想定外の挙動が起きます。この挙動はArray.from関数でArray型にしてしまうことで回避できます。TextEncoder.encode関数の戻り値(Uint8Array型)に対してmap関数を適用したらハマったので共有します。

Array.from関数による対処例

Array.from(Uint8Array.of(0, 1, 2)).map(x => 0.1) // [0.1, 0.1, 0.1]
Array.from(Uint8Array.of(0, 1, 2)).map(x => "a") // ["a", "a", "a"]

TextEncoder.encodeにおける再現と対処

(new TextEncoder('utf-8')).encode("abc").map(ch => ch.toString(16))
// Uint8Array(3) [61, 62, 63] ※整数に変換されている。

Array.from((new TextEncoder('utf-8')).encode("abc")).map(ch => ch.toString(16))
// ["61", "62", "63"] ※文字列のまま

TextEncoder.encodeにおける再現と対処(padStart関数が無意味になる)

(new TextEncoder('utf-8')).encode("abc").map(ch => ch.toString(16).padStart(3, "0"))
// Uint8Array(3) [ 61, 62, 63 ]
Array.from((new TextEncoder('utf-8')).encode("abc")).map(ch => ch.toString(16).padStart(3, "0"))
// Array(3) [ "061", "062", "063" ]

参考

C#でWin32 API TaskDialog関数を呼び出すサンプルコード

C#でTaskDialogを表示するにはWin32 APIのTaskDialog関数(comctl32.dll)を呼び出すサンプルコードです。適当なC#Windows フォーム アプリケーション (.NET Framework) プロジェクトのForm1にボタン(Button1)を作成して、Clickイベントの名前を合わせて実行してください。

関数の呼び出し自体よりもMAKEINTRESOURCEマクロを使用したTD_WARNING_ICONの定義の方が難関だと思います。C/C++におけるintの(WORD)キャストによるビット操作はuncheckedとビット演算子またはキャストにより実施可能です。

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace TaskDialogSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private static class NativeMethods
        {
            [DllImport("comctl32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
            public static extern int TaskDialog(
                IntPtr hwndOwner,
                IntPtr hInstance,
                string pszWindowTitle,
                string pszMainInstruction,
                string pszContent,
                TaskDialogCommonButtonFlags dwCommonButtons,
                IntPtr pszIcon,
                out int pnButton);
        }

        // MAKEINTRESOURCEに対応した変換
        // 単純にnew IntPtr(-1)等とするとWORDへのキャストが実施されずエラーとなる。
        private static IntPtr TD_WARNING_ICON => new IntPtr(unchecked((ushort)-1));
        private static IntPtr TD_ERROR_ICON => new IntPtr(unchecked((ushort)-2));
        private static IntPtr TD_INFORMATION_ICON => new IntPtr(unchecked((ushort)-3));
        private static IntPtr TD_SHIELD_ICON => new IntPtr(unchecked((ushort)-4));

        /// <summary>
        /// TASKDIALOG_COMMON_BUTTON_FLAGS
        /// </summary>
        [Flags]
        public enum TaskDialogCommonButtonFlags
        {
            OKButton = 0x0001,
            YesButton = 0x0002,
            NoButton = 0x0004,
            CancelButton = 0x0008,
            RetryButton = 0x0010,
            CloseButton = 0x0020
        }

        private void Button1_Click(object sender, EventArgs e)
        {
            NativeMethods.TaskDialog(
                Handle,
                IntPtr.Zero,
                "TaskDialogSample",
                "Main Instruction",
                "Content",
                TaskDialogCommonButtonFlags.OKButton,
                TD_SHIELD_ICON,
                out var button);
        }
    }
}

「Windowsの標準コントロール」と「C#の標準コントロール」

本文

前回の記事で平然と(Windows 10の)「標準コントロール」「コモンコントロール」と「C#の標準コントロール」という言葉を使ってしまったのですが、冷静に振り返ればこれらの言葉遣いは正しかったのかなと。多分に想像ですが、以下がその回答に近いかなと思います。

Windows 10の標準コントロール、コモンコントロール

  1. Windows 10ではUser32.dllとComctl32.dllがコントロールを提供する。
  2. Comctl32.dllが提供するコントロールはコモンコントロール(参照:InitCommonControlsEx関数)。
  3. User32.dllが提供するコントロールは標準コントロール(参照:ICC_STANDARD_CLASSES定数)。
  4. RICHEDITは別枠?
  5. 標準コントロールは初期化不要。
  6. コモンコントロールはInitCommonControlsEx関数の呼び出しが必要。
  7. ユーザー定義のカスタムコントロールなども存在。

C#の標準コントロール

  1. mscorlib.dllのSystem.Windows.Forms名前空間で定義された.NET Framework依存のコントロール

参考

docs.microsoft.com docs.microsoft.com