葛藤プログラマの一日

2008-05-30

Excelファイル内の検索

システムの見積するのに、特定の文字に囲まれた文字列の数を、シート毎、文字列毎に個数を計算する必要がでてきた。

そこで、以下のようなプログラムを作成。
はじめは、Worksheet.UsedRange の各セルを全部調べてましたが、あまりにも遅くて・・・(^^;
そこで、WorkSheet.UsedRangeで取得したRangeのFindとFindNextを使うことで数十倍速く・・・
はじめからこうすればよかったのね。


Sample
------


// 参照の追加で、COMコンポーネント「Micorosoft Excel 11.0 Object Library」の追加が必要
// COMコンポーネントの追加については、最後の記述を参照

using System.Runtime.InteropServices;
using System.Collections;
using System.IO;
using ExcelApp = Microsoft.Office.Interop.Excel;

public void main(){
SortedList> stringListInSheet = ExcelInfoData(Excelファイル名);
//おまけ
OutputCsvFile(stringListInSheet, ファイル名);
}


public SortedList> ExcelInfoData(string filePath, string leftSideChar, string rightSideChar)
{
SortedList> stringListInSheet = new SortedList>();
try
{
_excelApp = new ExcelApp.Application();
_excelApp.Visible = false;
_books = _excelApp.Workbooks;
_book = _books.Add(filePath);
_sheets = _book.Sheets;

for (int sheetCounter = 1; sheetCounter <= _sheets.Count; sheetCounter++)
{
try
{
_sheet = (ExcelApp.Worksheet)_sheets.get_Item(sheetCounter);
_range = _sheet.UsedRange;

List list = new List();
Object missing = System.Reflection.Missing.Value;

ExcelApp.Range findRange = _range.Find(leftSideChar, missing,
ExcelApp.XlFindLookIn.xlValues, ExcelApp.XlLookAt.xlPart,
ExcelApp.XlSearchOrder.xlByRows, ExcelApp.XlSearchDirection.xlNext, true, true, missing);
ExcelApp.Range prevRange;
if (findRange == null)
{
ReleaseAndNullComObj(findRange);
continue;
}
int columnIndex = findRange.Column;
int rowIndex = findRange.Row;
list.AddRange(GetStringBetweenChars((string)findRange.Text, new char[] { leftSideChar[0], rightSideChar[0] }));
prevRange = findRange;

while (findRange != null)
{
// 2度目以降はFindNext
findRange = _range.FindNext(prevRange);
ReleaseAndNullComObj(prevRange);

if (findRange.Column == columnIndex && findRange.Row == rowIndex)
{
break;
}

list.AddRange(GetStringBetweenChars((string)findRange.Text, new char[] { leftSideChar[0], rightSideChar[0] }))
prevRange = findRange;
}
ReleaseAndNullComObj(findRange);

// シート名の出力
stringListInSheet.Add(_sheet.Name, list);
}
finally
{
ReleaseAndNullComObj(_range);
ReleaseAndNullComObj(_sheet);
}
}
}
finally
{
ReleaseAndNullComObj(_sheets);
ReleaseAndNullComObj(_book);
ReleaseAndNullComObj(_books);
_excelApp.Quit();
ReleaseAndNullComObj(_excelApp);
}
return stringListInSheet;
}


private void ReleaseAndNullComObj(object comObj)
{
if (comObj == null)
return;
Marshal.ReleaseComObject(comObj);
comObj = null;
}


private List GetStringBetweenChars(string source, char[] sideChars)
{
int leftSideIndex = -1;

List result = new List();

for (int i = 0; i < source.Length; i++)
{
if (source[i] == sideChars[0])
{
if (leftSideIndex == -1)
leftSideIndex = i;
continue;
}
else if (source[i] == sideChars[1])
{
if (leftSideIndex != -1)
{
result.Add(source.Substring(leftSideIndex + 1, i - 1 - leftSideIndex));
leftSideIndex = -1;
}
}
}
return result;
}


// おまけ
private void OutputCsvFile(SortedList> data, string filePath)
{
using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.Unicode))
{
writer.WriteLine("シート名\t文字列\t数");
Dictionary statisticsData;
foreach (string key in data.Keys)
{
statisticsData = new Dictionary();
foreach (string value in data[key])
{
if (!statisticsData.ContainsKey(value))
statisticsData[value] = 1;
else
statisticsData[value]++;
}
foreach (string value in statisticsData.Keys)
{
writer.WriteLine("{0}\t{1}\t{2}", key, value, statisticsData[value]);
}
}
}
}









