Interactive Extensionsを自分で実装する – 2.Buffer

Ixを自分で実装する、2回目はBufferです。Bufferのシグネチャはこちら。

static IEnumerable<IList<T>> Buffer<T>(this IEnumerable<T> source, int count);
static IEnumerable<IList<T>> Buffer<T>(this IEnumerable<T> source, int count, int skip);

第1回のForEachは良かったかもしれませんが、テストコードだと動きがわかりにくいと思いますので使用例とConsole出力、それを実現するための実装という順で記載してみます。

使用例:

static void Test_Buffer()
{
    var source = Enumerable.Range(1, 10);
    var buffered = source.Buffer(3);

    foreach (var sequence in buffered)
    {
        foreach (var item in sequence)
        {
            System.Console.Write(item);
        }
        System.Console.WriteLine();
    }
}

buffer_test_1

与えられたシーケンスをBufferの第一引数コずつに区切り、区切った1つをIList<T>として、IList<T>のシーケンスなのでIEnumerable<IList<T>>として返しています。Bufferにはもう一つ、第二引数をとるオーバーロードがあります。こちらはこんな感じです。

static void Test_Buffer2()
{
    var source = Enumerable.Range(1, 10);
    var buffered = source.Buffer(3, 2);

    foreach (var sequence in buffered)
    {
        foreach (var item in sequence)
        {
            System.Console.Write(item);
        }
        System.Console.WriteLine();
    }
}

buffer_test_2

なるほど。第一引数のcountと第二引数のskipの値次第で同じ値が繰り返しでたり、飛ばされたりするんでね。skip=countの場合が、前者実装となっているようです。

 

Buffer…渡されたシーケンスを規定個数毎のIListにまとめたシーケンスを作成する。

実装(Let’s Implement It!)

namespace EmulateInteractiveExtensions
{
    public static class EmulateIxExtensions
    {
        public static IEnumerable<IList<T>> Buffer<T>(this IEnumerable<T> source, int count)
        {
            return source.Buffer(count, count);
        }
        public static IEnumerable<IList<T>> Buffer<T>(this IEnumerable<T> source, int count, int skip)
        {
            if (source == null){
                throw new ArgumentNullException();
            }
            if (count <= 0 || skip <= 0){
                throw new ArgumentOutOfRangeException();
            }

            int index = 0;
            Queue<List<T>> buffers = new Queue<List<T>>();
            foreach (var item in source)
            {
                if (index++ % skip == 0)
                {
                    buffers.Enqueue(new List<T>());
                }
                foreach (var buffer in buffers)
                {
                    buffer.Add(item);
                }
                if (buffers.Count != 0 && buffers.Peek().Count == count)
                {
                    yield return buffers.Dequeue();
                }
            }
            for (int i = 0; i < buffers.Count; i++)
            {
                yield return buffers.Dequeue();
            }
        }
    }
}

同じ値を何度もIListインターフェースで返すことがあるということから、なんらかIListのコレクションが必要だと考えました。IListが生成・出力される順序はFIFOなのでQueueを使っています。Listオブジェクトを無駄に生成したり削除したりしていない点は効率的かなと。

2014.4.1 引数チェックを追加.

広告

Interactive Extensionsを自分で実装する – 1.Foreach

Rxからの逆輸入版としてLinq to Objectに拡張サービスとして試験リリースされている Ix(Interactive Extensions) というものがあります。以前、Linq to Objectを再実装するという海外のブログを見たことがあります、それを真似て(クオリティは高くありませんが;)勉強も兼ねてこれを私なりに実装していきたいと思います!

InteractiveExtensionsを使うには、Nugetから

PM> install-package ix-main

でokです。オブジェクトブラウザでみるとわかりますが、InteractiveExtensionsのほとんどはEnumerableExという単一クラスの静的メソッドで構成されています。

system.interactive

 

 

第1回目はForEachです。

ForEach…渡されたシーケンスに対して引数で指定したアクションを適用する。

ForEachTest.cs

