FortranにC言語から固定配列を渡す

科学計算の場合、行列式や1次元配列、2次元配列が頻繁に出てくる…と思われる。
Fortran の場合、あまり可変配列を使わずに、あらかじめメモリ領域を確保する固定配列を使う…ってそうしていたのだが、最近はどうなのだろう。可変にするとメモリの確保に時間がかかってスピードに問題がでるので、固定長さの配列のほうが有利。

module structmodule
    implicit none
    ! struct define
    type F3D
        double precision :: x,y,z
    end type F3D
contains
    ! one array 
    integer function sum1( v, count )
        integer :: v(10)
        integer :: i, total
        integer :: count
        
        total = 0
        do i=1,count
            print *, 'in sum1: ', i, v(i) 
            total = total + v(i)
        end do
        sum1 = total
    end function sum1
    ! 二次元配列を渡す
    integer function sum2( v, cnti, cntj )
        integer :: v(3,4)
        integer :: cnti, cntj
        integer :: i,j
        integer :: total
        
        total = 0
        do i=1,cnti
            do j=1,cntj
                print *,i,j,v(i,j)
                total = total + v(i,j)
            end do
        end do
        sum2 = total
    end function sum2
end module structmodule

引き数で配列の数を渡しているが、内部で固定にしかならないので、この場合はあまり意味がない。
最大長をチェックしてオーバーフローにならないように気を付けるとか。

extern "C" {
	// 1次元配列
	int STRUCTMODULE_mp_SUM1( int ary[], int *count );
	// 2次元配列
	int STRUCTMODULE_mp_SUM2( int ary[][3], int *cnti, int *cntj );
}
	int v1[] = {1,2,3,4,5,6,7,8,9,10};
	int count = 10;
	int sum1 = STRUCTMODULE_mp_SUM1( v1, &count );
	cout << "ans:" << sum1 << endl;

	int v2[4][3] = {
		{1,2,3},
		{2,3,4},
		{3,4,5},
		{4,5,6},
	};
	int cnti = 3, cntj = 4;
	int sum2 = STRUCTMODULE_mp_SUM2( v2, &cnti, &cntj );
	cout << "ans:" << sum2 << endl;

注意しないといけないのは、Fortranは「1」始まりで、C++は「0」始まりということ。なので、Fortranで「3」番目にアクセスをすると、C++では添え字の「2」にアクセスする(0始まりなので、3番目の要素になる)。配列そのものを扱う場合には気にならないが、index を渡す場合には注意が必要。
Fortranのほうを0始まりにできるオプションもあるらしいのだが、元のソースに合わせるとすると標準的な1始まりの配列を扱うほうがよかろう。

あと、2次元配列の場合は、次元が逆のように見える。これは、Fortranのほうにあわせて作る。

カテゴリー: 開発, Fortran | FortranにC言語から固定配列を渡す はコメントを受け付けていません

Fortranの構造体(TYPE)をC++で扱う

Fortranへのデータの取り回しは、C言語と同じように構造体を使うとok。C++ならばクラスを使いたいところなのだが、Fotranで扱うことはできないので構造体(TYPE)で。

module structmodule
    implicit none
    ! struct define
    type F3D
        double precision :: x,y,z
    end type F3D
contains
    ! struct parameter
    double precision function flen( pt )
        type (F3D) pt
        flen = sqrt( pt%x**2 + pt%y**2 + pt%z**2 )
        return 
    end function
end module structmodule

Fortranの構造体は TYPE で定義して、メンバ変数には「%」を使って参照する。

構造体はC言語側でもう一度定義しないさないといけない。

// 構造体を呼び出す
extern "C" {
	struct F3D {
		double x,y,z ;
	};
	double STRUCTMODULE_mp_FLEN( struct F3D *pt );
}

型だけを合わせれば OK で、メンバ変数名は自由につけてよい…が、同じようにつけたほうが良かろう。
関数の引き数に使う場合は、integerの変数と同じようにポインタにする。

function の戻り値に構造体を使うことも可能だと思うのだが、メモリ関係がややこしくなりそうなので構造体のデータ受け渡しは、引数で行ったほうが無難。

カテゴリー: 開発, Fortran | Fortranの構造体(TYPE)をC++で扱う はコメントを受け付けていません

FortranのCOMMONブロックをC++から扱う

少しメモ書きとして垂れ流し状態にするので、ご容赦を。

Fortranの引数は、C言語のポインタに変換することを覚えておけば、引数で渡すときは特に問題なし。
ただし、いちいち大量の設定データなどを、CからFortranに渡すのは大変なので、初期値っぽく Fortran の common ブロックを使うのがよい。

! common block
module commodule
    implicit none
    integer posx, posy
    common /compos/ posx, posy
contains
    subroutine comsub1()
        print *, "comsub1: ", posx, posy
    end subroutine comsub1
end module commodule

のように、commodule というモジュールの中に、compos という名前の common ブロックを作っておく。compos は、commodule モジュール内で共有できるし、モジュールの外に出してしまえば所謂グローバル変数として扱うことができる。ただし、大きなプログラムになると変数名がバッティングするので、モジュール内に入れるかなんらの命名規則を作ったほうがよかろう。

extern "C" {
	// common でデータを共有する
	struct ccompos {
		int posx;
		int posy;
	};
	// Fortran側のデータを構造体で参照する
	// プレフィックスはつかない模様
	extern ccompos COMPOS;
	// intel fortran では、モジュール名+_mp_+関数名でマッピングする
	// モジュール名、関数名は大文字
	void COMMODULE_mp_COMSUB1( void );
}

C++から扱うときは、common ブロックのパラメータをそのまま構造体にする。この順番は fortran で定義した順番通りにする。名前のない common ブロックの場合はどうするのか不明。
データ領域は、fortran で作成したライブラリ内にあるので、C++からは extern で参照しておく。このときの名前が、fortran で定義した common ブロックの名称になる。
common ブロックへの読み書きは、fortran からも c++ からも同様に行える。

実は、先頭のアドレスだけわかれば後はポインタでアクセスが可能なので、構造体を定義しなくても名前からアクセスできるマクロを作れそうだが、あまりスマートにいきそうにないのでやめておく。

カテゴリー: 開発, C++, Fortran | FortranのCOMMONブロックをC++から扱う はコメントを受け付けていません

windows 環境で fortran と C++ を連携させる(intel fortranの場合)

お仕事用のメモです。

FORTRANとCが混在したプログラム – Nobuhito Mori
http://www.oceanwave.jp/index.php?FORTRAN%A4%C8C%A4%AC%BA%AE%BA%DF%A4%B7%A4%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%E0#ubf9712b
プログラミングの話:Fortran,C,C++ の連携
http://www.mlab.ice.uec.ac.jp/~ej-sib/prog/prog_mixed.html#fortran_from_other_C
Fortran プログラマーズガイド: 11 – C と Fortran のインタフェース
http://www.hiroshima-cu.ac.jp/japanese/IPC/hunet99/sun/WorkShop/ja/html_docs/fortran/prog_guide/11_cfort.doc.html
FORTRANとCが混在したプログラム – Nobuhito Mori
http://www.oceanwave.jp/index.php?FORTRAN%A4%C8C%A4%AC%BA%AE%BA%DF%A4%B7%A4%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%E0#v4d3436c
DLLのメイク
http://rakasaka.fc2web.com/propath/prodll_make.html#Command