COMコンポーネントは、Excelのセットアップでインストール項目として、 [.NET プログラミング サポート]を選んでインストールする必要があります。
http://www.microsoft.com/japan/msdn/office/office2003/OfficePrimaryInteropAssembliesFAQ.aspx
http://msdn.microsoft.com/ja-jp/library/kh3965hw(VS.80).aspx

なお、以下のURLからダウンロードしてインストールすることもできます。
http://www.microsoft.com/downloads/details.aspx?familyid=3c9a983a-ac14-4125-8ba0-d36d67e0f4ad&displaylang=en

2007-12-13

.NETでのLOGFONTの使い方

例えば、TextBox.Font

private LogFont _logFont = new LogFont();


public override Font Font {
get {
return base.Font;
}
set {
value.ToLogFont(_logFont);
_logFont.lfEscapement = 2700; // 文字列の傾き。反時計回りの角度
_logFont.lfFaceName = "@" + value.Name;
base.Font = Font.FromLogFont(_logFont);
}
}


// CharSetをUnicodeで指定すること!
//(そうしないと、lfFaceNameが正しく取得されない)
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
internal class LogFont
{
internal int lfHeight;
internal int lfWidth;
internal int lfEscapement;
internal int lfOrientation;
internal int lfWeight;
internal byte lfItalic;
internal byte lfUnderline;
internal byte lfStrikeOut;
internal byte lfCharSet;
internal byte lfOutPrecision;
internal byte lfClipPrecision;
internal byte lfQuality;
internal byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string lfFaceName;
}



2007-12-05

USBメモリのドライブの確認方法

private bool CheckDriveLetterUsbAndRemovable(string drive) {
if (!drive.Contains(":")) {
drive += ":";
}

string[] partitionInfoArray;
string driveNumber = null;
string driveLetter = null;
string physicalDiskPartitionInfo = null;
string logicalDiskPartitionInfo = null;
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_LogicalDiskToPartition");

foreach (ManagementObject mo in searcher.Get()) {
// 論理ドライブ
logicalDiskPartitionInfo = mo["Dependent"].ToString();
// Win32_LogicalDisk
ManagementObject dm = new ManagementObject(logicalDiskPartitionInfo);
driveLetter = dm["DeviceID"].ToString();
UInt32 driveType = (UInt32)dm["DriveType"];
//string caption = dm["Caption"].ToString();
//string description = dm["Description"].ToString();

if (driveLetter != drive)
continue;
// リムーバブルドライブか確認
if (driveType != 2)
continue;

// 物理ドライブ
physicalDiskPartitionInfo = mo["Antecedent"].ToString();
// Win32_DiskPartition
ManagementObject pd = new ManagementObject(physicalDiskPartitionInfo);
driveNumber = pd["DiskIndex"].ToString();
//string dIndex = pd["Index"].ToString();
//string dDviceId = pd["DeviceID"].ToString();

ManagementObjectSearcher disks = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive");
foreach (ManagementObject disk in disks.Get()) {
string disk_InterfaceType = disk["InterfaceType"].ToString();
string disk_Name = disk["Name"].ToString();
//string disk_MediaType = disk["MediaType"].ToString();
// USBインターフェースで、物理ドライブが一致したらOK
if ((disk_InterfaceType == "USB") & (disk_Name == "\\\\.\\PHYSICALDRIVE" + driveNumber)) {
return true;
}
}
}
return false;
}

2006-11-24

WindowsでのNTPは、マイクロソフトのKBなどに書かれていることと実際には動きがことなるようで、本当の動きを知るには試してみるしかなかった。以下の内容は、その検証結果の備忘録でもある。

NTPサーバー環境を作成するときに気をつけなければいけないのは、ドメイン環境とワークグループ環境でまったく動作が異なる点である。
そもそも、ドメイン環境ではKerberos認証のためにDCとクライアントの時刻が自動的に同期されるので、そのDCとNTPサーバーだけで時刻の同期を行えばよいということになる。
NTPサーバーの動作などについては以下のURLが参考になります。
http://www.atmarkit.co.jp/fwin2k/operation/winntp01/winntp01_01.html



WindowsにおいてNTP環境を構築する時の注意点


1.権威ある時刻サーバーの設定

 NTPサーバーは、「権威ある時刻サーバー」(「信頼できるタイムサービス)というときもある)である必要がある。Windows では、レジストリの値により「権威ある時刻サーバ」になる。
