Subscribe RSS

Archive for 4月, 2005

COM+ 4月 28

COM,DCOMとは何か

Microsoftの製品を使って開発をするためにはCOM,DCOMの知識が必要です。知らなければOfficeツールやWindowsが提供しているパーツを使うことができないのです。
逆にいうとM$の提供している製品はすべてCOMという約束にのっとってできているのです。ここには私なりにCOMを解説してみました。やっかいなものを勉強する手段のひとつにいろんな人の意見を見てみる、という方法があります。
その一助になれば幸いです。

仕事上必要で勉強しましたが、コンピューターサイエンスという観点では、ちと、疑問が残る仕様ですねぇ。COMって。

COM以前

Windows3.1により、ワープロ内に表計算ソフトで作成したグラフなどを「カット&ペースト」、つまりデータの切り貼りを簡単に行うことが可能になった。
その次にデータの関係を構築しておくと、データが修正されたら自動的に反映される、いわゆるDDEというメカニズムがとりこまれた。DDEはデータのやり取りだけができる、クライアントとサーバーの関係をもっていたが、DDEはデータの大きさなどに制限があった。
次にでてきたものがOLE (Object Link and
Embedding)である。これは、Wordを使用している中に、Excelワークシートを添付しクリックすると、Excelが別に起動するのではな
く、Word自身のメニューがExcelのそれと入れ替わってしまうように、複数のアプリケーションによりひとつのドキュメントを作成できる機能のことで
ある。(もっとも現在、こういう文書をOLEドキュメントと呼ぶ)当初のOLEは混乱が激しかったようで、有名なテクニカルライター全員が泣いていた。
複数のアプリで構成されるドキュメントへの解決策は、一般的なプログラム開発にも必要な技術であった。複数アプリケーションにより、あらたなひとつのアプ
リケーションを作っていくという考え方(コンポーネントアーキテクチャという)をインプリメントしたものが、COMである。

 

COMはインターフェースの定義の仕方

COMの基本はインターフェースの定義だけである。具体的には、あらゆるプログラミングインターフェースはIUnkownというインタフェースを継承しな
くてはならない。これにより、COMにのっとって開発されたプログラムを利用するならば、IUnknownに問いあわせればどういうインターフェースを
もっているかわかることになる。

インターフェースは複数もつことは可能である。もちろん各インターフェースもIunknownをもたねばならない。また、Iunknownにセットする最
初のいくつかのインターフェースポインタは決まった使い方をしなくてはならない。後述するAddreff,Relaseなどの機能を実装する。

WindowsがC++で開発されていることから、当初はインターフェースのリスト(Vtableと呼ばれる)は、ポインタだけであった。しかし
Visual Basicのようなポインタをサポートしていない言語では極めて使いにくい。そこで、COMとしては必須ではないが、Idispatchと
いうインターフェースが出てきた。最近のCOMではこちらを用意するのが普通である。Idispatchはインターフェースを名称で呼び出すことができ
る。Idispatch::Invokeにより具体的なメソッドを呼ぶことができるようになっている。このように、IunknownとIdispatch
の両方をもつことをデュアルインターフェースという。

長々と書いたCOMインターフェースはプログラムそのものとは別にIDL(Interface Definition Language)で定義し、コン
パイルし、タイプライブラリとしてモジュール内に保管される。Delphiではこの作業が色濃く残っており、タイプライブラリーの編集はプログラムの作成
と別作業になっている。プログラマが具体的に「あー、COMを作っているんだな。」と感じる作業である。

モジュールのみをダウンロードして実行した場合、IDLの内容はレジストリに書かれる。このことを「自己登録機能」という。明確に登録だけさせたい場合はプログラムの後ろに/REGSERVERとつけて実行する。

以上よりCOMが最低限もたねばならないとされている機能は、説明したIunknownインターフェースをそなえることと、レジストリ自己登録機能である。ところがこれだけの知識でCOMを作成できないから苦労する。

 

その他にCOMで決められていること

