sai’s diary

プログラミング関連の備忘録とつぶやきを書いています。

C - 配列変数のアドレス参照方法の違い(配列要素のアドレスと配列自体のアドレス)

配列のポインタを取得したい場合 a)配列名のみ b)&配列名[0] c)&配列名 の3通りの書き方があります。

アドレスはいずれも同じになるためあまり気にしていませんでしたが人に聞かれて調べてみたところ演算結果に差異があったのでメモ。

a)、b)は配列に含まれる先頭要素のポインタが取得でき、 c)は配列自身のポインタが取得できます。 http://c-faq.com/aryptr/aryvsadr.html

なので、"char foo[10];“の配列に対して a),b)のポインタに対して+1すると1byte加算される、 c)のポインタに対して+1すると10byte加算される。 という差異が出ます。

c)の挙動を開発で期待することはないかと思いますが、豆知識として。

shape形式の読み込み

国土数値情報で公開されているshape形式のデータを見てみようとしたら手ごろなビューワーが見つからなかったので調べたところ、Rubycsv形式に出力出来ました。

どの様なデータが公開されているかは以下で見てみると分かりやすいと思います。 国土情報ウェブマッピングシステム

require 'georuby'
require 'geo_ruby/shp'

include GeoRuby::Shp4r

@shpfile = "N06-14_Joint.shp"

ShpFile.open(@shpfile) do |shp|
  fields = shp.fields

  print "\"#{shp.file_root}\",record_count=#{shp.record_count}\n\n"
  print "x,y,"
  puts fields.map { |f| f.name }.join(",")

  shp.each do |s|
    print "#{s.geometry.x},#{s.geometry.y},"
    puts fields.map { |f| s.data[f.name] }.join(",")
  end
end

国土交通省国土政策局国土情報課では 国土数値情報(JPGIS準拠データ)からshape形式に変換するツールも公開されていました。 国土数値情報ダウンロードサービス(JPGIS準拠データ)

MacでIntelliJ IDEA14を動かす

システムテスト自動化のワークショップ参加に向けてMacにJDK8とIntelliJ IDEA14を入れたのですが 起動したらJDK6以前でないと起動出来ないとエラーが出ました。

f:id:saitohm:20150321124658p:plain

ググってみるとアプリのInfo.plistを編集すると起動出来るというコメントを見つけたので 試してみたらJDK8でも起動する様になりました。

ターミナルからvi /Applications/IntelliJ\ IDEA\ 14\ CE.app/Contents/Info.plistでファイルを開き、

 <key>JVMVersion</key>
 <string>1.6*</string>

 <key>JVMVersion</key>
 <string>1.8*</string>

に変更すればOKです。

正規の組み合わせではないので挙動がおかしくなる部分がありそうですが、 あとは使いながら試してみたいと思います。

Windows用のloopbackアドレス簡易パケットキャプチャ

WindowsだとWiresharkでloopbackアドレス宛の通信がキャプチャ出来ない、ということでrawソケットを利用した簡易版pcapファイル作成プログラムを作成してみました。

C#でもunsafeを利用するとポインタが扱える、 pcapファイルは意外とフォーマットが単純だった の2点が勉強になりました。

C#なのでポインタ使用箇所はBitConverter.GetBytesメソッドを使った方が良いと思いますが覚えたてのunsafeを使ってみたく。。。

バグ・改善点等、お気づきの点がありましたらコメント頂ければ幸いです。

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

namespace rawPcap
{
    class Program
    {
        // https://wiki.wireshark.org/Development/LibpcapFileFormat
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct PcapGlobalHeader
        {
            public UInt32 magicNumver;  // マジックナンバー
            public UInt16 versionMajor; // メジャーバージョン番号
            public UInt16 versionMinor; // マイナーバージョン番号
            public Int32 thiszone;      // タイムスタンプの補正
            public UInt32 sigfigs;      // タイムスタンプの精度
            public UInt32 snapLen;      // キャプチャしたパケットの最大数(オクテット単位)
            public UInt32 network;      // データリンク種別(http://www.tcpdump.org/linktypes.html)
        }
        unsafe private static readonly int PcapGlobalHeaderSize = sizeof(PcapGlobalHeader);