マイクロソフトのKBでは以下のように説明されている。
  • Windows2000  HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters\ReliableTimeSource
     を 1 に設定したサーバー。
  • WindowsXP, WindowsServer2003  HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config\AnnounceFlags
     を 5に設定したサーバー。ドメインコントローラでの設定変更のみ有効。

 参考:
    http://support.microsoft.com/kb/216734/
    http://support.microsoft.com/kb/314054/
    http://support.microsoft.com/kb/816042/

しかし...実際はメンバサーバーも、上記レジストリの値は有効。逆にWindowsServer2003の場合は、メンバサーバーも上記値でなければNTPサーバーとして使うことができない。























2.NTPサーバーのポートを開くこと

 NTP サーバーとして動作させるには、UDP:123 を開く必要がある







































3.テストする際の注意点

 時刻の同期をテストするときには、WindowsTimeサービスを停止&開始する必要がある。その際、HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient\SpecialPollInterval のデータ値を削除しておかなければ、起動した時にすぐには同期されない。なぜなら、SpecialPollInterval(前回の同期時刻)に保存されている時刻から、SpecialPollInterval(同期間隔)の時間が経過しなければ時刻が同期されないためである。さらに、注意する必要があるのが、SpecialPollInterval の値は停止したときに書き込まれるということである。
よって、
 NG: 値削除 → サービス停止 → サービス開始
 OK: サービス停止 → 値削除 → サービス開始
となる。陥りやすいミスなので気をつける必要がある。


4.NTPサーバーを複数指定した場合

 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters\ntpserver に複数のサーバーを指定した場合は、先頭から順に同期が試されるわけではない。複数指定されている場合には、通信の距離の近い方から同期する。(なので、必ずしも物理的にネットワークが近い方が先と言うことではない)


5.同期間隔について

  1. MinPollInterval値およびMaxPollInterval値による同期間隔

    MinPollInterval値の同期間隔から同期の状態を確認しながら徐々に間隔が空く形で最終的にはMaxPollInterval値の同期間隔で同期される
  2. SpecialPoolInterval値による同期間隔

    SpecialPoolInterval値に指定された間隔で定期的に同期される

6.環境による同期間隔の違い

  1. ドメイン環境
    ①「MinPollInterval値およびMaxPollInterval値による同期間隔」が使われる
  2. ワークグループ環境
    ①W32Time\Parameters\NTPServerに「0x1」付きでNTPサーバーが指定された場合は「SpecialPoolInterval値による同期間隔」が使われる
    ②それ以外は、「MinPollInterval値およびMaxPollInterval値による同期間隔」が使われる

7.NTPサーバー指定のオプション

 クライアントで、NTPサーバーを指定する際のでオプションには以下のようなものがある
 書式:サーバー名(or IPAddress),オプション
 オプション
  0x0(または、指定無し) --- 指定されたサーバーをすべてチェック、エラーログが出力される
  0x1 --- MinPoolInterval の間隔で同期し続ける
  0x8 --- MinPoolInterval の間隔で4回同期する
例:ServerName1,0x1 ServerName2,0x1

ラベル:

2006-09-27

System.Transactions による分散トランザクション

.NETFramework2.0から追加された機能です。これがあれば、別々のDBサーバーの情報を1つのトランザクションで更新できるようになります。いままでは、1つのトランザクションで複数のDBサーバーの情報を更新しようとしたら、リンクサーバーとか使わなければいけませんでしたが、これでだいぶ楽になります。


テストしたソースはこんな感じ…

SqlConnection con1 = null;
SqlConnection con2 = null;
try {
con1 = new SqlConnection("connectionString");
SqlCommand cmd1 = con1.CreateCommand();
cmd1.CommandText = "commandText ";

con2 = new SqlConnection("connectionString");
SqlCommand cmd2 = con2.CreateCommand();
cmd2.CommandText = "commandText";

using ( TransactionScope ts = new TransactionScope() ) {
con2.Open();
cmd2.ExecuteNonQuery();

con1.Open();
cmd1.ExecuteNonQuery();

ts.Complete();
}
} finally {
if ( con1 != null )
con1.Close();
if ( con2 != null )
con2.Close();
}


おおっ、すばらしい。きちんとトランザクションが1つになっています。
今回は、commandText については、トランザクションを使用したストアドプロシージャを指定しました。System.Transactions を使うことで、ストアドプロシージャで指定したトランザクションは無視されるようです。今まで使っていたストアドプロシージャがそのまま使えるという点で、安心です。