先に説明をおいておいた、Iunknownインターフェースに必ずセットされるメソッドについて述べよう。COMは誰かが使用中か誰も使用していないかを
管理している。もし、誰も使用していないのであればメモリー内に存在する意味はない。逆に、誰かが使用しているのに勝手に終了してはならない。そのため、
AddrefとReleaseというインターフェースを必ず装備せねばならない。
ユーザーは使用するたびに、Addrefを呼び出し使用することを宣言し(内部では参照カウントを+1)、使用が終了するとReleaseを呼び出す(内部では参照カウントを-1)。誰も使用していなければ(参照カウントが0)終了する。

また、COMの名称はクラスIDと呼ばれこの世で唯一無二でなければならない。クラスID(GUID)は、128ビットのユニークな名称が自動的に付加さ
れる。COM_Serverなどという名称は、世界で誰かがたまたま同じものを作るかも知れない。そこで、時間とLANカードのIDなどにより、必ずユ
ニークな名称がつけられる。これが真の名前となる。DCOMなどの場合、このGUIDで呼び出さねばならないことがある。

 

COMのプログラムとしての振る舞いの規定

ところでCOMはインターフェースの定義であるといっても、オブジェクトの起動、停止をもアーキテクチャに含む。これらをコントロールするために、システムに存在するものがCOMライブラリーであり、COで始まるインターフェースをもつ。
あるCOMが最初に呼び出された場合、ClassFactoryと呼ばれるCOMサービスにより起動(CoCreateInstance)され、インターフェースへのポインターは作成される。ユーザーが誰もいなくなったとき、COMは破棄される。
      オブジェクトのクラス名をクライアントが要求すると、そのクラスをサポートするサーバーを起動する。サーバーは、 
      

  1. クライアントと同じプロセス内
     
  2. クライアントと同じマシン上、ただし、別プロセス
     
  3. ネットワークを利用した別のマシン上

      
に存在しうる。最後のような別マシン上のサーバーと通信できる機能を特に、DCOMという。別マシンとの通信にはRPC(Remote Procedure Call)が使用される。
DCOMでは異機種間でも問題なく動くように、データの形式が異なっていると修正する機能「マーシャリング」がなされるようになっている。例えば、ダブル
ワードのワード長をもっているプラットフォームと、ワード長のプラットフォームでは形式が違う。この差異を吸収するのが「マーシャリング」である。
DCOMでは本来、このマーシャリング機能をプロキシー(送り側)とスタブ(受け取り側)といわれるモジュールにインプリメントしなくてはならない。忘れ
てほしくないのだが、このようにすることでCOM自身がデータ変換を考慮しなくてよくなる。
しかも、実際のマイクロソフトの製品ではOLEVariantにしうる値(WideString,Integerなど)ならばデフォルトのマーシャリング機能がサポートする。

 

アパートメントモデル

最後にスレッドとCOMについて記述する。
スレッドはOSがCPU資源を与える管理単位である。つまりひとつのプロセス内部に複数のスレッドはありうる。例えば、MS Wordの画面をコントロールするスレッドとプリント結果をキューするスレッドは別である。
これらのスレッドが共存するためには、相互関係を意識したコードが必要となる。しかし、ほとんどのプログラム(例えばVBでなにげなく書いたプログラム)
がスレッドについて意識していることは期待できない。COMでは「アパートメントモデル」という考え方でスレッドに対応、対応していないプログラム共に管
理する。

まず複数スレッドなど気にしないプログラムはシングルスレッドアパートメント(STA)として管理する。これはひとつのスレッドにひとつのCOMオブジェ
クトが存在し、そのスレッド内のクライアントのみがオブジェクトのメソッドを呼べる。もし、要求が複数きた場合、要求はキューにいれられ、順々に処理され
る。
WindowNT4.0ではフリースレッドもしくはマルチスレッドアパートメントと呼ばれるひとつのCOMオブジェクトを複数のスレッドで同時にクライア
ントが使用できるようになった。ただし、プログラマーは複数のスレッドが同時に動くことを考慮(スレッドセーフ)であることを考慮せねばならない。