と、Intel Fortran と Free Fortran 95 を使って windows 7 上で調べています。
要は、

  • Fortran で計算ロジックを lib/dll で作る
  • C/C++ からライブラリを呼び出す

ということをするので、その下調べ。
どうやら、巷で溢れてい情報は、linux 上の C/Fortran連携のようで、C言語の関数の最後にアンダーバーが付くというのは、windows 版だとちょっと違う…ってな具合です。Fortran側を「Func1」で定義しておいて、C言語側で「Func1_」で呼び出すというのは、gcc の仕様らしい。逆に言えば、Intel Fortran や Free Fortran
95 だと違っているんですよ、ってなことです。

■DLLの定義

subroutine DllSub1(x,y)

  ! この DLL のユーザーへのサブルーチン Dll1 の説明
  !
  !DEC$ ATTRIBUTES DLLEXPORT::DllSub1

  ! 変数
  integer, intent(in) :: x, y

  ! DllSub1 の本文

end subroutine DllSub1

integer function DllFunc1( x, y )

  ! この DLL のユーザーへのサブルーチン Dll1 の説明
  !
  !DEC$ ATTRIBUTES DLLEXPORT::DllFunc1

  ! 変数
  integer, intent(in) :: x, y

  ! DllFunc1 の本文
  DllFunc1 = x + y

end function DllFunc1

■Libraryの定義

! C++からの呼び出し
module callmodule
    implicit none
  contains
    ! 関数定義1
    integer function csub1( x, y )
        integer, intent(in):: x, y
        csub1 = x + y
        return
    end function csub1
end module callmodule

! 関数定義1
integer function csub2( x, y )
    integer, intent(in):: x, y
    csub2 = x + y
    return
end function csub2

■C++から呼出し

#include "stdafx.h"
#include <iostream>
using namespace std;

extern "C" {
	extern int CALLMODULE_mp_CSUB1( const int *, const int * );
	extern int CSUB2( const int *, const int * );
}

#define DllImport __declspec( dllimport )

extern "C" {
	DllImport void DLLSUB1( const int *, const int * );
	DllImport int  DLLFUNC1( const int *, const int * );
}

int _tmain(int argc, _TCHAR* argv[])
{
	cout << "c and fortran programing" << endl;

	int x = 10;
	int y = 10;
	int ans = CALLMODULE_mp_CSUB1(&x,&y);
	cout << "ans:" << ans  << endl;

	ans = CSUB2(&x,&y);
	cout << "ans:" << ans  << endl;

	DLLSUB1( &x, &y );
	ans = DLLFUNC1( &x, &y );
	cout << "ans:" << ans  << endl;

	return 0;
}

Intel Fortran の場合は上記の呼出ができます。gcc とは違って末尾に「_」が付きません。関数名称は、nm コマンドを使うと調べることができます。

>nm CallModule.obj

00000000 N .debug$S
00000000 N .debug$T
00000000 i .drectve
00000000 t .text
00000000 r .trace
00000001 a @feat.00
00000000 T _CALLMODULE.
00000006 T _CALLMODULE_mp_CSUB1

Fortran でライブラリを作って、C言語にリンクさせる場合には「CALLMODULE_mp_CSUB1」という定義か「CSUB2」という定義になります。「CALLMODULE」の部分は fortran の module 名ですね。関数名がぶつからないように module を使うと、それが名前に入ってくるという罠なんですが、まぁ、コンパイラ依存で書いて良いのならば、こんな風に直接名前を付けられます。module に入っていない場合は、実はC言語の定義「_CSUB2」のように先頭にアンダーバーが付いた名前になります。

DLLの関数として公開する場合は「!DEC$ ATTRIBUTES DLLEXPORT::DllSub1」のような属性をつけます。Fortranの場合は「!」以降がコメントになるので、コメントを使ったプラグラマです。これは独自文法になるので intel fortran のみかと。
C言語の呼び出しのほうは、通常のDLL呼出と同じで「__declspec( dllimport )」を使います。このあたりは定番です。Fortran の方からは、大文字で公開されるので fortran 内で「DllFunc1」のように小文字を使っていても、公開されている関数名は「DLLFUNC1」になります。

という訳で、このあたり初手でハマるので、ひとまず公開しておきます。

カテゴリー: 開発, Fortran | 7件のコメント

リモートコンピュータの画面をキャプチャする

acer w500 に win8 を試していて、何が困るかと言えばキーボードが無いことです。いや、USB があるのだから差し込めばいいのですが(iPadの場合は、Bluetoothキーボードか、ソフトウェアキーボードで我慢)、画面キャプチャが出来ないのが不便。キーボードを繋げば、Print Screen キーでキャプチャできるのはわかっているし、リモートデスクトップを使えばキャプチャができるのはわかっているのですが、画面そのままの状態が欲しい。iPad の場合は、電源+ホームボタンでOKなのですが、win8 の場合はどうなんでしょう?

試しに、winキー + 電源ボタンを押すと、ctrl+alt+del と同じ画面がでます。

■Mouse without Borders を使う

Mouse without Borders – k本的に無料ソフト・フリーソフト
http://www.gigafree.net/internet/remote/mousewithoutborders.html

つい先日知ったのですが、「Mouse without Borders」でリモートコンピュータの画面キャプチャができます。『タスクトレイ上のアイコンを右クリック → 「Get Screen Capture from」 から、他PC の
デスクトップ画像をキャプチャしたり、「Send Screen Capture to」 から、自PC のデスクトップ画像を
他のパソコンに送ることもできたりします』ってな具合で、うーむ、簡単にできますね。

実は、これを twitter で知った時に、画面キャプチャ用のツールを作っていたんですよね orz 実にタイムリー(?)というか、なんというか。

■自作してみる

マウス制御で print screen キーを押せばよいのですが、まぁ画面キャプチャだけの単体ツールを作ってみましょうということで。

こんな画面で、リモートコンピュータの全体の画面キャプチャとアクティブウィンドウの画面をキャプチャします。リモート先でも同じプログラムを動かしておいて、キャプチャする画面からホスト名を入れて「Scr」か「Act」ボタンを押すだけです。最初は win フォームのチープな画面だったのですが、Mouse without Borders に対抗して、Excel で画像を作ってみました。

こんな風に、キャプチャ先のコンピュータで同じプログラムを動かしておけばOKです。

■.NETリモートを使う

データのやり取りには、.NETリモートを使っています。tcp/ip で直接扱う方法もあるし、iis を使う方法もあるのですが、.NETリモートが一番手軽です。

.NET リモートでは同じクラスを共有して受け渡しをするので、まず MarshalByRefObject を継承するクラスを作ります。CapCom と CapComRemote という2つのクラスを作っているのは、同じプログラムでクライアント/サーバーを共有させているためです。別々のプログラムにする場合は、ひとつのクラスで十分です。

namespace RemoteScreenCap
{
	public class CapCom : MarshalByRefObject
	{
		/// <summary>
		/// 汎用コマンド送信
		/// </summary>
		/// <param name="cmd"></param>
		/// <returns></returns>
		public string GoSendCmd( string cmd )
		{
			if (SendCmd != null)
			{
				return SendCmd(cmd);
			}
			return "";
		}

		/// <summary>
		/// ホスト名を通知
		/// </summary>
		/// <param name="hostname"></param>
		public void GoSendHostname(string hostname)
		{
			if (SendHostname != null)
			{
				SendHostname(hostname);
			}
		}