最後に、環境整備ではまった点

1.クライアント、サーバーそれぞれに、MSDTCが必要!
WindowsXPは標準で入っているようですが、サーバーは別途インストールする必要があります。





  1. [Windows コンポーネントの追加と削除] をクリックします。
  2. [アプリケーション サーバー] ―. [ネットワーク DTC アクセスの有効化] を追加
  3. 分散トランザクション コーディネータ サービスを停止し、再び開始します。
  4. Microsoft SQL Server、および分散トランザクションと連携するその他のリソース マネージャ サービス (Microsoft メッセージ キューなど) をすべて停止し、再び開始します。
    (要は、再起動が良いでしょう(^^;)
2.「Distributed Transaction Coordinator」サービスが開始されていること!
これ、MSDTCのサービスです。


3.エラー別の対処法

【エラー内容】
"分散トランザクション マネージャ (MSDTC) のネットワーク アクセスは
無効になっています。 コンポーネント サービス管理ツールを使用して、
MSDTC のセキュリティ構成でネットワーク アクセスの DTC を有効にして
ください。"

http://support.microsoft.com/kb/839279

サーバー、クライアント、共に必要な設定。

「対処手順 」
(1) dcomcnfg.exe (または、[管理ツール] – [コンポーネントサービス ]を起動)

(2) コンポーネントサービスを展開して、[マイコンピュータ]のプロパティ を選択















(3) [MSDTC]タブ – [セキュリティ構成]ボタン を押す


















(4) Windows XP, Windows Server 2003R2 の場合


  1. 次の項目にチェックを付ける
    ・[セキュリティ設定] - [ネットワーク DTC アクセス]

  2. ・[セキュリティ設定] - [トランザクション マネージャ通信] - [受信を許可する]

  3. ・[セキュリティ設定] - [トランザクション マネージャ通信] - [送信を許可する]

  4. ・[セキュリティ設定] - [トランザクション マネージャ通信] - [認証を必要としない]


(5) Windows Server 2003 の場合
次の項目にチェックを付ける
・[セキュリティ設定] - [ネットワーク DTC アクセス]

・[セキュリティ設定] - [ネットワーク DTC アクセス] - [ネットワーク トランザクション]

・[セキュリティ設定] - [ネットワーク DTC アクセス] - [ネットワーク クライアント]

(6) OSに関係なく、
[DTC ログオン アカウント] – [アカウント] が「NT AUTHORITY\NeworkService」になっていることを確認。なっていなければ、アカウントを変更する。




【エラー内容】
"基本トランザクション マネージャとの通信が失敗しました。"
http://support.microsoft.com/kb/839279

「対処手順」

Windowsファイアーウォールの例外に以下の2つを追加
 「%windir%\system32\msdtc.exe」
 「[ポート番号] 135 、[TCP]」



【エラー内容】
"パートナー トランザクション マネージャにより、リモート トランザクションまたはネットワーク トランザクションのサポートが無効にされました。 (HRESULT からの例外: 0x8004D025) "
http://support.microsoft.com/?scid=kb;ja;817064&spid=3208&sid=62


DBサーバー側の設定変更

「対処手順」

  1. [Windows コンポーネントの追加と削除] をクリックします。
  2. [アプリケーション サーバー] ―. [ネットワーク DTC アクセスの有効化] を追加
  3. 分散トランザクション コーディネータ サービスを停止し、再び開始します。
  4. Microsoft SQL Server、および分散トランザクションと連携するその他のリソース マネージャ サービス (Microsoft メッセージ キューなど) をすべて停止し、再び開始します。

    最後のSQLServer再起動を忘れやすいので注意!ちなみに、WindowsServer2003R2の場合は、再起動が要りません。

2006-09-16

Windows Workflow Foundation (WF)

環境構築
インストールしたのは、(インストール順)
 Microsoft Pre-Release Software Microsoft .NET Framework 3.0 - Release Candidate
 Microsoft® Windows® Software Development Kit (Web) for RC 1 of Windows Vista and .NET Framework 3.0 Runtime Components
 Microsoft Visual Studio Code Name “Orcas” Community Technology Preview – Development Tools for .NET Framework 3.0
 Microsoft® Visual Studio® 2005 Extensions for Windows® Workflow Foundation Release Candidate 5

の4つです。もちろん、VirtualServer2005R2 にインストールです。


サンプル 
 Windows SDK .NET Framework (WinFX) 3.0 Samples RC1
 http://www.netfx3.com/ からダウンロード

を使っています。


 感想としては、WFを使うことのメリットは大きいでしょう。どちらかというと、WFの利点と言うよりも、ワークフローを導入するメリットになるかもしれませんが... 処理の流れがWFによってモデル化され、定型化されることによって、フローの可視性は相当あがります。
 今まではコード上で記述していたために、その流れが見づらくメンテナンスが難しいものでした。また、ドキュメントが残っていて流れが把握できたとしても、修正が重ねられて実際の流れとは異なっているということも良くあります。
 しかし、VisualStudioに組み込まれているWFを使うことによって、ドキュメントとプログラムが一体になっているため、そういった相違もなくなりますし、メンテナンスもし易くなります。メンテナンスがし易くなると言うことは、言い換えると流用しやすくなるということです。他のシステムで使っているワークフローを流用して、自分のシステム用にカスタマイズすることも出来るようになります。また、VisualStudioに統合されている効果も大きいです。ワークフローのソースをコピーすれば、他のシステムでも簡単に組み込むことが出来ます。
 WFに関することで言えば、フロー図の上にブレークポイントを設定できたり、アクティビティをカスタマイズして作成したり、という点に魅力を感じますが、もう少し使ってみて、WFの良さを探ってみたいと思います。 現状のWFの利用に難を言えば、各業務のためのサンプルとなるワークフローがあれば良かったのかなと思います。そうすると、導入しやすくなるのかな?と思います。けどそのうち、「ワークフローコレクション!」みたいな形でどこかのメーカーが売り出しそうですね(^^;


ちなみに、他のFoundationは、WPF(Windows Presentation Foundation)、WCF(Windows Communication Foundation) であるのに、WF という呼び名になっている理由は、ある団体と同じ呼び名になるから(プロレス団体ではなく、世界自然保護基金)だそうです。まぁ、どうでもいいですが、わざわざ変えなくても(^^;

2006-09-09

VirtualServer2005R2 を入手。早速使ってみました。
今回は、.NET Framework3.0 を試用するために入れてみたのですが、用途は他にも色々あると思いました。

VirtualServer2005R2の用途
 (1)システムのインストールテスト、動作テスト
 (2)あまり使われない複数の物理サーバーを、VirtualServer を使って一つの物理サーバーに集約
 (3)通常使うOSをVirtualServerにしてしまうとか(^^;...マシン交換時の移行が楽!?
考えたら他にもいろいろありそう。64bitサーバーであれば、メモリも多く積めるので、複数のVirtualServerを1台の物理サーバーで運用することも現実的になるかもしれません。

VirtualServer2005R2の新機能「差分ディスク」
ところで、VirtualPC2004 の時には無かった「差分ディスク」は、VirtualServer の用途をさらに広げています。

  • 「差分ディスク」は、「親ディスク」の内容と分離して状態を保存します。
  • 「親ディスク」の内容は変更されない
  • 複数の「差分ディスク」を作成して、その都度、使い分けることも可能
  • 後から「親ディスク」に「差分ディスク」を統合できる
と、なかなか良いところだらけです。一つ難を言えば、ディスク容量が結構必要になるということでしょうか?けど、それはVirturlPC2004のときの「復元ディスク」も同じだから、問題にはならないか...

例えば、「親ディスク」にOSをインストール。それ以降に行う作業を「差分ディスク」で行うようにすれば、複数の作業環境を別々の「差分ディスク」に保存することができ、切り替えて使用することができます。
VirtualPC2004の時にあった「復元ディスク」は、「親ディスク」に1:1であったため、複数の状態を保持することができませんでしたが、「差分ディスク」は「親ディスク」と1:nなので、状態を幾つでも保存できるところが、根本的に違います。

「差分ディスク」の使用で迷った点
 「差分ディスク」を作った後に、「親ディスク」を間違って書き換えられないように、マニュアルに書いてあるとおりに、vhdファイルをReadOnlyに変更。システムを起動すると、「書き込みできない」旨のエラー。色々試したところ、バーチャルマシンで「親ディスク」から「差分ディスク」を使うように変更しなければいけないのでした。これって大事なことなのに、マニュアルに書いてない(TT)

VirtualPC2004と比べて不便になった点
 バーチャルマシンを起動しているマシンとクリップボードが共有されない。これは、かなり不便!