なお、ひとつのプロセス内にはMTAを使う場合は、ひとつのアパートメントだけで十分であるとする。なぜならばMTAは複
数のスレッドをサポートするからである。逆にSTAを複数動かすことができるところが、COMの素晴らしさであろう。このため何も考えないVBプログラム
が複数スレッドで動くことができるのである。

この後、COM+というものにかわった。これはCOMが出来上がったころはコンポーネントの管理をするのが任意だったのだが、必ずトランザクションサーバーに登録することになったところが変わったようだ。
すでにCOMを仕事としていないので、この部分はウソがあるかもしれない。

さらに.net(ドットネット)では、COMのレジストリ機能を一部あきらめた。Windows3.1のころのモジュール探索が復活している。おそ
らくあらゆるライブラリーをレジストリーに登録すると、場所の移動もままならない状況が多くのユーザーから不満として出たんじゃないだろうか。

 

Dcomサンプルコード

サーバ側

クライアント側

Category: Windows  | Leave a Comment
Perl定石 4月 28

CGIがなんであるか、とか、Perlの基本とか、そういうのは他のサイトや本にいっぱいあると思います。ここでは、「私はこういう風にコーディングしてる。」というパターンを書いておきます。

実は私はPerlは大嫌いです。読みづらいし、変数がどこからでも参照可能なグローバル変数が基本というのは、プログラマーとしては気が狂いそうなスペックです。「文字列の扱いが簡単」というだけでここまで流行るというのはどうかとも思います。

が、インターネットプロバイダが用意する言語環境ってPerlくらいですから、仕方ないですね。早くJavaを用意してくれないかなぁ。

まあ、気を取り直してメモっておきましょう。

Perlはメインから代表的なルーチンを呼ぶというパターンを取るのが普通です。

  1. フォームのデコード
  2. 日付処理
  3. Cookieの書き込み
  4. Cookieの読み取り
  5. スケルトンHTMLの利用
  6. メイルの送出

これらをひとつのサブルーチン集とします。全体図はこちらをご覧ください。上で触れていないのは共通変数の初期化がふくまれているところと、最も最後に1を置いているところです。


フォームのデコード

#--------------------------------------------------------------
#? ?フォームからのデータを読み取るルーチン
#? ? 例: if ($in{'NAME'} eq '')
#--------------------------------------------------------------
sub read_form {?
? my($method, $accoc) = @_;
? my($buffer,$pair,@pairs,$key,$val);
? if ($ENV{'REQUEST_METHOD'} eq "POST" && ($method eq 'POST' ||
? !defined $method)) {
? ?? ?? read(STDIN,$buffer,$ENV{'CONTENT_LENGTH'});
? ?? ?? @pairs = split(/&/,$buffer);
? } elsif ($ENV{'REQUEST_METHOD'} eq "GET" && ($method eq 'GET' ||
? ?!defined $method)) {
? ? @pairs = split(/&/, $ENV{'QUERY_STRING'});
? }
? ?? ?? foreach $pair (@pairs) {

? ?? ?? ?? ?? ? ($key,$val) = split(/=/,$pair);
? ?? ?? ?? ?? ? $key =~ tr/+/ /;
? ?? ?? ?? ?? ? $key =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
? ?? ?? ?? ?? ? $val =~ tr/+/ /;
? ?? ?? ?? ?? ? $val =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;

? ?? ?? ?? ?? ? &jcode'h2z_sjis(*val); # 半角カナ→全角(SJIS)変換
? ?? ?? ?? ?? ? &jcode'convert(*val,'sjis'); # SJIS変換

? ?? ?? ?? ?? ? $val =~ s/\t//g; # タブコードを無効
? ?? ?? ?? ?? ? $val =~ s/\r\n//g; # Win → Mac (文中の改行はCRとする)
? ?? ?? ?? ?? ? $val =~ s/\n//g; # Unix → Mac

? ?? ?? ?? ?? ? $val =~ s/&/&/g; # タグ禁止
? ?? ?? ?? ?? ? $val =~ s/"/"/g;
? ?? ?? ?? ?? ? $val =~ s/</&lt;/g;
? ?? ?? ?? ?? ? $val =~ s/>/&gt;/g;
? ?? ?? ?? ?? ? $in{$key} = $val; # 入力データは%inへ
? ?? ?? }
}
はい、これです。$in{‘キーワード’}にフォームで定義した値がはいってきます。
タブコードを無効というように、これからCGIプログラムの中で特別な役割で使う文字は無視するようにします。