		/// <summary>
		/// スクリーンキャプチャ実行
		/// </summary>
		/// <param name="cmd"></param>
		/// <returns></returns>
		public byte[] GoScreenCap(string cmd)
		{
			if (ScreenCap != null)
			{
				return ScreenCap(cmd);
			}
			return null;
		}

		public delegate string SendCmdHandler(string cmd);
		public event SendCmdHandler SendCmd;
		public delegate byte[] ScreenCapHandler(string cmd);
		public event ScreenCapHandler ScreenCap;
		public event Action<string> SendHostname;
	}

	/// <summary>
	/// リモート側の通信クラス
	/// </summary>
	public class CapComRemote : CapCom { }
}

キャプチャしたデータは byte[] 配列で受信します。Bitmap クラスは Marshaling されないので、一度 BMP 形式のデータに直すわけですね。
イベントは戻り値を持つことができるので、delegate と event を使って定義します。これが、戻り値を必要としない場合は、Action< > を使うとひとつの定義で済みます。こっちのほうが最近の流行りです。

■画面のキャプチャと送信

ひとまず、全文を晒しておきます。もう少し整理したら Git に上げておきます。

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

		// 自ホスト名
		private string HostName = "";
		// リモート側のホスト名
		private string RemoteName = "";
		// 要求マシンのホスト名(未使用)
		private string ClientName = "";
		// 待ちポート番号
		private int PORT = 19000;

		private void Form1_Load(object sender, EventArgs e)
		{
			// ホスト名を表示
			this.HostName = System.Net.Dns.GetHostName();
			this.Text = string.Format("{0} - {1}", HostName, "RemoteScreenCap");

			// コネクション待ち(リモート側の処理)
			// HTTPチャンネルを設定する
			HttpChannel ch = new HttpChannel(PORT);
			ChannelServices.RegisterChannel(ch, false);
			var chObj = new CapComRemote();
			RemotingServices.Marshal(chObj, "chObj");
			// ハンドルを登録する
			chObj.SendCmd += new CapCom.SendCmdHandler(chObj_SendCmd);
			chObj.SendHostname += new Action<string>(chObj_SendHostname);
			chObj.ScreenCap += new CapCom.ScreenCapHandler(chObj_ScreenCap);
			// 受付を開始する
			ch.StartListening(null);
		}

		#region クライアント側の処理

		private bool remoteConnected = false;

		/// <summary>
		/// リモートコンピュータに接続
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void buttonConnect_Click(object sender, EventArgs e)
		{
			remoteConnected = RemoteConnect();
		}

		private bool RemoteConnect()
		{
			this.RemoteName = textRemote.Text;
			// 送信用のチャンネルを設定する
			RemotingConfiguration.RegisterWellKnownClientType(
				typeof(CapCom),
				string.Format("http://{0}:{1}/chObj", this.RemoteName, this.PORT));

			return true;
		}

		/// <summary>
		/// コマンドを送信
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void button1_Click(object sender, EventArgs e)
		{
			if (remoteConnected == false)
			{
				// 接続
				if ((remoteConnected = RemoteConnect()) == false)
				{
					MessageBox.Show("リモートコンピュータ {0} に接続できません", this.RemoteName);
					return;
				}
			}
			// コマンドを送信する
			var obj = new CapCom();
			string res = obj.GoSendCmd(textCommand.Text);
			MessageBox.Show(res);
		}

		/// <summary>
		/// 画面キャプチャ
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void button2_Click(object sender, EventArgs e)
		{
			if (remoteConnected == false)
			{
				// 接続
				if ((remoteConnected = RemoteConnect()) == false)
				{
					MessageBox.Show("リモートコンピュータ {0} に接続できません", this.RemoteName);
					return;
				}
			}
			// 画面キャプチャコマンドを送信
			var obj = new CapCom();
			byte[] data = obj.GoScreenCap("all");

			if (data == null)
			{
				MessageBox.Show("エラー発生", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			MemoryStream mem = new MemoryStream(data);
			Bitmap bmp = new Bitmap(mem);
			// mem.Close();
			/*
			this.BackgroundImage = null;
			this.BackgroundImage = bmp;
			this.Width = bmp.Width;
			this.Height = bmp.Height;
			*/
			Clipboard.SetImage(bmp);

		}
		#endregion

		#region リモート側の処理

		/// <summary>
		/// クライアントコンピュータ名を保存
		/// </summary>
		/// <param name="name"></param>
		void chObj_SendHostname(string name)
		{
			this.ClientName = name;
		}

		byte[] chObj_ScreenCap(string cmd)
		{
			Bitmap bmp = null;
			if (cmd == "active")
			{
				// アクティブウィンドウをキャプチャ
				bmp = ActiveWindowCapture();
			}
			else
			{
				// 画面全体をキャプチャ
				//Bitmapの作成
				bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
					Screen.PrimaryScreen.Bounds.Height);
				//Graphicsの作成
				Graphics g = Graphics.FromImage(bmp);
				//画面全体をコピーする
				g.CopyFromScreen(new Point(0, 0), new Point(0, 0), bmp.Size);
				//解放
				g.Dispose();
			}

			MemoryStream mem = new MemoryStream();
			bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Bmp);
			mem.Close();
			byte[] data = mem.ToArray();
			return data;
		}

		[StructLayout(LayoutKind.Sequential)]
		private struct RECT
		{
			public int left;
			public int top;
			public int right;
			public int bottom;
		}
		[DllImport("user32.dll")]
		private static extern IntPtr GetWindowDC(IntPtr hwnd);
		[DllImport("user32.dll")]
		private static extern IntPtr GetForegroundWindow();
		[DllImport("user32.dll")]
		private static extern int GetWindowRect(IntPtr hwnd,
			ref  RECT lpRect);
		[DllImport("user32.dll")]
		private static extern IntPtr GetDC(IntPtr hwnd);
		[DllImport("gdi32.dll")]
		private static extern int BitBlt(IntPtr hDestDC,
			int x,
			int y,
			int nWidth,
			int nHeight,
			IntPtr hSrcDC,
			int xSrc,
			int ySrc,
			int dwRop);
		[DllImport("user32.dll")]
		private static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);
		private const int SRCCOPY = 13369376;

		/// <summary>
		/// アクティブウィンドウをキャプチャ
		/// </summary>
		/// <returns></returns>
		private Bitmap ActiveWindowCapture()
		{
			IntPtr hWnd = GetForegroundWindow();
			IntPtr hDC = GetWindowDC(hWnd);
			//ウィンドウの大きさを取得
			RECT rect = new RECT();
			GetWindowRect(hWnd, ref rect);
			Bitmap bmp = new Bitmap(rect.right - rect.left, rect.bottom - rect.top);
			Graphics g = Graphics.FromImage(bmp);
			IntPtr gDC = g.GetHdc();
			BitBlt(gDC, 0, 0, bmp.Width, bmp.Height, hDC, 0, 0, SRCCOPY);
			g.ReleaseHdc(gDC);
			g.Dispose();
			ReleaseDC(hWnd, hDC);
			return bmp;
		}

		/// <summary>
		/// コマンド受信
		/// </summary>
		/// <param name="cmd"></param>
		/// <returns></returns>
		string chObj_SendCmd(string cmd)
		{
			return this.HostName + ":" + DateTime.Now.ToString() + ":" + cmd;
		}
		#endregion

		private void remoteCapture(string cmd)
		{
			if (remoteConnected == false)
			{
				// 接続
				if ((remoteConnected = RemoteConnect()) == false)
				{
					MessageBox.Show("リモートコンピュータ {0} に接続できません", this.RemoteName);
					return;
				}
			}
			// 画面キャプチャコマンドを送信
			var obj = new CapCom();
			byte[] data = obj.GoScreenCap(cmd);

			if (data == null)
			{
				MessageBox.Show("エラー発生", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			MemoryStream mem = new MemoryStream(data);
			Bitmap bmp = new Bitmap(mem);
			Clipboard.SetImage(bmp);

			label2.Text = DateTime.Now.ToString() + " completed.";
		}

		private void label1_Click(object sender, EventArgs e)
		{
			// 全画面のキャプチャ
			remoteCapture("all");
		}

		private void label3_Click(object sender, EventArgs e)
		{
			// アクティブな画面のキャプチャ
			remoteCapture("active");
		}
	}
}