namespace EmulateInteractiveExtensions.Test
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using NUnit.Framework;
    using EmulateInteractiveExtensions;

    [TestFixture]
    public class ForEachTest
    {
        [Test]
        public void Test_ForEach_SequentialAction()
        {
            var sequence = Enumerable.Range(1, 5);
            List<int> actual = new List<int>();
            List<int> expected = new List<int>(){1,2,3,4,5};

            sequence.ForEach(a => actual.Add(a));
            Assert.AreEqual(expected, actual);
        }

        [Test]
        public void Test_ForEach_SequanceAction_WithIndex()
        {
            var sequence = Enumerable.Range(2, 3);
            var actual = new List<Tuple<int,int>>();
            var expected = new List<Tuple<int, int>>();
            expected.Add(new Tuple<int, int>(2, 0));
            expected.Add(new Tuple<int, int>(3, 1));
            expected.Add(new Tuple<int, int>(4, 2));

            sequence.ForEach((a,i) => actual.Add(new Tuple<int,int>(a,i)));
            Assert.AreEqual(expected, actual);
        }
    }
}

EmulateIxExtensions.cs

namespace EmulateInteractiveExtensions
{
    using System;
    using System.Collections.Generic;

    public static class EmulateIxExtensions
    {
        public static void ForEach<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
        {
            if(source == null || action == null){
                throw new ArgumentNullException();
            }
            foreach (var item in source){
                action(item);
            }
        }

        public static void ForEach<TSource>(this IEnumerable<TSource> source, Action<TSource, int> action){
            if(source == null || action == null){
                throw new ArgumentNullException();
            }
            int i = 0;
            foreach (var item in source){
                action(item, i++);
            }
        }
    }
}

第1回目なので、補足を。

  • yield return はC#2.0の仕様です、シーケンス(IEnumerable<T>)を返すことで受け取った側はこれを使ってforeachに回すことができます。
  • 第一引数につくthisはC#3.0の仕様です、拡張メソッドといいあたかもそのクラスのpublicメソッドであるかのように扱うことができます。ちなみに拡張メソッドを初めて見たとき、後から関数を追加できるなんてカプセル化を壊すのでは??なんて思いましたが拡張メソッドで呼び出せるのはpublicに限られているし、クラスのprivate, protectedなメンバーをいじれるわけではなく、カプセル化は壊しません。
  • 各所に出てくる<T>はジェネリックというC#2.0の仕様です。C++でいうところのテンプレートで、型を引数として渡すことができます。
  • あと、他に知っておく必要があることとしてラムダ式やデリゲートがありますがこれは以前のブログを参照ください。

ForEachは渡されたシーケンスの個々のアイテムに対して、Actionで指定したデリゲートを実行するものです。

2014/4/1 引数のnullチェックを追加

 

Cソースで守ってほしいこと。

C言語、CPU命令と対応した非常によくできた言語だと思います。C++, Java, C#と続くプログラミングの王道、”C系”のルーツ。

ただそのC言語、簡単なはずなのにちゃんと使われていない!プログラミングのマナーである、わかりやすい関数名・変数名や、SOLID原則や、getterの中では値のsetはしない(コマンド照会分離原則)…等のどの言語でも当てはまることは除いて、C固有(C++すら除外!)の内容についてC使いにどうしても守ってほしいことを書いてみたいと思います。

守ってほしいこと

  1. .cに対応した.hファイルは.cの先頭でincludeすること
  2. static関数のプロトタイプ宣言は必ず書くこと
  3. 変数の宣言時初期化を意識すること

コードを読むのは学習って言うけど、これらも守られていないコードからは何も学べる気がしない。

 

1..cに対応した.hファイルは.cの先頭でincludeすること

間違い例(waveprocess.c): 

#include "type.h"
#include "common.h"
#include "doubleCalc.h"
#include "customheap.h"
#include "filewrap.h"
#include "wavetypes.h"
#include "waveprocess.h"

// 以下の関数は外部公開externされている前提
void DoSomething(profile* waveData){
    ...
}

理由:

DoSomething()を呼び出したい人はwaveprocess.hをインクルードしますよね?(includeせずextern宣言で利用できるけどそんなことしたらカオスだよ) そうするとwaveprocess.hをインクルードする。けど上のコードだとそれだけで使える保証がない。少なくとも引数に使っているprofile型の型定義が参照できなければビルドできないけどそれがはどこで定義されているかわかりませんよね、呼び出し側はそれを探してその定義があるファイルもインクルードする必要がある、しかもそれはwaveprocess.hの前にインクルードしなきゃいけない。外部公開関数が必要とする型はどうせインクルードしないといけないのだから、そんなものはヘッダ側で用意しましょう。それを保証するために.cに対応する.hを.cの先頭でインクルードする、のです。先頭でincludeしたときに外部公開に必要な型定義がなければエラーとなるため、対応する.hを先頭でインクルードすることが必要なヘッダをインクルードした状態であると保証できるのです。

正しい例(waveprocess.c):

#include "waveprocess.h"
// #include "type.h"  -> 基本型の定義だからwaveprocess.hでインクルードしなきゃいけなかった!
#include "common.h"
#include "doubleCalc.h"
#include "customheap.h"
// #include "wavetypes.h" -> ここにprofile型が定義されていた!
#include "filewrap.h"

// 以下の関数は外部公開externされている前提
void DoSomething(profile* waveData){
    ...
}

(waveprocess.h)

#ifndef WAVE_PROCESS_HEADER
#define WAVE_PROCESS_HEADER
#include "type.h"
#include "wavetypes.h"

void DoSomething(profile* waveData);

#endif

コメントアウトは説明のために書いてるだけで本番コードではもちろん書きませんよ。こうすれば、DoSomething関数を呼び出す側はそれが定義されているwaveprocess.hをインクルードするだけでいいのです。絶対そのほうがいいでしょ?

 

2.static関数のプロトタイプ宣言は必ず書くこと

間違い例(Test.c):

static void SubFunc1(){
    ...
}
static void SubFunc2(){
   ...
}
void ExternFunc(){
    SubFunc1();
    SubFunc2();
}

理由:
第一に、上から下に読むという基本に反している。でも「慣れ」って言うかもしれないからこれにはあまり触れない。第二に、関数の変更時に面倒なことになる。例えば、SubFunc1でSubFunc2を使いたいとしたらどうする?上の例で関数呼び出ししたんじゃそんな関数はないってエラーになる。

正しい例(Test.c):

static void SubFunc1();
static void SubFunc2();

static void SubFunc1(){
    ...
}
static void SubFunc2(){
   ...
}
void ExternFunc(){
    SubFunc1();
    SubFunc2();
}

もう定義順なんて気にしない、させない。

 

3.変数の宣言時初期化を意識すること
C++のソースですら下のような例をみる。

間違い例(Test.c):

void TestFunc(waveprofile* profile, rawwave* wave){
    DWORD dwCount;
    BOOL bFlag;
    if( profile->failed){
        dwCount = 2;
    }else{
        dwCount = 4;
    }
    bFlag = IsPeakExist(wave, dwCount);
    ...
}

理由:

確かにCは変数宣言が先頭にないとダメだけど、初期化漏れ・状態不整合についての意識が欠けていないだろうか。状態不整合ってのは値が代入されるまでの間、存在するけど使えない状態があること。上のコードくらいならまだマシかもしれないけど、変数を何個も使い出したらこれは初期化されてるとかこれはまだとか頭にキャッシュしないといけない。状態不整合をカプセル化するためのオブジェクト指向言語であり、状態不整合が副作用を及ぼすから関数型言語があるんだと思っている、私は。

正しい例(Test.c):

void TestFunc(waveprofile* profile, rawwave* wave){
    DWORD dwCount = (profile->failed)? 2 : 4;
    BOOL bFlag = IsPeakExist(wave, dwCount);
    ...
}

条件? 真時の値 : 偽時の値っていう3項演算子を嫌う人もいるけど、不整合を生まない、宣言時に書ける(ifステートメントは書けない)ってことを考えてメリットないって考えるほうがどうかしてる。他にも安易にstatic変数使うなとか、言いたいことはいっぱいある。こんな内容が常識として扱われたらいいんだけど。・・・グチでした。