        private static readonly UInt32 LinkTypeRaw = 101;
        private static readonly UInt32 MagicNumver = 0xa1b2c3d4;
        private static readonly UInt32 snapLenDefault = 65535;
        private static readonly UInt16 Major = 2;
        private static readonly UInt16 Minor = 4;

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct PcapRecordHeader
        {
            public UInt32 tsSec;        // タイムスタンプ(秒)
            public UInt32 tsUsec;       // タイムスタンプ(マイクロ秒)
            public UInt32 inclLen;      // number of octets of packet saved in file
            public UInt32 origLen;      // actual length of packet
        }
        unsafe private static readonly int PcapRecordHeaderSize = sizeof(PcapRecordHeader);

        private static readonly ulong maxFileCount = 65535;

        static void Main(string[] args)
        {
            Socket sock = null;
            FileStream stream = null;

            try
            {
                ulong count;

                Console.CursorVisible = false;

                Console.CancelKeyPress += delegate
                {
                    if (null != sock) { sock.Close(); sock = null; }
                    if (null != stream) { stream.Close(); stream = null; }
                };

                StringBuilder fileName = new StringBuilder();

                sock = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
                sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
                sock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0));

                sock.IOControl(IOControlCode.ReceiveAll, BitConverter.GetBytes(1), null);

                byte[] recvBuffer = new byte[sock.ReceiveBufferSize];

                for (count = 0; count < maxFileCount; count++)
                {
                    fileName.Clear();
                    fileName.AppendFormat("rawPcap{0:D5}.pcap", count);
                    if (!File.Exists(fileName.ToString()))
                    {
                        break;
                    }
                }

                if (maxFileCount == count)
                {
                    Console.WriteLine("自動生成するキャプチャファイルが上限に達しました。({0})", fileName.ToString());
                    return;
                }

                stream = new FileStream(fileName.ToString(), FileMode.CreateNew);
                byte[] globalHeader = new byte[PcapGlobalHeaderSize];
                byte[] recordHeader = new byte[PcapRecordHeaderSize];
                unsafe
                {
                    fixed (byte* bytePointer = globalHeader)
                    {
                        PcapGlobalHeader* globalHeaderPointer = (PcapGlobalHeader*)bytePointer;
                        globalHeaderPointer->magicNumver = MagicNumver;
                        globalHeaderPointer->versionMajor = Major;
                        globalHeaderPointer->versionMinor = Minor;
                        globalHeaderPointer->thiszone = 0;
                        globalHeaderPointer->sigfigs = 0;
                        globalHeaderPointer->snapLen = snapLenDefault;
                        globalHeaderPointer->network = LinkTypeRaw;
                    }
                }

                stream.Write(globalHeader, 0, globalHeader.Length);

                count = 0;
                int recvSize;
                while (true)
                {
                    Console.Write("recv packet={0}                        ", count);
                    recvSize = sock.Receive(recvBuffer);
                    TimeSpan span = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0));
                    unsafe
                    {
                        fixed (byte* bytePointer = recordHeader)
                        {
                            PcapRecordHeader* recordHeaderPointer = (PcapRecordHeader*)bytePointer;
                            recordHeaderPointer->tsSec = (UInt32)span.TotalSeconds;
                            recordHeaderPointer->tsUsec = (UInt32)span.Milliseconds*1000;
                            recordHeaderPointer->inclLen = (UInt32)recvSize;
                            recordHeaderPointer->origLen = (UInt32)recvSize;
                        }
                    }
                    stream.Write(recordHeader, 0, recordHeader.Length);
                    stream.Write(recvBuffer, 0, recvSize);
                    stream.Flush();

                    Console.CursorLeft = 0;
                    count++;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("{0}", e);
            }
            finally
            {
                if (null != sock) { sock.Close(); sock = null; }
                if (null != stream) { stream.Close(); stream = null; }
            }

            return;
        }
    }
}

雑多 - Windows8.1が不調なのでシステムファイルチェッカーを使ってみました。

自宅のPC(Windows 8.1)が度々ブルースクリーンになる様になってしまったのでOS再インストール以外に手はないかとググったところ、システムファイルチェッカーなるものがあると知り、試してみました。

