秋月 巌 AKIZUKI,Iwao
| Linux の勢い |
|---|
前々号(本誌99年4月号)でLinux版のSybase Adaptive Serverの紹介をしたが、それから2ヶ月の間にLinuxを巡る状況は大きく進展した。飛ぶ鳥を落とす勢いだといっていいだろう。
中でも私がもっとも重要だと思うのは、IBMがLinuxの正式なサポートを表明したことである。Linux上で動作する製品を出すベンダーが増えるということは、Linuxが正常に動作することを保証するベンダーが増えるということでもある。
| 誰が責任をもつか? |
|---|
この辺は微妙な問題なのだが、たとえばOSの障害のためにあるソフトウエアが動かないとしても、ベンダーはそれをOSベンダーの責任に転嫁することができない。ユーザーからみれば、そのOS上で動作することを期待して購入したのだから当然である。結局ソフトウェアベンダーはOSのその障害を回避する形でプログラミングすることになる。
しかし現実的には、Windowsのようにインストールされているアプリケーションによって環境の無限の組み合わせが考えられる状況では、すべての環境で同じように動作するプログラム(とくにインストーラ)を開発することは誰にもできない。そのためソフトウェアベンダーは“できる限り多くの環境で動作する”という最大公約数的な方法をとり、ユーザーがそれに仕方なく納得しているというのが現状ではないだろうか。
| 払拭された問題点 |
|---|
それでも、何かに対して責任をもつ人が参加するということは重要である。IBMは改修したソースコードをLinuxコミニュティに還元することを明言している。OSの障害を回避するためにトリッキーな方法を使うよりは、OS自身を修正できる方が問題が簡単に解決できる場合も多いだろう。
もちろん、Linuxに商業ベンダーが参加する以前からLinuxにはコミニュティがあり、彼らが責任をもって品質を確保してきたことは知っている。しかし、Microsoft幹部の言い分ではないが、やはり、商業ベンダーが契約に基づいて、品質を保証するということが大切だと私は思っている。そしてその役目を担ってきたのが、各種ディストリビューションベンダーなわけである。それに加えてアプリケーションベンダーが加わり、彼らがOSのソースコードに触れられるという事実を考えれば、Linuxにはサポートがない、という状況は払拭されたということができる。
| 嘘から出た誠 |
|---|
断っておくが、私はLinuxを推進する立場の人間ではない。歓迎はしているが、新しいものをサポートすることは骨が折れることだし、私自身はここ数年、Windows NTにフォーカスしてきたのである。今さら新しいノウハウを蓄積したいとは、あまり、考えない。
しかしこの勢いである。しかも米国のインターネット関連企業株は暴騰しきっており、成長に必要な資金は潤沢である。無視することはできない。それでこの2ヶ月間はLinux関係の情報に敏感に接してきた。米Microsoftの米司法省との裁判における情勢をみても、時代が急旋回していることは事実である。米Microsoftは、自分たちには競合相手が多く、競争が強いられていることを裁判で強調しているが、裁判中に本当にそのような情勢になってきたことは皮肉である。それは嘘から出た誠なのか、最初から真実だったのか。おそらく、後者だろう。彼らの主張は笑い種のように報じられたが、狼少年の最後の悲鳴が真実であったように、誰も気づかないうちに彼らも真実を語っていたのである。
| Linuxはなぜ、Windows NTよりも優れているか? |
|---|
さて、LinuxはWindows NTと比較して、何が優れているのだろうか? この解答は、Linuxは十徳ナイフではないということに尽きる。
十徳ナイフとは、本誌のコラム「電脳十牛図」で高根氏がWindowsを表現して使った形容詞である。氏は夢のない表現と反省していたが、現在のWindowsの特徴を的確に表現している。もっとも十徳ナイフといっても、持ち運びに不便があるくらいに重い十徳ナイフである。
現在のWindowsのマルチメディアまわりの重装備は、業務用のアプリケーションを運用する上で無意味なものが多い。重装備によって製品の安定性は犠牲になり、またプラットフォームの均質性も損なわれる。だいたいMicrosoftのシステムやWindows上で動作するアプリケーションは、多少問題があっても、ないよりはあった方が便利、という基準で提供されてきたものが多い。そして、実際あった方がないよりもずっと助かることが多いので、つい使ってしまうのである。
| しかし、巷間の評価は疑う必要もある |
|---|
だから、Linuxが安定していて、Windows NTが不安定であるという評価には、多少、下駄をはかせてもいい。
私自身、日頃Windows NT 4.0を使用していて、あまりにもトラブルが多く憂鬱な思いをしているが、トラブルの直接の原因がOSであることは全体の数分の一である。もっとも、問題のあるアプリケーションや周辺機器を認めてしまう仕様に問題があるのだ、ということはできるだろう。
また、Linuxを使用するユーザーの平均的なスキルとWindows NTを使用するユーザーの平均との差も考慮する必要がある。
日経Windows NT誌が「NTの迷信を斬る」という特集で取材していたが、機能を限定して使用する限りWindows NTは意外(本当に意外である)と安定してるという。だから、十徳ナイフとしてのWindows NTと専用ナイフとしてのLinuxを比較するのではなく、十徳ナイフの「ナイフ」だけを使ったWindows NTとLinuxを比較する必要がある。
| そしてLinuxの限界 |
|---|
これは別の意味では、クライアントOSとしてのLinuxに、限界があることを示している。十徳ナイフとしてのLinuxを、まだ市場は要求していない。サーバーというと立派なイメージがあるが、サーバーはクライアントに比べると基本的には仕事の種類が少ないのだ。ただ、安定して大量のシーケンシャルな動作を行なう必要があるだけである。
一部で言われているように、「Linuxが無料だからLinuxがWindows NTに対してマーケティング的に有利だ」というのはあまり意味がない。現在のTCOの中で、パッケージソフトウェアの価格の占める割合はそれほど高いわけではない。また、ディストリビューションベンダーの製品は有料だし、Windowsを無料で使用している人も大勢いるのである。
わずかなネットワーク設定を変えるたびにOSの再起動を要求し、しかも再起動に永遠の半分くらいの時間がかかるOSに1時間何千円のエンジニアが同伴しなければならない、そのコストにこそ問題があるのである。そう、Windows NTの最大の欠点は、その起動時間にあるといってもいい。多少不安定だろうが、メモリリークがあろうが、瞬時で再起動するならば、問題は半分になる。
| SybaseのAdaptive Server Enterpriseはいつまで無料なのだろう? |
|---|
ところで、以前紹介したLinux用の日本語版Adaptive Server Enterpriseの新しいバージョン(11.9.2)が有償で発売されている。無償で公開されているのはAdaptive Server Enterprise 11.0.3で、日本語の文字コードに対応した英語版である。
新しいバージョンが有償化されると、以前のバージョンの無償配布をしなくなるのではないかと心配していたのだが、継続するようなので、とても、うれしい。というのは、ダウンロード時に登録が必要なので、私が顧客や読者にすすめた時点で無償配布が行なわれていないと、顧客は有償製品を購入しないといけない。私は別に自分が使うためにローコストなデータベースサーバーを探しているわけではないのである。これで、安心してサポートすることができる。
| 旧バージョンでも問題がない |
|---|
データベースサーバーのコアの部分はすでに進歩していないので旧バージョンでも問題はないし、また、サーバーアプリケーションは文字コードが日本語に対応していれば、ツール類が日本語化されていなくても大きな問題ではない。もちろん、最新の機能や日本語管理ツールがほしいならば、有償バージョンを利用すればいい。
スキルや利便性に応じて価格を選択できるということはすばらしいことだ。先日発表されたデータベースベンダー別のシェアで、Sybaseは後退していたが、このようなビジネスモデルを用意できるならば将来は明るいはずだ。もっとも、マーケットは消費者に有利なものが生き残るほど利口ではないということもできるが。
| Transact SQLの互換性がもたらすMicrosoft SQL Serverクロスプラットフォーム |
|---|
Sybaseのデータベースは概して優れているが、有償の製品を選ぶならばシェアを考えてOracleを選択するのが妥当だと思う。また、私がSybaseにこだわるのは、SQLの拡張言語であるTransact SQLが、Microsoft SQL Serverと高い互換性をもつからである。つまり、ストアドプロシージャを共有することができる。もちろん最新の機能は互換性がないが、最新の機能とは言いかえれば、それまでなくても困らなかった機能のことなのである。つまり、Microsoft SQL Serverのプラットフォーム依存という問題を、Sybase Adaptive Serverは解決するのである。
この連載はローコストでユーズフルなクライアント/サーバーシステム開発を目的としている。私はこの問題で日々頭を悩ませているのである。Linux版のSybase Adaptive Serverは、この目的の究極的な解答だということもできるので、多少、話がそれたことを許していただきたい。きっと、近いうちにWindowsのVisual Basicクライアントアプリケーションから、LinuxのSybase Adaptive Serverにアクセスする方法を紹介できる日がくるだろう。
| Recordsetコントロールの実装 |
|---|
ConnectionコントロールとRecordsetコントロールによって構成されるHyper SQLdb Client Library(図1)の解説も今回で3回目になる。
今回解説するRecordsetコントロール(図2、記事末リスト3参照)には2つの使い道がある。まず、任意の文字列を引数に指定し、MakeRecordsetメソッドを使用してRecordsetオブジェクトを作成する方法である。
この方法は汎用的に使用することができ、用途はDB Serverへのデータベースアクセスに限らない。たとえば、ある任意のデータソース(たとえばCSVファイル)があり、それを文字列として読みこむことが可能だとする。読み込み時にRecordsetコントロールのフォーマット形式に整形し、MakeRecordsetメソッドの引数に指定することで、Recordsetオブジェクトが完成する。もちろん、データ連結コントロールと連結することもできる。
MakeRecordsetメソッドはHyper SQLdb Client Libraryとして実装される以前にも使用していたActiveX DLLを移植したものである。この実装方法は本連載の98年12月号以降で解説しているほか、PCDNのWebページ「翔泳社VB Magazine & DDJJライブラリ」(http://pcdn.int21.co.jp/pcdn/vb/noriolib/vbmag/9812/db_solu/)にも掲載されているので参考にしていただきたい。
図1:Hyper SQLdb Client Library![]() | 図2:Recordsetコントロールのデザイン画面![]() |
| Connectionコントロールとの連携 |
|---|
もうひとつの使い方は、Connectionコントロールと連携して使用する方法である。これこそがRecordsetコントロールを使用する本来の姿であり、Type9 DB Server専用のクライアントコンポーネントである。Connectionコントロールと連携するには、次のような処理を記述する。
Dim WithEvents cn As ConnectionDim WithEvents rs As RecordsetPrivate Sub cmdConn_Click() Set cn = Controls.Add("DBCliLib.Connection", "control1") cn.ServerName = "localhost" cn.ServerPort = 1010 cn.cnOpen "BIBLIO"End SubPrivate Sub cmdSend_Click() Set rs = Controls.Add("DBCliLib.Recordset", "control2") rs.rsOpen "SELECT * FROM Authors WHERE Author like 'a%'", cnEnd SubPrivate Sub rs_RecordsetComplete() Set DataGrid1.DataSource = rsEnd Sub |
| 引数に渡されたオブジェクトのメソッドを利用する |
|---|
注目してほしいのは、rsOpenメソッドの実行時である。ここでは最初の引数にSQLのSELECT文、2番目の引数にConnectionコントロールを指定している。この構文はADOのRecordsetオブジェクトのOpenメソッドと同様である。
rsOpenメソッドの実装部は、次のように1行のコードで完結している。
Public Sub rsOpen( _ Optional Source As Variant, _ Optional ActiveConnection As Object, _ Optional CursorType As Long, _ Optional LockType As Long, _ Optional Options As Long) ActiveConnection.SelectRequest Source, MeEnd Subこのプロシージャの唯一のコードでは、2番目の引数で受け取ったConnectionコントロールのSelectRequestメソッドを使用している。最初の引数にはRecordsetコントロールのrsOpenメソッドで、ひとつめの引数に指定されたSQL文を指定し、次の引数では自分自身、つまり、Recordsetコントロールを指定している。
Public Sub SelectRequest( _ SqlStatement As Variant, _ RecordsetObj As Object) Set rsObjR = RecordsetObj wscConnect.SendData SqlStatementEnd Sub引数として受け取ったRecordsetコントロールをモジュールレベルの変数に格納してから、初めてSQL文がサーバーに送信される。
rsObjR.MakeRecordset resultSetこのようにひとつのRecordsetオブジェクトを作成するためにConnectionコントロールとRecordsetコントロールが互いのメソッドを呼び出しあう。これはADOのRecordsetオブジェクトのメソッドを模倣するためにとったスタイルだが、アーキテクチャとして優れているかどうかは判断が難しいところである。少なくとも実装側からみれば混乱しかねない構造だが、使う側からみればレコードセットの作成をRecordsetコントロールのメソッドで実行できるのは理にかなっている。
Private Sub wscConnect_DataArrival(ByVal bytesTotal As Long) Dim strData As String ' DBsvr(ActiveX EXE)からデータを受け取る wscConnect.GetData strData, vbString resultSet = resultSet & strData If InStr(resultSet, "Result Start") > 0 And InStr(resultSet, "Result End") > 0 Then ' テキスト(resultSet)を元にレコードセットを作成 rsObjR.MakeRecordset resultSet resultSet = "" End IfEnd Sub |
| レコードの移動メソッド |
|---|
Recordsetコントロールが公開する以下のMove...メソッドが、ADOのRecordsetオブジェクトのラッパーであることは一目瞭然である。
Public Sub MoveFirst() dsRecordset.MoveFirstEnd SubPublic Sub MoveNext() dsRecordset.MoveNextEnd SubPublic Property Get EOF() As Boolean EOF = dsRecordset.EOFEnd PropertyPublic Property Get BOF() As Boolean BOF = dsRecordset.BOFEnd Propertyこれにより、RecordsetコンポーネントはADOのRecordsetオブジェクトと同様のプログラミング方法でレコードポインタを操作することができる。
| Fieldsコレクションの参照 |
|---|
次のプロパティ関数もADOのFieldsコレクションをラップしている。
Public Property Get Fields(Index As Long) _ As ADOR.Field Set Fields = dsRecordset.Fields(Index)End Property結果的にこのプロパティにインデックス番号を指定して参照することで、ADOのRecordsetオブジェクトの各Fieldオブジェクトを扱うことができる。
| まとめ |
|---|
多少動作は複雑になったが、結果的には、うまくADOのアーキテクチャを模倣することができた。作り始めたときは、無理かなと思ったりしたが、工夫することでほとんどの問題が解決できた。実はこれを作ったことで、Visual Basicを見直してしまったのである
Visual Basicのアーキテクチャは洗練されているとは言いがたいが、COMサポートのために上手に拡張されたということはできると思う。コンポーネントのアーキテクチャとしてCOMは十分に複雑だが、Visual Basicはそれを上手に隠蔽している。細かい制御ができないと不満の人もいるかもしれないが、Visual Basicはやはりこれでいいと思う。細かい制御ができるために手間がかかったり、動作が不安定になるよりは、とりあえずでも動く方がいい。
もっとも、最終的にどうしようもなくなったとき、逃げ場がないのもVisual Basicである。しかし、それはWindowsプラットフォームをターゲットにしている限り、どの開発ツールを使っても解決できないのである。
Option ExplicitPrivate CPOS As String ' 区切り記号Private CPOSlen As Integer ' 区切り記号の長さPrivate dsRecordset As ADOR.Recordset' 既定のプロパティ値:Const m_def_RecordCount = 0Const m_def_Fields = 0Const m_def_EOF = 0Const m_def_BOF = 0' プロパティ変数:Dim m_RecordCount As LongDim m_Fields As VariantDim m_EOF As BooleanDim m_BOF As Boolean' イベント宣言:Event RecordsetComplete()Public Sub rsOpen( _ Optional Source As Variant, Optional ActiveConnection As Object, _ Optional CursorType As Long, Optional LockType As Long, Optional Options As Long) ActiveConnection.SelectRequest Source, MeEnd SubPublic Sub MakeRecordset(RecordsetString As String) Dim fld As ADOR.Field Dim strRow As String Dim strField As String Dim lngCurPointer As Long Dim CurRecordLen As Integer Dim intFieldStartPos As Integer Dim intFieldNextPos As Integer Dim i As Integer CPOS = Chr(9) CPOSlen = Len(CPOS) ' フィールド情報の先頭に移動 Set dsRecordset = New ADOR.Recordset lngCurPointer = InStr(RecordsetString, "Field information") Do While True 'intPosRow > 0 ' 処理する行の先頭ポインタを取得 lngCurPointer = InStr(lngCurPointer, RecordsetString, vbCrLf) + 2 ' 処理する行の長さを取得 CurRecordLen = InStr(lngCurPointer, RecordsetString, vbCrLf) - lngCurPointer ' 処理する行を取得 strRow = Mid(RecordsetString, lngCurPointer, CurRecordLen) ' フィールド情報の最後ならば、ループを終了 If Left(strRow, 21) = "End field information" Then Exit Do End If intFieldNextPos = 1 Dim AppendParamName As String Dim AppendParamType As String Dim AppendParamSize As String For i = 1 To 3 intFieldStartPos = intFieldNextPos ' 区切り記号が見つかったら、左側がフィールドの値 intFieldNextPos = InStr(intFieldStartPos, strRow, CPOS) + CPOSlen If intFieldNextPos <> 0 + CPOSlen Then ' 値を strField 変数に割り当てます。 strField = Mid(strRow, intFieldStartPos, _ intFieldNextPos - intFieldStartPos - CPOSlen) 'Left(strRow, intPos - 1) Else ' 区切り記号が見つからなければ、最後のフィールドです。 strField = Mid(strRow, intFieldStartPos, Len(strField) - CPOSlen + 1) End If Select Case i Case 1 AppendParamName = strField Case 2 AppendParamType = Val(AppendParamType) Case 3 AppendParamSize = Val(strField) End Select Next dsRecordset.Fields.Append AppendParamName, adLongVarChar, _ AppendParamSize, adFldUpdatable 'adFldRowID Loop dsRecordset.CursorType = adOpenKeyset dsRecordset.LockType = adLockOptimistic dsRecordset.Open ' レコードのスタート位置にポインタを移動 lngCurPointer = InStr(RecordsetString, "Result Start") Do While True 'intPosRow > 0 ' 処理するレコードの先頭ポインタを取得 lngCurPointer = InStr(lngCurPointer, RecordsetString, vbCrLf) + 2 ' 処理するレコードの長さを取得 CurRecordLen = InStr(lngCurPointer, RecordsetString, vbCrLf) - lngCurPointer ' 処理するレコードを取得 strRow = Mid(RecordsetString, lngCurPointer, CurRecordLen) If Left(strRow, 10) = "Result End" Then ' Modi Exit Do End If ' 新規レコードを追加 dsRecordset.AddNew intFieldNextPos = 1 For Each fld In dsRecordset.Fields intFieldStartPos = intFieldNextPos ' 区切り記号が見つかったら、左側がフィールドの値 intFieldNextPos = InStr(intFieldStartPos, strRow, CPOS) + CPOSlen If intFieldNextPos <> 0 + CPOSlen Then ' 値を strField 変数に割り当てます。 strField = Mid(strRow, intFieldStartPos, _ intFieldNextPos - intFieldStartPos - CPOSlen) 'Left(strRow, intPos - 1) Else ' 区切り記号が見つからなければ、最後のフィールドです。 strField = Mid(strRow, intFieldStartPos, Len(strField) - CPOSlen + 1) End If If fld.Type = 17 Or fld.Type = 18 Or _ fld.Type = 19 Or fld.Type = 16 Or _ fld.Type = 2 Or fld.Type = 4 Or _ fld.Type = 131 Or fld.Type = 3 Or _ fld.Type = 5 Then ' 数値型の場合数値に変換 fld.Value = Val(strField) Else fld.Value = strField End If Next dsRecordset.Update dsRecordset.MoveFirst Loop RaiseEvent RecordsetCompleteEnd SubPrivate Sub UserControl_GetDataMember(DataMember As String, Data As Object) Set Data = dsRecordsetEnd SubPublic Sub MoveFirst() dsRecordset.MoveFirstEnd SubPublic Sub MoveNext() dsRecordset.MoveNextEnd SubPublic Property Get EOF() As Boolean EOF = dsRecordset.EOFEnd PropertyPublic Property Get BOF() As Boolean BOF = dsRecordset.BOFEnd Property' ユーザーコントロールのプロパティを初期化しますPrivate Sub UserControl_InitProperties() m_EOF = m_def_EOF m_BOF = m_def_BOF m_RecordCount = m_def_RecordCount m_Fields = m_def_FieldsEnd Sub' 記憶領域からプロパティ値を読み込みますPrivate Sub UserControl_ReadProperties(PropBag As PropertyBag) m_EOF = PropBag.ReadProperty("EOF", m_def_EOF) m_BOF = PropBag.ReadProperty("BOF", m_def_BOF) m_RecordCount = PropBag.ReadProperty("RecordCount", m_def_RecordCount) m_Fields = PropBag.ReadProperty("Fields", m_def_Fields)End Sub' 記憶領域にプロパティ値を書き込みますPrivate Sub UserControl_WriteProperties(PropBag As PropertyBag) Call PropBag.WriteProperty("EOF", m_EOF, m_def_EOF) Call PropBag.WriteProperty("BOF", m_BOF, m_def_BOF) Call PropBag.WriteProperty("RecordCount", m_RecordCount, m_def_RecordCount) Call PropBag.WriteProperty("Fields", m_Fields, m_def_Fields)End SubPublic Property Get RecordCount() As Long RecordCount = dsRecordset.RecordCountEnd PropertyPublic Property Get Fields(Index As Long) As ADOR.Field Set Fields = dsRecordset.Fields(Index)End PropertyPublic Property Get State() As Long State = dsRecordset.StateEnd PropertyPublic Sub MoveLast() dsRecordset.MoveLastEnd SubPublic Sub MovePrevious() dsRecordset.MovePreviousEnd SubPublic Sub rsClose() dsRecordset.CloseEnd Sub |