C#のファイルヘッダテンプレート

MSDN Archiveで、本日時点最もダウンロードされている Code Header Designer を使ってみました。

このツールができることは、CやC++でもよく見るファイルに著作権情報やファイル名・作者などをヘッダとして統一的なフォーマットで作成することです。DownloadページからSeup32.msiを落としてきてインストールします。インストールはNext連打でOkです。

instal

インストール後、[スタート] – [すべてのプログラム] – [CSharp Code Header Designer]をクリックすると以下のようなウィンドウが立ち上がります。

top

使い方にちょっと癖(バグ?)があるようですが、以下のようにします。

Step1.HeaderDesignerのプロジェクトを作成する

  1. [File] – [New]から新しいプロジェクトを適当なディレクトリに保存します。.csprojと同じディレクトリでいいでしょう。
  2. 下のような画面が出てくるので、ProjectName(ここではTEST)と対象のc#コードを含むプロジェクトorソリューションを指定します。プロジェクトを選択すると自動的にC#ソースが選択されます。3つほどあるオプションはご自由に設定してください。
  3. OKで閉じたら[File] – [Save] からプロジェクトを上書き保存しておきます。

file_select

Step2.Headerのテンプレートを構成する

  1. 簡単には[Templates]から[Standard]または[Style Cop]を選択し、ヘッダテンプレートのベースを選択します。StandardはC,C++風、StyleCopはXMLのC#風ベースです。
  2. Feedback列に「値をいれてください」と出てくるものはValue列に値を入れます。
  3. 再び[File] – [Save] でプロジェクトを保存します。

style

見てもらえばわかると思いますが、”[]”で囲まれた領域が置換される文字列です。自分で何か設定すると下のReplaceable Fieldに行が自動追加されます。”[AUTO: ..]”はファイル名や作成者などで、自動的に読み取って反映されます。Feedbackの列に「値を入れてください」といわれる限り正しいヘッダではありません。

Step3.テンプレートを適用する

  1. [Project] – [Apply Headers]を実行すると対象のC#ソースにStep2で設定したヘッダが適用されます。
  2. [Project] – [Apply Headers]が実行できない場合、[File] – [Open]からStep2で保存したプロジェクトを読み直してください。

ここらへんでたまに動かない場合があるように感じますが読みなおせば実行できるようです。まぁそもそもファイル選択ボタンが「toolStripButton1」なんてツールチップが出るくらいなので完成度は高いとは言えませんね。適用結果はこちら。

//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="Tocs Company">
//     Copyright © Tocs Company. All rights reserved.
// </copyright>
// <authorTocs</author>
// <email>test@mail.com</email>
// <date>24/03/2014</date>
// <summary>no summary</summary>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication
{
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

ありゃりゃ良く見ると<author>の開始タグがおかしなことになってますね、デフォルトのタグがすでにおかしいようです。使える場面はありそうですが、私自信著作権(Copyright)表記には賛成ですが、作成者(Author)や自明なファイル名などを載せることにあまり意味が感じられません…

Reactive Extensionsの入門の入門から、必殺技を繰り出すまで

ReactiveExtensionsの学習にはかずきさんのブログがよくまとまっていると感じました。ReactiveExtensions(以下Rx)は非同期処理でその真価を発すると推測していますが、イベント向けのFromEventというファクトリメソッドで遊んでみます。

まず、Observable.FromEventメソッドのシグネチャは次のようになっています。

 Observable.FromEvent

public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
	Func<Action<TEventArgs>, TDelegate> conversion,
	Action<TDelegate> addHandler,
	Action<TDelegate> removeHandler
)

「ややこしー、特に第一引数は一体何モノ?」と思ったのが私の第一印象でした。WPFでの実装例は下のような感じになります。

    public static class RxEventHelper
    {
        public static IObservable<MouseButtonEventArgs> MouseDownAsObservable(this MainWindow window)
        {
            return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>
                (
                    handler => (sender, e) => handler(e),
                    handler => window.MouseDown += handler,
                    handler => window.MouseDown -= handler
                );
        }
    }