実施方法はマイクロソフトのサイトで説明されている様に管理者権限でコマンドプロンプトを起動し、sfc /scannowコマンドを実行すればOKです。(対応OSはWindows 8.1/8/7/Vistaとのことです。)

sfc /scannowコマンドで異常なし又は修復完了すれば良いですが、以下の様なメッセージが出てしまった人は追加作業が必要になります。(私は出ました。。。)

Windows リソース保護により、破損したファイルが見つかりましたが、それらの一部は修復できませんでした。
詳細は、CBS.Log %WinDir%\Logs\CBS\CBS.log に含まれています。

マイクロソフトのサイトは修復に失敗したファイルを個別にtakeownコマンドで修復する手順があったのですが、マイクロソフトのコミュニティで紹介されていたDISM.exe /Online /Cleanup-image /Restorehealthコマンドを試したら修復されました。

Windows イメージを修復するDISM がサポートされているプラットフォームを見るとOSのバージョンによる制限もある様ですが、OS再インストールされる前に試してみる価値はあると思います。

C# - Windows サービス アプリケーションのお試し

C#を扱うことになりそうなので、勉強のためWindowsのサービスアプリを動かしてみました。

ビルド環境の準備

ビルド環境はフリーのSharpDevelop 4.4を利用。
SharpDevelop 4.3用の日本語化パッチがあり、適用してみたところ大丈夫そうでした。

日本語化パッチの適用手順
  1. SharpDevelopを停止する。
  2. <SharpDevelopインストール先>\SharpDevelop\4.4\data\resources配下にStringResources.jp.resourcesファイルを配置。
  3. <SharpDevelopインストール先>\SharpDevelop\4.4\data\resources\languages\LanguageDefinition.xmlファイルを修正。
  4. SharpDevelopを起動して、メニューのTools→Options→UI LanguageからJapaniseを選択
修正前)<!--\<Languages name="Japanese"   code="jp"  icon="japan.png" />-->  
修正後)<Languages name="Japanese"   code="jp"  icon="japan.png" />  

動作確認

あとはSharpDevelopのメニューからファイル→新規作成→ソリューション、
C#Windowsアプリケーション→コンソールアプリケーションを選択するとひな形が作られるのでひな形をベースにMSDNサイトのサイトを見ながらコーディング。

方法 : プログラムでサービスを作成する
方法 : サービス アプリケーションにインストーラーを追加する
ServiceInstaller クラス
EventLog クラス

using System;
using System.ServiceProcess;
using System.Diagnostics;
using System.Collections;
using System.Configuration.Install;
using System.ComponentModel;
using System.Threading;

namespace SampleService {

    /* サービスインストール用クラス */
    [RunInstaller(true)]
    public class SampleServiceInstaller : Installer {

        private ServiceInstaller serviceInstaller;
        private ServiceProcessInstaller processInstaller;

        /* コンストラクタ */
        public SampleServiceInstaller() {
            /* プロセスのインストーラ設定 */
            processInstaller = new ServiceProcessInstaller();
            processInstaller.Account = ServiceAccount.LocalSystem;  /* アカウント=ローカルシステム */

            /* サービスのインストーラ設定 */
            serviceInstaller = new ServiceInstaller();
            serviceInstaller.ServiceName = "SampleService";         /* このサービスを識別するためにシステムが使用する名前を示す。(ServiceBase.ServiceNameと一致させること) */
            serviceInstaller.Description = "サンプルです。";         /* サービスの説明 */
            serviceInstaller.StartType   = ServiceStartMode.Manual; /* スタートアップの種類=手動 */

            Installers.Add(serviceInstaller);
            Installers.Add(processInstaller);
        }
    }


    public class Entry {
        /* Main関数 */
        public static void Main() {
            /* サービス開始 */
            ServiceBase.Run(new Service1());
            return;
        }
    }


    /* サービス定義用クラス */
    public class Service1: ServiceBase {

        /* イベントログ用メンバ変数 */
        private EventLog eventLog;

        /* コンストラクタ */
        public Service1() {
            ServiceName         = "SampleService";  /* サービス名 */
            CanPauseAndContinue = true;             /* サービスの一時停止・再開をサポート */
            AutoLog             = false;            /* 自動のログ出力を無効化 */

            eventLog        = new System.Diagnostics.EventLog();
            eventLog.Source = "SampleService";  /* イベントビューアーで表示されるソース */
        }