■受信側のコード

ちょっとだけ解説をすると、以下の部分がリモートコンピュータ側のコネクション待ちです。HttpChannel クラスでチャンネルを作って登録するあたりは定番なコードになります。RemotingServices.Marshal メソッドで登録するクラスが内部変数になっていますが、本来は外側に出すべきかなと。まぁ、参照している間は消えないのでこれでOKです。
…が、.NETリモートの場合、この通信用のオブジェクトが不定期に解放されるのを防ぐために、lifttimeを無限大にしておくことをお奨めします。この話は別な機会に。

private void Form1_Load(object sender, EventArgs e)
{
	// ホスト名を表示
	this.HostName = System.Net.Dns.GetHostName();
	this.Text = string.Format("{0} - {1}", HostName, "RemoteScreenCap");

	// コネクション待ち(リモート側の処理)
	// HTTPチャンネルを設定する
	HttpChannel ch = new HttpChannel(PORT);
	ChannelServices.RegisterChannel(ch, false);
	var chObj = new CapComRemote();
	RemotingServices.Marshal(chObj, "chObj");
	// ハンドルを登録する
	chObj.SendCmd += new CapCom.SendCmdHandler(chObj_SendCmd);
	chObj.SendHostname += new Action<string>(chObj_SendHostname);
	chObj.ScreenCap += new CapCom.ScreenCapHandler(chObj_ScreenCap);
	// 受付を開始する
	ch.StartListening(null);
}

■送信側のコード

.NETリモートのクライアント側からは、http プロトコルを使います。受信側で定義したアドレスに対してリクエストを投げるわけですね。
受信した byte 配列から bitmap クラスを再作成するには、MemoryStream クラスを使います。まあ、詳細はコードを見て頂くということで。

private bool RemoteConnect()
{
	this.RemoteName = textRemote.Text;
	// 送信用のチャンネルを設定する
	RemotingConfiguration.RegisterWellKnownClientType(
		typeof(CapCom),
		string.Format("http://{0}:{1}/chObj", this.RemoteName, this.PORT));

	return true;
}

/// <summary>
/// 画面キャプチャ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
	if (remoteConnected == false)
	{
		// 接続
		if ((remoteConnected = RemoteConnect()) == false)
		{
			MessageBox.Show("リモートコンピュータ {0} に接続できません", this.RemoteName);
			return;
		}
	}
	// 画面キャプチャコマンドを送信
	var obj = new CapCom();
	byte[] data = obj.GoScreenCap("all");

	if (data == null)
	{
		MessageBox.Show("エラー発生", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
		return;
	}
	MemoryStream mem = new MemoryStream(data);
	Bitmap bmp = new Bitmap(mem);
	// mem.Close();
	/*
	this.BackgroundImage = null;
	this.BackgroundImage = bmp;
	this.Width = bmp.Width;
	this.Height = bmp.Height;
	*/
	Clipboard.SetImage(bmp);

}

metro アプリケーションの場合もキャプチャができるので、こんな風に画面をスライドしている途中の画面もキャプチャができます。

あと、キーボードを押さなくていいので、普通のデスクトップのメニューが出ているところとかも撮りやすいはずです。

ひとまず、実行ファイルのダウンロードはこちらから。
http://moonmile.net/up/RemoteScreenCap-0.1.zip

カテゴリー: 開発, C# | リモートコンピュータの画面をキャプチャする はコメントを受け付けていません

2012年4月.NETラボ勉強会で発表します

宣伝がてら。

.NET Lab > Events > .NETラボ 勉強会 2012年04月
http://dotnetlab.net/dnn/Events/NET%E3%83%A9%E3%83%9C%E5%8B%89%E5%BC%B7%E4%BC%9A2012%E5%B9%B404%E6%9C%88/tabid/113/Default.aspx

.NETラボ勉強会で発表をします。今回の勉強会のテーマ…は、雑多なので特にない模様(苦笑)。まあ、ひとまず直前に Microsoft Windows Developer Days (WDD) ホーム なるイベントがあるので、その内容も交えてということで、Windows 8 in Acer の話をします。

ここのところ、acer w500 に windows 8 cosumer preview を入れて四苦八苦しているのは、

  • スレート PC に入れて、タッチパネル動作で Wndows 8 が使えるのか/使いやすいのか?
  • iPad に比べて Windows 8 の優位性は?逆に劣位なところは?
  • 本格的にスレートPCとして使えるのか?顧客に勧められるのか?

ってなところです。前身に(?)Windows Phone や Windows Moble があるわけですが、どうも wndows 8 metro application の位置は利用者(ユーザー)にとって微妙な位置になりそうですね、ってところと、開発者(developer)としてどんな風に提案をしていけばよいのか、というのを模索している途中です。その途中経過ということで iPad の操作も交えて発表をしていきます。なので、今回は、解説というよりも「途中経過」な話になるかと思います。

imageimage

おまけとして、先日、グリニッジ天文台に行ってきたので GMT+0 のお土産を買ってきました。当日来て下さった方には抽選(多分、じゃんけんとか)でお配りしますので、お楽しみにってことで。

場所は、

■会場:晴海区民館
都営大江戸線勝どき駅下車A2a出口 徒歩10分
http://www.tafuka.co.jp/tokyo_chuo/tsukishima_harumi.html

なところなので、いつもの品川とは違うので注意を。私も遅れないように行かないと。

カテゴリー: 勉強会 | 2012年4月.NETラボ勉強会で発表します はコメントを受け付けていません

IE10 から Office Web Apps を使ってみよう…としたが使えん

windows 8 consumer preview を acer w500 に入れて、MS-Office 2010 を入れて、と程よく環境が整ってきたので、既存のアプリケーションが使えるかどうかテストしていきます。

スレートPCにwindows 8を入て使う理由の大きなものとして、

  • iPadでExcel/Wordを扱うよりも、Windowsで扱うほうが正統であろう。
  • iPadアプリを作るよりも、Windowsアプリのほうが(私には)作り易い。

という点です。Androidが入っていないのは、手元にないというのが副要因、Linux+Javaの組み合わせであればLinuxを弄りたいのにってのが主要因だったりします。

それはさておき、ms-office を扱うのには、windows os が良かろうというのが結論です。

■Office Web Apps

Office Web Apps について | Microsoft Office 2010
http://www.microsoft.com/ja-jp/office/2010/webapps/default.aspx

「Office Web Apps」ってのは、「Office 365」の前身で Web 上で Word/Excel が扱えるブラウザアプリケーションです。Windows Live ID を登録すれば使えば無料で扱えるので、これだけ使うのであれば Office 365 を購入する必要はありません。まあ Office 365 の場合は、それ以外の機能がはいっているので、Word/Excel だけではないのですが。