第2,3引数はイベントへのハンドラの追加・削除なのでいいとして、第1引数ちゃんと理解できていますか?3つのラムダ式ではhandlerを引数にしていますが、第1引数のそれと第2,3引数のそれとでは型が違います。第1引数は =>演算子が続いているので切れ目がパッと理解できなかったのですが、切れ目はここです。

fromeventについて

つまり下のように書き換えられます。こうするとわかりやすいかも。

    public static class RxEventHelper
    {
        public static IObservable<MouseButtonEventArgs> MouseDownAsObservable(this MainWindow window)
        {
            return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>
                (
                    mouseAction => {
                        MouseButtonEventHandler handler = (sender, e) =>
                        {
                            mouseAction(e);
                        };
                        return handler;
                    },
                    handler => window.MouseDown += handler,
                    handler => window.MouseDown -= handler
                );
        }
    }

上のコードで第1引数内のhandlerと、第2,3引数内のhandlerの型は同じにしています。このhanlderデリゲート変数は省略でき、省略表記すると良く見るソースになるわけです。

さて、”シーケンス”を処理するイベントを構成して真価を発揮するRxですが、実務でイベントをシーケンス処理したい好例は思いつきませんでしたが、Rxでできることを初めて知ったとき、私の頭にひらめいた「アレ」を作ってみたいと思います。何をしたいかはコードをみればわかってきます。以下WPFの例ですが、WindowsFormsでもイベントの型が変わるだけでやることは何も変わりません。まず先ほど作っていたマウス関連イベントを拡張メソッドとして作成します。(※拡張メソッドである必要はありませんし、今回は有難みもほとんどありません;)

 RxEventHelper.cs

    using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Reactive.Linq;

    public static class RxEventHelper
    {
        public static IObservable<MouseButtonEventArgs> MouseDownAsObservable(this UIElement window)
        {
            return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>
                (
                    mouseAction => {
                        MouseButtonEventHandler handler = (sender, e) =>
                        {
                            mouseAction(e);
                        };
                        return handler;
                    },
                    handler => window.MouseDown += handler,
                    handler => window.MouseDown -= handler
                );
        }

        public static IObservable<MouseEventArgs> MouseMoveAsObservable(this UIElement window)
        {
            return Observable.FromEvent<MouseEventHandler, MouseEventArgs>
                (
                    mouseAction => (s, e) => mouseAction(e),
                    handler => window.MouseMove += handler,
                    handler => window.MouseMove -= handler
                );
        }

        public static IObservable<MouseButtonEventArgs> MouseUpAsObservable(this UIElement window)
        {
            return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>
                (
                    mouseAction => (s, e) => mouseAction(e),
                    handler => window.MouseUp += handler,
                    handler => window.MouseUp -= handler
                );
        }
    }

MouseDown, MouseUpで同じようにかけますが、先ほどの通り意識して見比べられるようにしておきました、対象クラスはUIElementにしていれば問題ないでしょう。WindowsFormsならFormかControlにしておけばよさそうですね。今回はWPFなのでイベント処理を行うのはMainWindowです、とはいえXAMLは編集不要です。デフォルトのGridが張り付けられたままでいいです。c#のソースは以下のようにしてみます。

MainWindow.xaml.cs

namespace RxTest
{
    using System;
    using System.Windows;
    using System.Reactive.Linq;

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private Point prev = new Point(0,0);
        private IObservable<Point> dragOperation;

        public MainWindow()
        {
            InitializeComponent();
            RegisterXXXCommand();
        }

        public void RegisterXXXCommand()
        {
            dragOperation = this
                .MouseMoveAsObservable()
                .SkipUntil(this.MouseDownAsObservable().Do(_ => this.CaptureMouse()))
                .TakeUntil(this.MouseUpAsObservable().Do(_ => this.ReleaseMouseCapture()))
                .Select(e => e.GetPosition(null));
                //.Where(p => { prev = p; return (prev.X < p.X && prev.Y > p.Y); });

            Subscrbe();
        }

