「ほっ」と。キャンペーン
<   2010年 02月 ( 23 )   > この月の画像一覧
(.Net)ComboBoxでDataSourceオブジェクトの中にNullがあると...
ComboBoxの DataSource に List<string> オブジェクトを指定したんですが、その List<string> の中に Null があるとそこっから先が Item として追加されないみたいなんです。

例えば下記のような場合です。(C#)
//ComboBoxNullテスト
List<string> lst = new List<string>();
lst.Add(null);
lst.Add("a");
lst.Add("b");
lst.Add("c");
comboBox1.DataSource = lst;

この場合、最初に追加した値が null ですが、実際にコンボボックスを表示すると下記のようになります。
e0091163_1353080.jpg


次に、null を3番目にすると、下記のようになりました。
//ComboBoxNullテスト
List<string> lst = new List<string>();
lst.Add("a");
lst.Add("b");
lst.Add(null);
lst.Add("c");

e0091163_1355179.jpg


つまり、null のところまでは要素として入っていますが、それ以降は無視という感じです。

上記は List<string> の例でしたが、バインドするオブジェクトに DictionaryEntry を使ってみました。(DictionaryEntryに関しては(.Net)DictionaryEntryを使ってコンボボックスに表示文字列と処理用の値をセットする参照)
System.Collections.ArrayList ary = new System.Collections.ArrayList();
ary.Add(new System.Collections.DictionaryEntry(null, null));
ary.Add(new System.Collections.DictionaryEntry("a", 1));
ary.Add(new System.Collections.DictionaryEntry("b", 2));
ary.Add(null);
ary.Add(new System.Collections.DictionaryEntry("c", 3));
 
comboBox1.DataSource = ary;
comboBox1.DisplayMember = "Key";
comboBox1.ValueMember = "Value";


これだと5行目の null のところで、ArgumentNullException が発生し「値を Null にすることはできません。」と怒られます。
5行をのけると、最後の所までちゃんと追加されるんですよね。


なので、今回の現象は List<型> を直接バインドしたときのみ起こるのかもしれません。
[PR]
by jehoshaphat | 2010-02-28 13:08 | .Net開発 | Trackback | Comments(0)
(.Net)Stringの仕様にやられた
事の発端は下記のように string 型のリストを匿名メソッドを使って処理しようとしたときです。
List<string> lst = new List<string>();
lst.Add("test1");
lst.Add("test2");
lst.Add("test3");
 
lst.ForEach(delegate(string s)
{
s = "hoge";
});
lst.ForEach(Console.WriteLine);

この時期待していたのはすべての文字列が hoge になることでしたが、結果は test1 , test2 , test2 でした。

もしかして...と思って下記のようなテストコード書いてみました。
private void button1_Click(object sender, EventArgs e)
{
string str = "test";
funcA(str);
Console.WriteLine(str);
}
 
private unsafe void funcA(String str)
{
str = "aaa";
Console.WriteLine(str);
}

引数に string 型変数を渡してメソッド内で書き換えてます。呼び出し元での Console.WriteLine の結果は "aaa" を期待してたんですが、"test" のままでした。
string って参照型だから、メソッドへも参照渡しされてメソッド内で変更した場合呼び出し元にも影響するんではという思いは儚くも消え去りました。

で、下記のMSDNをみると、.Net では String オブジェクトは、作成時点以降に値を変更できないことから、不変(変更不可)と呼ばれてるようです。
MSDN:String クラス
MSDN:文字列の使用 (C# プログラミング ガイド)

これで謎が解決しました。
つまり上記の例だと関数内で文字列を変更したときには新しい String オブジェクトが生成されており、それは呼び出しメソッドでは参照できないためこのような結果になったんでしょうね。

さらに、上記の例の時の各変数のアドレスを調べてみました。
private void button1_Click(object sender, EventArgs e)
{
unsafe
{
string str = "test";
fixed (char* p = str)
{
Console.WriteLine("呼び出し前strのポインタ: 0x" + ((int)p).ToString("x"));
funcA(str);
fixed (char* p2 = str)
{
Console.WriteLine("呼び出し後strのポインタ: 0x" + ((int)p2).ToString("x"));
Console.WriteLine(str);
}
}
}
}
 
private unsafe void funcA(String str)
{
fixed (char* p = str)
{
Console.WriteLine("引数strのポインタ(文字列変更前):0x" + ((int)p).ToString("x"));
str = "hoge"; //この時実際は新しい string オブジェクトが別メモリ空間に生成されている
fixed (char* p2 = str)
{
Console.WriteLine(str);
Console.WriteLine("引数strのポインタ(文字列変更後):0x" + ((int)p2).ToString("x"));
}
}
}

結果は下記のようになりました。

呼び出し前strのポインタ: 0x1392e14
引数strのポインタ(文字列変更前):0x1392e14
hoge
引数strのポインタ(文字列変更後):0x13a6438
呼び出し後strのポインタ: 0x1392e14
test

やはり、メソッド内で文字列変更すると、実際には別メモリ空間に新しい string オブジェクトが生成されているので、アドレスが変わってます。
つまり引数で渡された str 変数と文字列変更後の str 変数は別物となってるわけですね。
(また、== 演算子で文字列変更前の str と変更後の str を比較すると別インスタンスになるので false が返ります。)

後、ウォッチ式に &str (str変数のポインタが入ってるアドレス)を追加してて気づいたんですが、funcA に入ると &str の値が変わります。
つまり、文字列を指し示すポインタがコピーされたことになるようです。

わかりやすくするために図を書いてみました。
まず、呼び出し元で str に "test" が代入されたときです。
e0091163_2165033.jpg


次に、funcAに入ったときです。
e0091163_2171463.jpg

この時に、funcA内の引数で渡された str はどうやらポインタがコピーされているようなのです。
ただし、str のポインタの指し示す先は呼び出し元と同一のアドレスです。(値が"test")

そして、funcA内で str に新しい文字列を代入したときです。
e0091163_2174175.jpg

この時、string の仕様として、インスタンスは新しいメモリに作成されるので、引数で渡された str ポインタの指し示す先も変更となります。
これで呼び出し元 str と funcA 内の引数で渡された str との間に差異が生じるわけですね。


結局、メソッドの引数に string オブジェクトを参照で渡したいときは ref キーワードをつけないといけないようです。
ref キーワードを付けると引数で渡された参照のポインタはコピーされないようです。
図で書くとこんな感じ。。。
呼び出し元で str に "test" が代入されたときです。
e0091163_2181187.jpg


次に、funcAに入ったときです。
e0091163_2183777.jpg

ref が点いてるため、ポインタのコピーが作成されません。(つまりこれが参照渡し)

そして、funcA内で str に新しい文字列を代入したときです。
e0091163_2191037.jpg



よく、.Net では規定として値渡しを用いると言われてることがよく理解できました。
参照型を渡す場合でも、ref を付けないとポインタがコピー(値渡し)されてたんですね。


ちなみに、下記のようにクラスを生成し、そのメンバとして string オブジェクトを持たして、メソッド内で文字列変更するとどうなるでしょうか?
public class Form1 : Form
{
....
private void button1_Click(object sender, EventArgs e)
{
StringTest cls = new StringTest();
cls.Str1 = "test class";
funcB(str, cls);
Console.WriteLine(cls.Str1);
}
 
private void funcB(string str, StringTest cls)
{
cls.Str1 = "hoge class";
}
}
public class StringTest
{
public string Str1 { get; set; }
}

この場合、結果は "hoge class" となります。
この場合、funcB 内で引数で渡された cls ポインタ自体は呼び出し元の cls のコピー(値渡し)ですが、ポインタの値が同一、つまり、同じ StringTest インスタンスを指し示しているため、メソッド内で書き換えても値が保持されてたわけですね。
図で書くとこんな感じ。
e0091163_2193923.jpg



今までなんでこれに気付かなかったのか不思議です。
やはり詳しい言語仕様しらないと怖いですね。。。
[PR]
by jehoshaphat | 2010-02-26 21:09 | .Net開発 | Trackback | Comments(3)
(.Net)C#で変数のアドレスが知りたい
昔VB.Netでやってたときに、一回インスタンスのアドレスを調査したい時があったんですが、結局その方法が無くて困った経験があります。

C#だと、デバッガ中に知るのは超簡単です。
ウォッチウィンドウに &変数名 と入れると、そのアドレスを知ることができます。
参照型の場合は、ツリーを展開すれば参照先がわかります。
e0091163_2143667.jpg


プログラム中で、変数のアドレス知りたいときはちょっと手順が必要です。

まず、調べたい方が基本型の場合は簡単です。
C# メモリアドレスを表示?にあるようにデバッグ時と同じで変数の前に&を付けるのと、unsafe キーワードでポインタの利用可能とします。
後は、ビルドオプションで、「アンセーフコードの許可」にチェックを入れるだけです。

面倒なのはString等参照型のアドレス取りたいときです。
C#では一応ポインタが扱えるようなので、その方法を用います。
ただし、この方法だとポインタの値(つまりインスタンスのアドレス)はわかるんですが、ポインタ自身のアドレスはわかりません。ポインタ自身のアドレスはデバッガのウォッチを使うしかなさそうです。

.Net ではヒープにたまるオブジェクトは自動メモリ管理機構の対象になるようで、CLRによって勝手に移動させられることがあるようです。(これを移動型と呼ぶらしいです。)
なので、オブジェクトを勝手に移動させないように、一時的に固定する必要あります。
それが fixed ステートメントのようです。

使い方はこんな感じです。
string str = "test";
fixed (char* p = str)
{
Console.WriteLine("ポインタの値:0x" + ((int)p).ToString("x"));
}


このようにコード内でポインタ使ってまでアドレス知りたいことはそうないので、ウォッチ式があれば十分ですね。

参考:
@IT:連載 改訂版 C#入門 第21章 ポインタを使用できる「安全でないコード」
[PR]
by jehoshaphat | 2010-02-26 21:05 | .Net開発 | Trackback | Comments(0)
(Linux)tcpdumpで特定のプロトコルだけみたい
sshでリモートしながら tcpdump 使うと ssh のパケットも拾ってしまうため、他のプロトコルが読めません。
例えばHTTPだけの通信をみたい場合は下記の用にしてやればいいようです。

tcpdump -i eth0 -n tcp port 80

これでTCPでポート80の通信を拾います。
ポート番号を返れば任意のプロトコルが拾えますね。

参考(というかまんま):
ネットワークパケットを覗いちゃえ
[PR]
by jehoshaphat | 2010-02-23 02:53 | ネットワーク | Trackback | Comments(0)
(Linux)ポートを開いているプロセスを知りたい
netstat でみるとあるポートが開かれているんだけど、どのプロセスが開いているのか調べたいというときの方法です。

下記コマンドでできるようです。

# lsof -i:ポート番号
又は
# lsof -c プロセス名



参考:
@IT:特定のポートをオープンしているプロセスを調べるには
[PR]
by jehoshaphat | 2010-02-23 02:52 | ネットワーク | Trackback | Comments(0)
(Linux)NAT内でFTPサーバ立ち上げるにはアドレス変換が必要
CentOS で vsftpd を使ったFTPサーバを構築してます。
今までは CTU(NTT西日本光プレミアムの加入者網終端装置。つまりはルータ) の 21 ポートを制御用コネクションと開放し、直下のLANにあるサーバにマッピングしてました。

もちろんサーバ機はプライベートアドレスしか持っておらず、CTUの静的アドレス変換機能を使って外部からFTP接続を仕様としているので、当然 PASV モードでの運用となります。

で、/etc/vsftpd/vsftpd.conf には下記のように PASV に関する設定を追加して運用してました。

pasv_promiscuous=YES パッシブモードを利用にする
pasv_min_port=4000 ←↓パッシブモード利用時のポート範囲
pasv_max_port=4029 ※firewall、ルーターのポートマッピングにあわせる


で、今回(Linux)CentOSでちょっとトリッキーなNATルータを構築してみたで書いた他所サーバ機(192.168.1.2)にもFTPサーバの機能を持たせて、インターネット側からアクセスできるようにする必要が生じました。

上記にあるような設定にしたんですが、どうも外部から接続できません。(制御用ポートは21がすでに使ってるのでかなり別のポート番号を使ってます。)
ログインはできてるっぽいんですが、ファイルリスト取得中にタイムアウトしてしまいます。
この時ffftpのログを見てると、アクセスしようとしているIPがローカルのIPアドレス(ここのネットワークAにあたる部分)を示してました。

で、調べてみると PASV で待ち受けているアドレスがなぜかローカルのアドレスを送ってるっぽいですね。
上記のCTU直LAN(ネットワークA)上のFTPサーバは21ポートを制御用として使っているため、気が利く CTU が PASV 待ち受けアドレスをグローバルアドレスに変換してくれているようです。
(多分21ポートならという条件で勝手にアドレス変換してるんでしょうね。。)

解決策としては、PASV 待ち受けアドレスとしてグローバルアドレスをクライアントに送ってあげなければなりません。
その機能が vsftpd にも入っているようです。

vsftpd.conf に下記のように設定すればいいようですね。

pasv_promiscuous=YES パッシブモードを利用にする
pasv_min_port=4000 ←↓パッシブモード利用時のポート範囲
pasv_max_port=4029 ※firewall、ルーターのポートマッピングにあわせる

pasv_addr_resolve=yes pasv_addressで名前解決を有効にするかどうか。yesならpasv_addressでDDNS名が使える。
pasv_address=jehu.example.com ルータ側のアドレスもしくは、DDNS名

pasv_addr_resolveが有効だと、pasv_address の設定でダイナミックDNS名を指定してるとIPに直してくれるようです。
pasv_addressにグローバルIPを設定します。ただ、個人サーバは固定IP持ってない時は pasv_addr_resolve を有効にすることでDDNS名も指定できるようですね。


FTPの仕様と PASV に関する話は、パソコンおやじ:FTPサーバの公開で非常によくまとめられています。FTPサーバ構築当初は非常にお世話になったページです。


参考までに vsftpd でFTPサーバ構築時によく指定してる設定もあげときます。

(設定ファイル /etc/vsftpd/vsftpd.conf)
anonymous_enable=NO anonymousユーザ拒否

chroot_list_enable=YES アクセスディレクトリの制限を有効に。
chroot_list_file=/etc/vsftpd/chroot_list ホームディレクトリ以外へのアクセス拒否させるユーザファイル指定
↑のファイルにアクセス拒否をするユーザーを1行ずつ書く。

local_umask=002 パーミッションマスク。775のパーミッションになる
002だと所有者、所有グループは77,その他は5になる

use_localtime=YES JST使用のため追加



■ユーザーのアクセス制限
・ファイル:/etc/vsftpd/ftpusers
ここに操作させたくないユーザ名を記述します。
passwordの認証時に不正なログインとして失敗に終わります。

・ファイル:/etc/vsftpd.user_list
vsftpd.conf に userlist_deny=NO の記述がない、又は userlist_deny=YES と指定した場合は拒否リスト。(デフォルト)
vsftpd.conf に userlist_deny=NO があると接続許可リスト。


vsftpdの設定の詳細は、パソコンおやじ:FTPサーバ(vsftpd)の構築(RedHat系編)が参考になります。


参考:
パソコンおやじ:NATルータ環境でのPASVモード対応
FTPサーバー構築(vsftpd)
[PR]
by jehoshaphat | 2010-02-23 02:50 | ネットワーク | Trackback | Comments(0)
(WindowsServer)グループポリシーで任意のサーバをイントラネットと定義する
以前の記事に書いたように、共有フォルダ上の.Netアプリケーションを動かすには caspol による設定と、(場合によっては)共有フォルダのあるサーバをイントラネットとして定義する必要があります。

本来は、Windows自身が正しくアドレスを解釈して、イントラネット・インターネットを判断すべきなのですが。。
その点ついて、IEやエクスプローラがどのようにゾーンをイントラネット、インターネットと判断してるのかは@IT:検証 IEの自動Proxy設定とセキュリティ・ゾーンが参考になります。
また、MS自身がアドレス形式によってイントラネットがインターネットゾーンとしてしまうことも、MSサポート:FQDN または IP アドレスを使用すると、イントラネット サイトがインターネット サイトとして識別されるにて認めています。


さて、本題ですが、いちいちクライアントPCのインターネットオプションからLAN内のサーバをイントラネットに追加するのは面倒です。
(しかも、これってユーザ毎の設定っぽいのでドメイン環境だとPC個別に設定してもあまり意味がありません。)

そこで、思いついたのが Active Directory のグループポリシーを使ってすべてのユーザにイントラネットの定義を自動的に設定する方法です。(Windowsドメインを構築してないとできませんけどね。)

グループポリシーの "ユーザーの構成" → "管理用テンプレート" → "Windowsコンポーネント" → "Internet Explorer" → "インターネットコントロールパネル" → "セキュリティページ" を開きます。
そして、"サイトとゾーンの割り当て一覧" を有効にし、値の名前と値を入れて設定します。
値の名前にサーバのアドレスが入ります。(例:file://192.168.0.100)
の意味は下記となるようです。
  "1":イントラネット ゾーン
"2":信頼済みサイト ゾーン
"3":インターネット ゾーン
"4":制限付きゾーン
e0091163_2221055.jpg

ただし、ここで設定するとユーザが今まで個別に設定してたゾーンは無視されるので注意しなければなりません。
よって、ユーザがあらかじめ個別にゾーンを指定してたか調査して行う方が安全です。

これに気付かなかったため、大事になっちゃいました。

特に、やられたのがIE6の仕様です。
上記の設定を適用後、あるユーザに個別にゾーンの追加をしようと思い、IEでインターネットオプションのセキュリティタブから信頼済みサイト登録しようと思ってダイアログウィンドウ開きます。
「次のWebサイトをゾーンに追加する」でURLを入れて「追加」ボタン押すと、下のサイトリストには入力したURLが入ります。
てっきりこれでOKかと思って閉じました。
が、再度「サイト」ボタン押下すると追加したものがありません。
IE7だとサイトの追加画面はすべて無効で登録ができない仕様になってます。
また、登録してるのにリストに出てこないケースもありました。
これはMSも認めており、MSサポート:Windows XP SP2 の [サイトとゾーンの割り当て一覧] ポリシーで IP アドレスのサイトが反映されないに記されています。
本当に設定を確認したければ、レジストリを見ろということですかね。。。

グループポリシーエディタの説明には「このポリシーを構成しなかった場合、ユーザーは自分でサイトとゾーンの割り当てを選択することができます。」としかありません。もうちょい詳しく書いてほしいですよね。


ゾーンとして設定するURLやIPアドレスには、ワイルドカードも使えるようです。
例: http://*.example.co.jp
例: *.example.co.jp ←これだとすべてのプロトコルで信頼って意味になるようです。

詳しくはよりセキュリティを高めるための設定: Internet Explorerに書かれてます。


最初は "ユーザーの構成" → "Windowsの設定" → "InternetExplorerのメンテナンス" → "セキュリティ" の "セキュリティゾーンおよびコンテンツの規制" かと思ってましたけど、警告でたのでやめて、上記の方法にしてみました。

とりあえず今のところは問題なく運用できてます。
[PR]
by jehoshaphat | 2010-02-15 22:21 | サーバがらみ | Trackback | Comments(0)
クライアントPCからWindowsServer2003を管理
Windows Server 2008の場合は、Windows Server 2008をリモートから管理するツール(RSAT)にあるように、RSAT を使えば可能でした。

今回は Windows Server 2003 R2 を Windows XP で管理する方法です。

それには、Adminpak.msi をインストールする必要あります。

そこら辺の詳細はMS サポート:Windows XP Professional ベースのクライアントまたは Windows Server 2003 ベースのクライアントを使用して Windows サーバー ベースのコンピュータをリモート管理する方法にあります。

Adminpak は Windows Server のインストールメディアからしかインストールできないと思ってたが、ダウンロードできるようですね。
Windows XP X86用はhttp://www.microsoft.com/downloads/details.aspx?FamilyID=86b71a4f-4122-44af-be79-3f101e533d95&DisplayLang=jaからできます。

インストール終わると管理ツールにサーバ管理用の項目がでてきます。

試しに、ドメイン環境で一番よく使う「Active Directory ユーザーとコンピュータ」を起動してみました。
ところが、編集しようとすると権限が無いと怒られます。(参照は可能です)
おそらくクライアントPCにドメインの一般ユーザでログインしているためと思われます。(ローカルPCへのフルアクセスはできる)

で、最初、管理ツールの「Active Directory の管理」のプロパティから別の資格情報で実行しようとしたが、グレーアウトしてできません。
仕方ないので、%systemroot%\system32\ にMMCファイルで各コンソールが保存されているので、「Active Directory の管理」の実態である、 admgmt.msc のショートカットを任意の場所に作り、プロパティから別資格で起動するように設定しました。
これで、ショーカット起動時に、資格情報を聞かれるようになたので、ドメイン管理者で起動すると編集も可能になりました。
[PR]
by jehoshaphat | 2010-02-14 23:36 | サーバがらみ | Trackback | Comments(0)
レジストリにてプロキシの有効無効の調査
Windowsドメインに参加しているあるPCがグループポリシーで設定してるにも関わらずネットアクセス時にプロキシが通ってないようなので、調査してみました。
本来はそのPCにリモートデスクトップで入り、インターネットオプションから「接続」タブの「LANの設定」からみればいいんですが、「接続」タブを非表示にするようにグループポリシーで設定しているので見れません。


ということで、レジストリをリモートから参照することとしました。
他のPCのレジストリを参照する方法についてはWindowsドメイン環境で他のPCのレジストリを操作するを参照。。


下記のレジストリパスにプロキシの設定が入っているようです。
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ProxyEnable
値が 0 が無効、1が有効だそうですね。
詳細はWindows:IEのプロキシ設定はどこにある?が参考になります。

ところで、リモートでレジストリを参照すると、ルート直下にある HKEY_CURRENT_USER が見当たりません。
見えるのは HKEY_LOCAL_MACHINE , HKEY_USERS のみです。

調べてみると、HKEY_CURRENT_USER は現在ログインしているユーザを表すようです。
その実体は HKEY_USERS 配下にあるようですね。
HKEY_USERS 配下の S-1-5-18 ~ S-1-5-20 まではシステムが作成する特殊な固定SIDだそうです。
S-1-5-21-xxx っていうのが実際のクライアントユーザのレジストリをあらすようです。
この辺の詳細は、レジストリキーHKEY_USERSとは?を参照。

つまり、リモートレジストリだとパスが、
HKEY_USERS\S-1-5-21-9999999999-9999999999-9999999999-9999\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ProxyEnable
となるようですね。


余談ですが、SIDはSID と名前の変換 ~ SID ルックアップ・ツールを使うとユーザ名に変換できるようです。ただし、そのユーザが登録されているActiveDirectoryが参照できる必要がありますが。。。
(ドメインユーザの場合。ローカルユーザの場合はそのローカルPC上でやる必要がある)
[PR]
by jehoshaphat | 2010-02-13 13:22 | 豆知識 | Trackback | Comments(0)
Windowsドメイン環境で他のPCのレジストリを操作する
他のPCのレジストリをリモートで操作する方法です。

%systemroot%\regedit.exe

をコンテキストメニューで「別のユーザとして実行」します。
または、ショートカットを作りプロパティから「別の資格情報で実行する」にチェックします。

これでドメイン管理者権限で実行し、レジストリエディタのファイルメニューから「ネットワークレジストリへの接続」を押下します。

後は対象となるクライアントPCを選択してやるだけですね。
[PR]
by jehoshaphat | 2010-02-13 13:21 | 豆知識 | Trackback | Comments(0)