■Excel の場合

結論から言うと、スレートPCで Office Web Apps は利用に耐えません。

こんな風に、一応閲覧はできます。上下スクロールもスムースに動くのですが、左右のスクロールがまずい。metro IE10 の場合、画面を右にスライドさせると、左からブラウザで表示した前の画面がでてきます。いわゆる「戻る」ボタンと同じ動作で、通常のブラウジングをしているときは便利なのですが、Ajax のようなアプリケーションの場合はこれが鬼門です。

こんな風に、指で左にスライドさせると前の画面(ドキュメントのリストの画面)がでてきます。これが意図した状態だったらいいのですが、Excel シートが横に長くて元の列(A列など)に戻そうとしてスライドさせていくと、勢い余って前の画面が出てきてしまうのです。前の画面を表示した時は「戻る」と同じ状態になるので、途中の閲覧や編集状態が消えてしまうんですよね。この「勢い余って」なのですが、閲覧をしてみるとかなり頻繁に発生します。なので「気を付けて」閲覧をしている分には問題ないのですが、結論としては「利用に耐えない」という結果になります。

ブラウザアプリケーションは、Ajax を使ってブラウザ上でドキュメントなどを編集することができる、ってのが売りなのです。Office Web Apps も非常に高機能な Ajax を使ってこれを実現しているのですが、

IE10 metro で動作させた場合には、ソフトキーボードのおかげで最初の10行しか見えません。しかも、セル上で編集をすることができなくて、必ず数式バーで編集しないといけないのです。これは、デスクトップPCとマウスで動かしたときはセル上で編集が可能なのですが、スレートPCだと数式バーのみになります。明らかに動きが変です。
例えば、100行目のセルを編集したいときは、一度100行目まで移動させてから上の数式バーにスクロールさせるという手間…と言いますか、実はこれすらできません。

何かのはずみで、下の行にどうやっても移動できないのです orz これはちょっとまずいかなぁと。

■Word の場合

Word の場合は、新規のDocumentを作ってタイピングしてみたのですが、なんか行がおかしくて入力すらまともにできません。

■Power Point の場合

唯一まともに動きそうなのが Power Point です。

一応、閲覧と編集ができることまでは確認できたのですが、画面のずれなどからちょっとお金をとるには厳しいという品質です。

■結論

という訳で、どこからどう突っ込んだら良いものやら…という具合で。おそらく、office web apps のほうを治さないといけないのと、consumer perview がどの段階なのかが問題なのですが、ひとまず microsoft connect あたりに一発投げておきますか。

■google docs では?

ちなみに google docs は metro IE10 ではまともに動きません。まぁ、iPad の safari 上でもまともに動かない(safari 自体が落ちてしまう)ので、なんとも言えませんが。

 

カテゴリー: 開発, windows 8 | 1件のコメント

Windows 8 用のミニソフトキーボードを作る(2)

win8 の metro アプリケーションを作る前段階として WPF アプリケーションを作ればよいので(Windows Phone アプリでもいいのだけど、win phone の方は私はノータッチなので)、画像のタッチイベントを実装してみます。

■WPF コントロールに画像を貼りつけ

WPF アプリケーションに画像を貼り付ける時には、Image コントロールを使うのですが、ちょっとコツがいります。

<UserControl x:Class="MiniSoftKey.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="216" d:DesignWidth="320"
             TouchDown="UserControl_TouchDown"
             TouchUp="UserControl_TouchUp"
             TouchMove="UserControl_TouchMove">
    <Grid>
        <Image Height="216" HorizontalAlignment="Left" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="320"
               Source="/MiniSoftKey;component/Images/iphone_alpha_normal.bmp" />
    </Grid>
</UserControl>

ここでは、リソースから「iphone_alpha_normal.bmp」というファイルを貼り付けているんですが、普通に「Stretch=”None”」(伸長しない)で張り付けると、

のように拡大されてしまうんですよね。ビットマップファイルのサイズは、320×216 にしてあるので、丁度なはなずなのですが…って、これは画面のDPI(96dpi)に起因するそうで「デバイス非依存ビットマップ」というものを使っているからです。デバイス非依存ビットマップで表示すると、ディスプレイの1ドットとビットマップの1ドットは違う、という…DTP的にはいいんでしょうが、なんともプログラマ泣かせといったところです。

じゃあ「デバイス依存ビットマップ」で表示すれば良いのでは?と思ってネットを探してみたのですが、なんだかうまい方法が見つかりませんでした。なので、仕方がないので、Image コントロールの width と height を画像の縦横に合わせて「Stretch=”Fill”」にしておきます。リソースから直接 width/height を取る方法もあるのですが、今回は画像の切り替えも行うので、こんな風にしておきます。

ちなみに、画面が96DPIの場合には、画面に表示されるサイズは4/3に拡大されます。なので、画面表示から3/4すれば元のビットマップのドット数になりますね。

画像ファイル(bmpファイル)自体は、プロパティのビルドアクションを「Resource」に変更しておきます。そうすると、先の xaml のように「Source=”/MiniSoftKey;component/Images/iphone_alpha_normal.bmp”」でアクセスが可能です(これは自動的に変換されます。「MiniSoftKey」はプロジェクト名です)。
# ただし、metro の場合は、逆に「コンテンツ」のままにしておきます。「ms-xapp:////Images/iphone_alpha_normal.bmp」な感じでアクセスが可能です。これもハマりました orz

■タッチイベントの取得

タッチイベント自体は、TouchDown/TouchUp/TouchMove で取ります。これはマウスで操作するときの MouseDown/MouseUp/MouseMove にあたります。ただし、TouchMove のイベントは「タッチし続けている状態」に限られます。当然ですが、指が浮いている間はイベントは取れません。このあたりは MouseMove と違うところですね。ちなみに、metro アプリの場合は、PointerDown などのイベントになります。

private void UserControl_TouchDown(object sender, TouchEventArgs e)
{
	GlobalData.MainForm.label1.Text = "touch down";
	TouchPoint pos = e.GetTouchPoint(this);
	GlobalData.OnMouseDown(new System.Drawing.Point((int)pos.Position.X, (int)pos.Position.Y));
}

private void UserControl_TouchUp(object sender, TouchEventArgs e)
{

	GlobalData.MainForm.label1.Text = "touch up";
	TouchPoint pos = e.GetTouchPoint(this);
	GlobalData.OnMouseUp(new System.Drawing.Point((int)pos.Position.X, (int)pos.Position.Y));
}

private void UserControl_TouchMove(object sender, TouchEventArgs e)
{
	TouchPoint pos = e.GetTouchPoint(this);
	GlobalData.OnMouseMove(new System.Drawing.Point((int)pos.Position.X, (int)pos.Position.Y));
}

ミニキーボードの場合は、acer 上のタッチイベントだけを対象にしていますが、本来は mouse と touch の両方のイベントを取って整合性を合わせないとだめなんですよね。指でタッチをしても、mouse イベントは発生するので、このあたりが面倒です。逆に、デスクトップPCでマウス操作した時は、touch イベントは発生しません。
metro アプリの場合は、mouse/touch/pen のイベントが pointer というひとつのイベントにまとめられているので、このあたりはプログラミングが楽になっています…が、イベントの順序までは未調査なので、両用のアプリを作るときにはどうなるのやら。

■画像の切り替え

動的に画像を切り替えるときは、BitmapImage オブジェクトを作成した後に、Image コントロールの Source プロパティに設定します。