        private void Subscrbe()
        {
            dragOperation.Subscribe(p => { }, () =>
            {
                Subscrbe();
                hadouken();
            });
        }

        private void hadouken(){
            // 体内のエネルギーを自らの両手の中に..
        }

    }
}

MouseDown~MouseUpはRxの例で見かけたことがあるかと思います。そこで使っているRepeatをここでは使っていません。なぜでしょうか? まずここでやりたいことはMouseDown~MouseMove~MouseUpの一連の操作が行われた場合に1度だけ処理をしたいのです。しかし、MouseMoveAsOvservableではマウス移動のたびにメッセージが飛んできます。途中フィルタリングはできても、完了まで待機というのは難しいものと思われます(今の私にはわかりませんでした)。

そのため一連の処理が完了したOnCompletedで”必殺技”を放つようにしました。必殺技とは言え何発も打てないとダメなので、dragOperationの最後にRepeat()を付けたいところですがRepeat()をするとOnCompletedは呼ばれなくなります。「Repeatを使わずに繰り返してSubscribeしたい」、じゃあどうしようということでOnCompletedで再びSubscribeしている訳です。コード上再起っぽく見えますが、再帰呼び出しにはなっていません。また、イレギュラーなことにOnCompletedで処理をすることだけが目的なので、普通最もよく使うはずのOnNextでは何もしたくありません。これは必須引数になっていますので、p=>{} として何もしないAction<Point>を定義しています。

コマンド解釈のところは本来はもっと頑張る必要がありそうです。とりあえず前後関係を把握して[下]・[右下]・[右]の順に位置が移動するということは、前後関係からいえばX,Yはこう移動するはずで…と想定できると思います。さらにリアルなことを言えばタイミング制約もあるでしょうが、ここまでで。。。

C#でマウスクリッカーをつくる

ネットの掲示板にあがっていた質問。   やりたいこととしてあがっていた仕様はこちら:

  • 連打回数指定
  • 連打間隔指定
  • 連打秒数指定
  • スタート&ストップのホットキーを設定

いきなりですが、連打回数x連打間隔 = 連打秒数だと予想されますので連打秒数は今回作成外として放置させて頂きます、今回はホットキーを使うということなのでWindowsFormsのプロジェクトで作成します。まず手間のかかるホットキー、こちらのサイトを参考にさせていただき以下のようなクラスを作りました。※クラス名は似ていますが、機能は異なりますのでご注意ください。

ModiferKey.cs

namespace ClickerByCSharp
{
    public enum ModiferKey : uint
    {
        None = 0,
        Alt = 1,
        Control = 2,
        Shift = 4,
    }
}

HotKey.cs

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

namespace ClickerByCSharp
{
    public class Hotkey
    {
        [DllImport("user32.dll")]
        public static extern int RegisterHotKey(IntPtr hWnd, int id, ModiferKey modifer, Keys vk);
        [DllImport("user32.dll")]
        public static extern int UnregisterHotKey(IntPtr hWnd, int id);

        private ModiferKey _modifer;
        private Keys _hotKey;
        private int _id;
        private Form _hostForm;
        public Hotkey(ModiferKey modifer, Keys hotkey)
        {
            _modifer = modifer;
            _hotKey = hotkey;
        }

        public void Register(Form hostForm)
        {
            for (int i = 0; i < 0xbfff; i++)
            {
                if (RegisterHotKey(hostForm.Handle, i, _modifer, _hotKey) != 0)
                {
                    _hostForm = hostForm;
                    _id = i;
                }
            }
        }

        public void Unregister(){
            UnregisterHotKey(_hostForm.Handle, _id);
        }
    }
}