        /* サービス開始時のハンドラ(必須) */
        protected override void OnStart( string[] args ) {
            eventLog.WriteEntry("OnStart.",EventLogEntryType.Information);
        }

        /* サービス停止時のハンドラ(必須) */
        protected override void OnStop() {
            eventLog.WriteEntry("OnStop.",EventLogEntryType.Warning);
        }

        /* サービス一時停止時のハンドラ(オプション) */
        protected override void OnPause() {
            eventLog.WriteEntry("OnPause.",EventLogEntryType.Error );
        }

        /* サービス一時停止解除時のハンドラ(オプション) */
        protected override void OnContinue() {
            eventLog.WriteEntry("OnContinue.",EventLogEntryType.FailureAudit);
        }
    }
}

ビルド後にコマンドプロンプトを管理権限で起動して、下記コマンドを実施してサービスをインストール

cd c:\Windows\Microsoft.NET\Framework\v4.0.30319
InstallUtil.exe "<ビルドしたバイナリ>"

インストール後に「コンピュータの管理」から該当サービスを開始すればOKでした。
(AnyCPUでビルドしたバイナリをc:\Windows\Microsoft.NET\Framework64\v4.0.30319でインストールしようとしたら怒られました。)

尚、インストールコマンドに/uを指定するとアンインストールになります。

cd c:\Windows\Microsoft.NET\Framework\v4.0.30319
InstallUtil.exe "<ビルドしたバイナリ>" /u

C#はnewした領域を解放しなくて良いそうですが、違和感がありますね。
3.9 自動メモリ管理

他に参考になりそうなページ。
C# チュートリアル
C# プログラミング ガイド

Ruby on Rails - メールアドレスの取り扱いについての疑問。

Ruby on Railsチュートリアル6.2.5一意性を検証するで紹介されていたテクニックで、メールアドレスをDBに登録する際にDB登録前のコールバック処理でメールアドレスをすべて小文字にする処理が紹介されていましたが、ローカルパート(@より前の部分)まで小文字にしても大丈夫だろうかと気になり、調べてみました。

RFC5321の2.4章を見るとメールボックス(=メールアドレスと解釈しています)のローカルパートに関しては大文字小文字を区別する記載(The local-part of a mailbox MUST BE treated as case sensitive.)がありましたが、直後に大文字・小文字を区別するのは推奨しない(However, exploiting the case sensitivity of mailbox local-parts impedes interoperability and is discouraged. Mailbox domains follow normal DNS rules and are hence not case sensitive.)という記載も見受けられました。

尚、ドメイン(@より後の部分)に関しては大文字、小文字を区別しない旨が書かれていました。 (Mailbox domains follow normal DNS rules and are hence not case sensitive.)

RFC5321
RFC5321(日本語訳)((株)HDE様に感謝)

RFC5322 の3.4.1章にも書式(mailbox,addr-spec)の規格がありましたが、最終的にはRFC5321を参照する形に読めました。

上記から厳密にはローカルパートの小文字化は問題があると考えましたが、Ruby on Rails チュートリアルに書かれている問題を試せていないので宿題にします。

仮にDBのアダプタがメールアドレスの大文字・小文字を区別することを問題としているなら、ドメイン部だけ小文字化すれば良いですが、そうでないならせめてユーザ登録した際の確認メールを送付する際に小文字化したメールアドレスで送付すれば意図しないメールアドレスでユーザ登録が完了してしまったなんていう事態は回避出来るのではと思います。

Ruby on Rails チュートリアルの一部抜粋:

残念なことに、メールアドレスの一意性を保証するためには、もう1つやらなければならないことがあります。それは、メールアドレスをデータベースに保存する前にすべての文字を小文字に変換することです。その理由は、データベースのアダプタが常に大文字小文字を区別するインデックスを使っているとは限らないからです16。

16.
著者のシステム上のSQLiteとHeroku上のPostgreSQLで直接実験してみたところ、この手順は実際に必要であることがわかりました。

補足: ドメイン名に関する規定はRFC1035の2.3.1章にあります。

RFC1035
RFC1035(日本語訳)(Ishida So様に感謝)