日付の形式変換

#----------------------------------------------------------------
#? ?? ? 日付けの形式化
#? ?? 例: $reg_date? ?= &format_date($reg_date)
#----------------------------------------------------------------
sub format_date {

? ? local($_) = @_;
? ? local($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime($_);
? ? $mon++;
? ? $min? = "0$min"? if $min? < 10;
? ? $hour = "0$hour" if $hour < 10;
? ? $sec? = "0$sec"? if $sec? < 10;
? ? $mday = "0$mday" if $mday < 10;
? ? $year += 1900;
? ? local($week) = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat")[$wday];
? ? $_? ? = "$year/$mon/$mday($week) $hour:$min";
? ? $_;
}
入力に任意の日付を指定できますが、今の時間を入手するには&format_date(time)です。

Cookieのセット

#--------------------------------------------------------------
#? ? Cookieをセット
# 例: &set_cookie()
# $mycookie ='BMG'? ?# CookieのIDを共通変数としてセット
#--------------------------------------------------------------
sub set_cookie {
? ?? ?? my($text) = "id\:$id\,password\:$password";

#Expiration Dateの計算
? ?? ?? ($secg,$ming,$hourg,$mdayg,$mong,$yearg,$wdayg,$ydayg,$isdstg) = gmtime(time + $expday*24*60*60);
? ?? ?? $y0="Sunday"; $y1="Monday"; $y2="Tuesday"; $y3="Wednesday"; $y4="Thursday"; $y5="Friday"; $y6="Saturday";
? ?? ?? $m0="Jan"; $m1="Feb"; $m2="Mar"; $m3="Apr"; $m4="May"; $m5="Jun"; $m6="Jul"; $m7="Aug"; $m8="Sep"; $m9="Oct"; $m10="Nov"; $m11="Dec";
? ?? ?? @youbi = ($y0,$y1,$y2,$y3,$y4,$y5,$y6);
? ?? ?? @monthg = ($m0,$m1,$m2,$m3,$m4,$m5,$m6,$m7,$m8,$m9,$m10,$m11);
? ?? ?? $date_gmt = sprintf("%s\, %02d\-%s\-%04d %02d:%02d:%02d GMT",$youbi[$wdayg],$mdayg,$monthg[$mong],$yearg +1900,$hourg,$ming,$secg);

? ?? ?? print "Set-Cookie: $mycookie=$text; ", "expires=", $date_gmt, "; \n";

}
まず、有効期間を所定の形式にします。Cookieの書き込み自身は最後のPRINT文でできるのですが、クッキーのテキストは’キーワード=値’の形式ひとつです。

したがって、値の中をさらに’キーワード:値,キーワード:値’というようにします。それをやっているのが冒頭の$textをセットしているところです。


Cookieの読み取り

#--------------------------------------------------------------
#? ? Cookieを得る
# 例: &get_cookies()
#--------------------------------------------------------------
sub get_cookie {
? ?? ?? my($cookies) = $ENV{'HTTP_COOKIE'}; # 入力
? ?? ?? my(@pairs) = split(/;/,$cookies); 

? ?? ?? # データの展開? 項目名1:内容1,項目名2:内容2,...
? ?? ?? foreach $pair (@pairs) {

? ?? ?? ?? ?? ? ($key,$val) = split(/=/,$pair,2);
? ?? ?? ?? ?? ? $key =~ s/ //g;

? ?? ?? ?? ?? ? if ($key eq $mycookie) {

? ?? ?? ?? ?? ?? ?? ?? ?@pairs = split(/,/,$val);
? ?? ?? ?? ?? ?? ?? ?? ?foreach $pair (@pairs) {

? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ($key,$val) = split(/:/,$pair,2);
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? $COOKIE{$key} = $val;
? ?? ?? ?? ?? ?? ?? ?? ?}
? ?? ?? ?? ?? ?? ?? ?? ?last;
? ?? ?? ?? ?? ? }
? ?? ?? }
}
Cookieを読み取るのは$ENVを見ればいいことになっています。
面倒なのは、それの展開です。フォームのデコードと同じ要領です。

結果は、
$COOKIE{‘キーワード’}にはいってきます。


スケルトンHTMLの書き出し

#---------------------------------------------------------------
#? ?? ?? ?? ?HTMLスケルトンファイルを標準出力に書き出す
# 引数? ?? | $_:スケルトンファイル名
#? ?? ?? ?? ?
# 戻り値? ?| ない
#---------------------------------------------------------------
sub skelton_write {
? open(FH,"<@_") or error('Open error',@_);
? while (<FH>) {
? ? s/__%(.+?)%__/$htsvals{$1}/g;
? ? print;
? }
}
$htsvalsというハッシュ関数にキーワードと値をセットしておきます。仮に$htsvals{‘id’}=’TAKAO’
$htsvals{‘password’}=’stynee’としたとしましょう。

入力ファイルにHTMLのスケルトンを用意します。例えば、次のようなものです。

<HTML>

IDは__%id%__<BR>

パスワードは__%password%__<BR>

</HTML>

このファイルを入力とすることでhtsvalsに定義された変数でhtmlが埋められて書き出されます。

HTMLの装飾とCGIを切り離すことができ、メンテナンスが大変ラクです。


メイルの送信

#--------------------------------------------------------------
#? ?メイル送り出し
#
# $sendmail = '/usr/bin/sendmail'? ?を共通変数としてセット
#--------------------------------------------------------------
sub sendmail {
? ? local($mailto, $from, $subject, $mail_data) = @_;

? ? $subject =~ s/&amp;/&/g;
? ? $subject =~ s/&quot;/"/g;
? ? $subject =~ s/&gt;/>/g;
? ? $subject =~ s/&lt;/</g;
? ? &jcode'convert(*subject,'jis');
? ? &jcode'convert(*mail_data,'jis');
? ? $subject = &enc_b64($subject);

? ? open(SEND, "| $sendmail -t $mailto")
? ?? ?? ?|| &error("Can\'t send mail to $mailto: $!");

? ? print SEND "X-Mailer: $prod_name v$version $a_email\n";
? ? print SEND "To: $mailto\n";

? ? print SEND "From: $from\n";
? ? print SEND "Subject: $subject\n";
? ? print SEND "Content-Transfer-Encoding: 7bit\n";
? ? print SEND "Content-Type: text/plain; charset=iso-2022-jp\n\n";
? ? print SEND $mail_data;

? ? close(SEND);

}

sub enc_b64 {
? ? my($subject) = @_;
? ? my($str, $padding);
? ? while ($subject =~ /(.{1,45})/gs) {
? ?? ?? $str .= substr(pack('u', $1), 1);
? ?? ?? chop($str);
? ? }
? ? $str =~ tr|` -_|AA-Za-z0-9+/|;
? ? $padding = (3 - length($subject) % 3) % 3;
? ? $str =~ s/.{$padding}$/'=' x $padding/e if $padding;

? ? "=?ISO-2022-JP?B?$str?=";
}
必要なパラメータは一行目を見ていただくといいと思います。$sendmailはシステムによってSENDMAILの置き場所が違うので共通変数でセットします。

[戻る]