ホットキーはWinAPIのRegisterHotKey, UnregisterHotKeyで登録・解除をします。登録したホットキーが押されると、WM_HOTKEYメッセージが登録したウィンドウに対して飛んできます。このときウィンドウは最小化していてもちゃんとメッセージが飛んでくるので、ウィンドウは最小化されてもタスクトレイに表示されていても問題はありません。ここでは2重登録や解除漏れなどのエラー処理はしていませんが、登録解除漏れはIDisposableインターフェースを実装するなどして対応しておいたほうがよいと感じます。

これを設定するための簡単なUIをユーザーコントロールとして作成します。

hotkeyselector

HotKeySelectControl.cs

using System.Windows.Forms;

namespace ClickerByCSharp
{
    public partial class HotkeySelectControl : UserControl
    {
        private Keys hotkey = Keys.L;

        public HotkeySelectControl()
        {
            InitializeComponent();
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (Keys.A <= keyData && keyData <= Keys.Z){
                hotkey = keyData;
            }
            textBoxHotkey.Text = hotkey.ToString();
            return base.ProcessCmdKey(ref msg, keyData);
        }

        public Hotkey GetHotKey()
        {
            ModiferKey modifer = ModiferKey.None;
            modifer = checkBoxCtrl.Checked ? (modifer | ModiferKey.Control) : modifer;
            modifer = checkBoxAlt.Checked ? (modifer | ModiferKey.Alt) : modifer;
            modifer = checkBoxShift.Checked ? (modifer | ModiferKey.Shift) : modifer;
            return new Hotkey(modifer, hotkey);
        }
    }
}

ユーザ設定を元に、先ほど作ったHotKeyクラスインスタンスを取得するためだけです。1文字しか設定できないので、TextBoxのMaxLengthは1にしています。このUserControlをはりつけ、ホットキーのメッセージを受け取るための親Formを作成します。親FormでWM_HOTKEYを待つのですが、その時にHotkeyのIDが必要になるので、登録・解除処理は親Formから呼び出すことにします。このFormが起動直後に実行されるMainFormの想定です、前述の通りいきなりタスクトレイに引っ込んでもいいのですがなるべく本質的なところわかりやすくするため、通常表示のままにしておきます。

mouseclickform

MouseClickForm.cs

using System;
using System.Windows.Forms;

namespace ClickerByCSharp
{
    public partial class MouseClickForm : Form
    {
        private int elapsedTime;
        private Hotkey _hotKey;

        public MouseClickForm()
        {
            InitializeComponent();
        }

        private void buttonStartWithDelay_Click(object sender, EventArgs e)
        {
            elapsedTime = 0;
            timerCountdown.Enabled = true;
        }

        private void timerCountdown_Tick(object sender, EventArgs e)
        {
            const int timeToStart = 5;
            elapsedTime++;
            labelCountDown.Text = String.Format("{0}s後に開始...", timeToStart - elapsedTime);
            if (elapsedTime == timeToStart){
                timerCountdown.Enabled = false;
                ExecuteMouseClick();
            }
        }

        private void buttonRegister_Click(object sender, EventArgs e)
        {
            _hotKey = hotkeySelecter.GetHotKey();
            _hotKey.Register(this);
        }

        private void buttonUnregister_Click(object sender, EventArgs e)
        {
            _hotKey.Unregister();
            _hotKey = null;
        }

        protected override void WndProc(ref Message m)
        {
            const int WM_HOTKEY = 0x0312;
            base.WndProc(ref m);
            if ((m.Msg == WM_HOTKEY) && (_hotKey.ID == (int)m.WParam))
            {
                ExecuteMouseClick();
            }
        }

        private void ExecuteMouseClick()
        {
            var clicker = new SequentialMouseClick(
                (int)numericUpDownSequantialHitCount.Value,
                (int)numericUpDownHitSpan.Value);
            clicker.Start();
        }

    }
}

ホットキーの登録がうまくいかなかった場合に備えて、テストボタンを作っています。テストボタンでは5sからカウントダウンし、0sになると対象のマウスクリック処理を発動させます。ここまででホットキーの登録とホットキーが押された場合の処理ができました。上のクラスで天下り的に出てきた SequentialMouseClickクラス、これが最後につくるクラスです。

 SequantialMouseClick.cs