public void Form1_MouseUp(object sender, MouseEventArgs e)
{
	//label1.Text = "mouse up";
	if (_pop != null)
	{
		_pop.Close();
		_pop = null;

		BPos pos = CalcBCode(e.X, e.Y);
		_send.Sendkey(pos.Code);
		if (pos.Code == "shift")
		{
			if (_send.shift_mode == true)
			{
				BitmapImage bi = new BitmapImage(new Uri(@"Images/iphone_alpha_shift.bmp", UriKind.Relative));
				userControl11.image1.Source = bi;
			}
			else
			{
				BitmapImage bi = new BitmapImage(new Uri(@"Images/iphone_alpha_normal.bmp", UriKind.Relative));
				userControl11.image1.Source = bi;
			}
		}
	}
}

ちょっと無駄なコードが入っていますが、こんな感じ。UriKind.Relative を付けて画像リソースを相対パスで拾ってきます。多分、内部的に「Source=”/プロジェクト名;component/」が付けられたと同じような動作になるのだと思います。

■画像のどのキーが押されているのか?

ここからは、ちょっとおまけ。プログラミングノウハウの話。
先のキーボードは単なる画像ファイルです。ボタン自体をちまちまと作っても良かったのですが、ひとまず某iPhoneの真似をしたかったので、そのままキャプチャしました。で、どのボタンが押されているのかを調べるのに、普通はキーのサイズをちまちまと測って、Rectange をチェックすれば…良いのですが、結構な手間になります。まあ、仕事ならそれでもいいけど。

という訳で、次のように少しトリッキーな方法を使います。

private void InitBPos()
{
	/// 標準キーボード
	_blist = new List<BPos>();
	_blist.Add(new BPos { Code = "Q", X = 15, Y = 30 });
	_blist.Add(new BPos { Code = "W", X = 47, Y = 30 });
	_blist.Add(new BPos { Code = "E", X = 82, Y = 30 });
	_blist.Add(new BPos { Code = "R", X = 111, Y = 30 });
	_blist.Add(new BPos { Code = "T", X = 145, Y = 30 });
	_blist.Add(new BPos { Code = "Y", X = 174, Y = 30 });
	_blist.Add(new BPos { Code = "U", X = 208, Y = 30 });
	_blist.Add(new BPos { Code = "I", X = 238, Y = 30 });
	_blist.Add(new BPos { Code = "O", X = 271, Y = 30 });
	_blist.Add(new BPos { Code = "P", X = 301, Y = 30 });
	_blist.Add(new BPos { Code = "A", X = 31, Y = 85 });
	_blist.Add(new BPos { Code = "S", X = 65, Y = 85 });
	_blist.Add(new BPos { Code = "D", X = 94, Y = 85 });
	_blist.Add(new BPos { Code = "F", X = 127, Y = 85 });
	_blist.Add(new BPos { Code = "G", X = 161, Y = 85 });
	_blist.Add(new BPos { Code = "H", X = 191, Y = 85 });
	_blist.Add(new BPos { Code = "J", X = 223, Y = 85 });
	_blist.Add(new BPos { Code = "K", X = 256, Y = 85 });
	_blist.Add(new BPos { Code = "L", X = 286, Y = 85 });
	_blist.Add(new BPos { Code = "Z", X = 61, Y = 137 });
	_blist.Add(new BPos { Code = "X", X = 94, Y = 137 });
	_blist.Add(new BPos { Code = "C", X = 125, Y = 137 });
	_blist.Add(new BPos { Code = "V", X = 161, Y = 137 });
	_blist.Add(new BPos { Code = "B", X = 191, Y = 137 });
	_blist.Add(new BPos { Code = "N", X = 223, Y = 137 });
	_blist.Add(new BPos { Code = "M", X = 256, Y = 137 });
	_blist.Add(new BPos { Code = "BS", X = 298, Y = 137 });
	_blist.Add(new BPos { Code = " ", X = 155, Y = 193 });
	_blist.Add(new BPos { Code = "n", X = 279, Y = 193 });

	_blist.Add(new BPos { Code = "shift", X = 20, Y = 137 });
}
private BPos CalcBCode(int x, int y)
{
	// 距離が近いものを取得
	int len2 = 10000;
	BPos pos = _blist[0];
	foreach (var p in _blist)
	{
		int l2 = (p.X - x) * (p.X - x) + (p.Y - y) * (p.Y - y);
		if (l2 < len2)
		{
			len2 = l2;
			pos = p;
		}
	}
	return pos;
}

各ボタンの中央の座標だけを取得したリストを作っておいて、タッチした座標と一番近いボタンを返す、という CalcBCode 関数ですね。これは、画像処理のクラスタリングの応用で「画面の何処をタッチしても、なんとなくボタンが押せる」という技です(笑)。全スキャンするので、数が多い場合にはスピードが問題になるのですが、これぐらいならば大丈夫。ただし、スペースバーのように横に長い場合は、中央の座標だけでは足りないので、適当に左右にも点を必要としますが(上記のソースにはそれが入っていません)。