using System.Threading;
using System.Runtime.InteropServices;

namespace ClickerByCSharp
{
    public class SequentialMouseClick
    {
        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;
        [DllImport("user32.dll")]
        public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        private int _count;
        private int _intervalInSecond;
        private Thread _clickThread;

        public SequentialMouseClick(int count, int intervalInSecond){
            _clickThread = new Thread(new ThreadStart(ClickThread));
            _count = count;
            _intervalInSecond = intervalInSecond;
        }

        public void Start(){
            _clickThread.Start();
        }

        private void ClickThread(){
            int count = 0;
            while (count < _count)
            {
                Thread.Sleep(_intervalInSecond * 1000);
                mouse_event(NativeCall.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
                mouse_event(NativeCall.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                count++;
            }
            Stop();
        }

        public void Stop()
        {
            _clickThread.Abort();
        }
    }
}

世間はRxやasync/awaitで盛り上がっていますが、今回に限っては昔からあるThreadでわかりやすくしてみました。起動されたら別スレッドでマウスクリックイベントを発行させます。マウスクリックはマウスのダウンとアップイベントの組み合わせで実現しています、ここもWindowsAPIを使っています。また、マウスクリックイベントは1s単位にしていますが、これは無駄にクリックしまくって制御不能になるのを避けているだけでちゃんとしたエラー処理ができるようになれば高○名人を超えるスーパークリッカーにすることももちろん可能です。

ActiveXコントロールをC#でつくる

タイトルに偽り有です。

ユーザ環境でプログラミングしてもらえるようなSDKを「何でリリースするか?」って結構難しい話だと思います。言語の問題以上に、Native, Managedどちらで公開するか? は大きな問題です。Managedしかないだろぅと思っている方はちょっと先行く方です、現実は違います。2014年現在サポート切れたVB6.0以前の環境も依然として現役なのです。作り手はManagedで作りたいけどVB6.0で使えないとなると裾野を狭めてしまうことになります。ユーザあってのソフトウェア、当然ないがしろになんてできません。

「COMがあるじゃん」その通りです。ただ誰しも組込む知識があるわけではありません。「ソフトウェアはただのツールで毎度毎度覚えてられないよ」、という方も大勢いらっしゃいます。そんな方に今でも使われている(よう)なのはActiveXコントロールです。VBを使う人ならGUI上でドラッグアンドドロップでほいっとできる手軽さが今でも受けています。長い前振りですが、そんなわけで今日はC#でActiveXコントロールを作ってみようかと…

①WindowsFormControlライブラリとしてプロジェクトを新規に作成します。
②プロジェクトのビルドの設定から “COM相互運用機能の登録”にチェックをつけます。(下図)

ビルド設定

③ユーザコントロールを作成し、公開属性をつけます。

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

namespace ControlLibraryTest
{
    [ClassInterface(ClassInterfaceType.AutoDispatch)]
    [Guid("052347A1-359C-4C31-A78D-ECC636994460")]
    [ProgId("ControlLibraryTest.TestControl")]
    public partial class TestControl: UserControl
    {
        public TestControl()
        {
            InitializeComponent();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            labelCurrentTime.Text = DateTime.Now.ToString();
        }
    }
}

ローカルで実行するだけなのでこれだけ。あ、管理者として実行してくださいね。あとはhtmlで、以下のような感じで。 ActiveX Control Padのダウンロードしておくと確認が簡単にできます。

ActiveXControlPad2

親要素の<OBJECT ID/>タグを作った後、左の列に出てくるボタンでGUI操作してやると<PARAM/>子要素が勝手にできます。このHTMLを表示するとこんな感じ。

html表示

できました。ただ、待ってください!これはActiveXですが、ユーザ環境でドラッグアンドドロップで使えるActiveXコントロールではありません。そう、タイトルに偽り有と書いたのはこれのことで、C#でActiveXのDLLは作成はできるし署名もすれば公開できるんだけど、ActiveXコントロール (ocx) はできないのです。

この点をちゃんと理解して環境選択する必要がありそうです。