public void Form1_MouseUp(object sender, MouseEventArgs e)
{
	//label1.Text = "mouse up";
	if (_pop != null)
	{
		_pop.Close();
		_pop = null;

		BPos pos = CalcBCode(e.X, e.Y);
		_send.Sendkey(pos.Code);

具体的にキーの送信自体は、keybd_event 関数を使います。UpとDownをきちんと対応させないと駄目なのですが、普通にIMEも使えるので、これがベストな方法でしょう。

class SendKeyCode
{
	[DllImport("user32.dll")]
	public static extern uint keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
	const byte VK_SHIFT = 0x10;
	const byte VK_CONTROL = 0x11;
	const byte VK_MENU = 0x12;
	const byte VK_LWIN = 0x5B;
	const byte VK_RWIN = 0x5C;
	const byte VK_APPS = 0x5D;

	public bool shift_mode = false;

	public void Sendkey(string code)
	{
		// return;

		if (code == "shift")
		{
			shift_mode = !shift_mode;
		}
		#region 特殊キーダウン処理
		/*keybd_event(VK_SHIFT, 0, 0, (UIntPtr)0);
            keybd_event(VK_CONTROL, 0, 0, (UIntPtr)0);
            keybd_event(VK_MENU, 0, 0, (UIntPtr)0);
            keybd_event(VK_LWIN, 0, 0, (UIntPtr)0);
             */
		if (shift_mode) keybd_event(VK_SHIFT, 0, 0, (UIntPtr)0);
		#endregion

		byte cd ;
		switch (code)
		{
			case "n": cd = 13; break;
			case "BS": cd = 8; break;
			default:
				cd = (byte)char.ConvertToUtf32(code, 0);
				break;
		}

		keybd_event(cd, 0, 0, (UIntPtr)0);
		keybd_event(cd, 0, 2/*KEYEVENTF_KEYUP*/, (UIntPtr)0);

		#region 特殊キーアップ処理
		/*keybd_event(VK_SHIFT, 0, 2, (UIntPtr)0);
            keybd_event(VK_CONTROL, 0, 2, (UIntPtr)0);
            keybd_event(VK_MENU, 0, 2, (UIntPtr)0);
            keybd_event(VK_LWIN, 0, 2, (UIntPtr)0);
             */
		if (shift_mode) keybd_event(VK_SHIFT, 0, 2, (UIntPtr)0);
		#endregion
	}

}

とこんな感じで作っていきます。

アルファベットキーボードで打っていたのですが、

  • アルファベットと数字の切り替えが面倒くさい。
  • カーソル移動やIME変換の場合には、カーソルキーがあったほうが良い

ので、単純に某iPhoneキーボードを流用するだけでは操作性に難点が出ます。acer の画面自体、iPhone よりも広いので、もっと大きくキーボードの領域が取れます。
ひとまず、某iPhoneキーボードの移植が終わったら、独自のキーボード(101か106キーボード)のデザインを考えてみましょうか。

~~
追記 2013/07/05

コメントで要望があってので、サンプルソースをアップしておきます。
http://sdrv.ms/17On4Fc
からダウンロードできます。
(中にあるキーボードのキャプチャは、まあ、試験的な画面キャプチャということで)

カテゴリー: 開発, C# | 6件のコメント

Windows 8 用のミニソフトキーボードを作る(1)

ここのところ、acer w500 での windows 8 環境を整えるのに四苦八苦していましたが、なんとなく開発環境が整ったのでお試しコーディングを開始します。

開発環境としては、

  • windows 8 customer preview 版を acer w500 にインストール
  • windows 8 customer preivew + visual studio 11 beta を vaio type s にインストール
  • windows 7 上で visual studio 2010 で開発

という具合です。acer と vaio はデスクトップPCから、リモートデスクトップで接続して操作します。スレートPC(タブレットPC)を使うのは、実際に動かして試してみるためですね。visual studio 11 beta には metro アプリを動かすための「シミュレーター」の機能があるのですが、vaio type s が多少非力(cpu 1.6GHz)なのとタッチ操作自体は実機のほうがやりやすい(ユーザーインターフェースを考察しやすい)ので、この構成になっています。

win8cp + vs11beta の組み合わせは、vmware でも可能なのですが、win8cp のパフォーマンスチューニングがいまひとつなのか、実用に耐えるスピードで vs11beta を動かせませんでした
なので、多少遅いですが、win8+vs11 の組み合わせは仮想環境ではなくて実際のマシンに入れるのが得策です(イライラして、創造力が失われますからね)。

さて、普通ならば metro アプリをと思うところなのですが、ひとまず win8 のデスクトップアプリを作ってみます。つーか、acer で win8 を使うと、ソフトキーボードの大きさに辟易するのでこれをなんとかしたい、というのが今回の主旨。実は、iPad も同じ問題を抱えていて、メモを取るだけでも画面の半分以上を占有してしまうという状態なのです。

iPad の場合

windows 8 の場合

まあ、タブレットPCで両手打ちをするのであれば、これが選択肢として上がるのは仕方がないのですが、折角の貴重な画面の半分をキーボードに占有されるのは「閲覧性が悪い」というわけで、コンセプトデザイン(苦笑)的に作ってみました。

何処かで見たようが画面なのは、さておき…某iPhoneでこのサイズで打っている訳だから、タブレットでも打てない訳はないよね、という具合です。

■製作開始の前に

で、単純にマウス/指でぽちぽち打つ分には、mouse click か mouse down/up を取ればよかろうと思っていたのですが、意外とハマりました。

  • ソフトキーボードの画面を前面に出てこないようにする。
  • マウスと指のタッチイベントが、win form, wpf, metro で異なる。

ところですね。後、諸々出て来そうなのですが、まだ製作途中なので。

■ソフトキーボードの画面を前面に出す

C#で2ch専用ブラウザをつくろう スクリーンキーボードの作成
http://cs2ch.blog123.fc2.com/blog-entry-85.html

を参考にして、ウィンドウスタイルに WS_EX_NOACTIVATE を設定します。設定の仕方は、Form クラスの CreateParams プロパティをオーバーライドという方法。これが一番正当なやりかたです。

private const int WS_EX_NOACTIVATE = 0x8000000;
protected override CreateParams CreateParams
{
	get
	{
		CreateParams p = base.CreateParams;
		if (!base.DesignMode)
		{
			p.ExStyle = p.ExStyle | (WS_EX_NOACTIVATE);
		}
		return p;
	}
}

ソフトキーボード自体にボタンを付ける場合には、ボタンコントロールがアクティブにならない様にします。今回は、iPhone の画面キャプチャをそのまま使うために、ボタンスタイルの変更はしなかったのですが、こんな風に ButtonEx クラスを作って貼りつけるそうです。

class ButtonEx : Button
{
    public ButtonEx()
    {
        //アクティブにしたくなウインドウ(Form)に配置する
        //コントロールは下記をつけないと駄目
        base.SetStyle(ControlStyles.Selectable, false);
    }
}

これで、メモ帳(win8の場合は出すのに苦労しますが)などにキーを送ることができます。キーの送信自体は、keybd_event 関数を使えば ok です。

■タッチイベントを拾う

ウィンドウをアクティブにしないほうは簡単にできたのですが、実はタッチイベントのほうが実は大変でした。

こんな風に、iPhone でキーを操作した時みたいに、ボタンの上にガイドを出したかったのです。iPad/win8 のソフトキーボードが大きいのは、両手で打てるように作っているのもそうですが、指でキートップの刻印が隠れてしまうのが主原因と思われます。ハード的にキーボードが別になっている場合は、ブラインドタッチが基本だったり、キートップを見ながらぽちぽち打つサイズになっているのでそれなりに大きくなります。
キー付きの携帯電話の場合、キーサイズが小さくなりますが、何処を押しているか(何処を押せばよいのか)は、ボタンの凹凸で分かります。しかし、iPhoneのようなスマートフォンの画面を直接操作する場合は、通常のボタンのようなハード的なフィードバックがないのでキー自体が探しづらいのですよね。
なので、apple はキーの上にガイドを出すように決めたのです(これが特許になっているかどうかは不明です)。ワタクシ的に云えば、iPad にも同じようなサイズのソフトキーを付けて欲しかった(付けてほしい)ですね。

このガイドを出すのに、普通のデスクトップアプリならば、

  • マウスがポイントされたとき(mouse overイベント)
  • マウスを押した時(mouse downイベント)

のどちらかで表示させます。しかし、タブレットの場合は、マウスとは違って mouse over というイベントは発生しません。当然、指が空中に浮いている間はタブレットはそれを判別できないので、最初のイベント mouse down になります…というハズなのですが、実は違います。

windows にはマウスの右クリックというイベントがあるので、これをエミュレートするために、タブレットPCの場合は「指を長押ししたときに右クリックと見なす」という処理が os に入っています。なので、指で画面を押した瞬間には、mouse down イベントが発生しないのですよ。これがハマりどころです。
「マウス」と「指」とはかなり操作が違うことを覚えてておかなければなりません。

windows xp のTable PCでは、Microsoft.Ink というクラスライブラリがあって、これを使えるのですが、windows 8 にはありません(多分、windows 7 にもありません)。

Microsoft.Ink 名前空間
http://msdn.microsoft.com/ja-jp/library/microsoft.ink(v=vs.90).aspx

じゃあ、windows 8 のデスクトップアプリ(非metroアプリ)ではどうするかというと、WPFアアプリ/コントロールを使います。

UIElement.TouchDown イベント (System.Windows)
http://msdn.microsoft.com/ja-jp/library/system.windows.uielement.touchdown.aspx

XAML のコントロール(UIElement)には、TouchDownというタブレット用のイベントが用意されています。試すと分かるのですが、mouse down イベントは違ったタイミングで呼び出され、きちんと「指で画面を押した瞬間」にイベントが発生します。
ちなみに、スタイラスを使ったときの StylusDown イベントもあるのですが、指との違いはなんでしょうかねぇ?スタイラス自体に何かの機能があるときは、こっちのイベントが呼び出されるのかもしれません。

という訳で、タッチダウンのイベントは、以下のように取ります。

private void UserControl_TouchDown(object sender, TouchEventArgs e)
{
	GlobalData.MainForm.label1.Text = "touch down";
	TouchPoint pos = e.GetTouchPoint(this);
	GlobalData.OnMouseDown(new System.Drawing.Point((int)pos.Position.X, (int)pos.Position.Y));
}

private void UserControl_TouchUp(object sender, TouchEventArgs e)
{

	GlobalData.MainForm.label1.Text = "touch up";
	TouchPoint pos = e.GetTouchPoint(this);
	GlobalData.OnMouseUp(new System.Drawing.Point((int)pos.Position.X, (int)pos.Position.Y));
}

private void UserControl_TouchMove(object sender, TouchEventArgs e)
{
	TouchPoint pos = e.GetTouchPoint(this);
	GlobalData.OnMouseMove(new System.Drawing.Point((int)pos.Position.X, (int)pos.Position.Y));
}

GetTouchPoint メソッドは、マウスの位置を取得するものです。対象コントロールからの相対位置になります。今回は、画像を貼りつけ(Imageコントロール)を使っているので、重宝します。ちなみに、マウス位置をおなじみの「System.Windows.Forms.Cursor.Position」で取得しようとすると、「指」と「マウス」の違いで四苦八苦します。具体的には TouchDown イベントが発生したときには、System.Windows.Forms.Cursor.Position の値は更新されていません。故に、以前のマウス位置(タッチ位置)が取得されてしまうという厄介な出来事が起こります。

GlobalData.OnMouseDown 関数は、form コントロールの上に xaml のコントロールを貼りつけているので、イベントをアップさせるための自作の仕組みです。このあたりは、全面的に WPF アプリケーションで作れば不必要だと思います。

■metro アプリのタッチイベントは?

ちなみに、metro アプリのタッチイベントは WPF アプリとは異なります。

IE10のタッチイベントってなんだろう – vantguarde – web:g
http://web.g.hatena.ne.jp/vantguarde/20110915/1316013489

これは、javascript の例ですが、C# も場合も同様に、

  • PointerPressed
  • PointerReleased
  • Tapped

などがあります。

Pointer が付いているものは、マウス、指、スタイラスを一括で扱うイベントです。ボタンクリックにあたるものは Tapped イベントを使えばOKです。ボタンコントロール自体には、Click イベントがあります。

private void buttonClearClick(object sender, RoutedEventArgs e)
{
    listMessage.Items.Clear();
}

private void rectPointerPressed(object sender, PointerEventArgs e)
{
    listMessage.Items.Add("pointer pressed");

}

private void rectPointerReleased(object sender, PointerEventArgs e)
{
    listMessage.Items.Add("pointer released");

}

private void rectTapped(object sender, TappedRoutedEventArgs e)
{
    listMessage.Items.Add("pointer tapped");

}

WPF アプリの場合は、ボタンをダブルクリックすると自動的にイベントを作ってくれますが、metro の場合はイベント名を入れないといけません。どっちがいいのか微妙なのですが、WPF に合わせて欲しいなと思っています。

ちなみに、iPhone を作成する XCode の場合には、イベント名は自分で付ける方式です。なので、どちらが「良い」とも限りません。慣習ってところです。

ってな訳で、WPF アプリを作っていたからといって、スムースに metro アプリに移行できるかというと違う訳で、優位性としては、

  • xaml を知っている方が有利(win form だと出てこないので)。なので、silverlight を触っている人もokかと。

ってところですが、

  • でも、イベント関係が違うので、思い込むと迷走するかも。

という落とし穴があります。

なので「指でタッチしてスムースに動く」アプリケーションを作る場合には、デスクトップPCでシミュレーションだけでなく、実機が必須な予感がしています(なので、買ったというもある)。

ちょっと、長くなったので、一旦切ります。実際にソフトキーボードを作るノウハウは、別の記事にでも。

カテゴリー: 開発, C#, windows 8 | 3件のコメント

windows 8 cp でリモートデバッグを有効にする

acer w500 に visual studio 11 beta を入れるのは辛いし、cpu が 1GHz ではプログラミングは辛いので、リモートデバッグで動かすようにします。

方法としては2種類あって、

  • 空きデスクトップPCにwin8+vs11をインストールして、acerへリモートデバッグ
  • VMwareにwin8+vs11をインストールして、acerへリモートデバッグ

という方法があります。試しに両方の環境を用意してみて、どちらがやりやすいかなと。手元の空きPCがないので、Vaio Type-S に win8+vs11 を入れてあります。vs11での開発自体は、メインPCからリモートデスクトップを使うので、ネットワーク的には

メインPC -> VMWare or Type-S -> acer

という具合になりますね。

方法 : リモート デバッグをセットアップする
http://msdn.microsoft.com/ja-jp/library/bt727f1t.aspx

リモートデバッグ自体は、上記のURLを参考にしてDVDから入れます。共有フォルダから直接起動もできるようなのですが、今回は acer にリモートデバッグを入れるて試すということで。

image

実は、Visual Studio 11 beta の DVD の場合、フォルダ名が変わっていて「Remote Tools」になっています。ここで「rtools_setup_x86」を実行すればOK。

image

こんな風にインストールされていきます。

スタート画面に「Remote Debugger」がありますね。

image

実行すると、リモートデバッグモニターが表示されます。アイコン化してバックグラウンドで動かすことも可能です。終了するときは、タスクバーのインジケーターのところで終了させればokです。

image

これで準備はできたので、適当なmetroアプリを作って動かしてみます。

■別のwin8マシンで、vs11からリモートで実行する

あらかじめvs11で用意されているテンプレートを使って、デバッグ実行する時に「リモートコンピューター」に変更します。

image

初回は、次のように開発者ライセンスを設定待ちのダイアログがでます。

image

acer 側では、こんな風に同意待ちですね。Visual Studio 11 beta をインストールした時と同じように、Windows Live アカウト(Microsoft アカウント)を入力します。

image

これでうまく行くはずなんですが、下記なる謎のエラーが出て起動できません。

image

…と思ったら、ビルドターゲットが「x64」を変更したままでした。この開発マシンは win8 64bit版が入っていて、acer は 32bit版なんですよね。プラットフォーム ターゲットを「Any CPU」に変えればOKです。

リモートコンピュータを選択すると、アタッチ先のコンピュータ名を選択します。

デバッグを実行するとこんな具合に、実機(acer)と、メインPC間でデバッグができます。

__

実機なので、スライドの動作も楽だし、実機ならではの動作確認が可能ですね。シミュレータをマウスで使っていると、やっぱり違った感覚になるので、実機は必須かと思います。

デバッグなので、ブレークポイントも使えます。

image

今回は、開発PC(実際は、Vaio Type-S をリモートデスクトップ接続)から実機(acer)を制御したのですが、VMWare上にあるwin8+vs11を使っても同様のことができるかと。windows 8 自体がまだ発売されていないのと、メインの開発機のOSを入れ替えるのは躊躇してしまう(というか入れてはいけない)ので、サブマシンが必須。暫くは、こういう暫定のwin8開発マシンを用意しておいて、メイン開発マシンとの併用になるかと。

カテゴリー: 開発, windows 8 | 1件のコメント