秋月巌ソリューション事務所
秋月 巌 AKIZUKI,Iwao
| 今回からVisual Basic 6.0を使用する.といっても,リリース版ではなくプレリリース版である.原稿執筆時に製品版の入手も不可能ではなかったのだが,とりあえず手元にあるこのプレリリース版を利用する.出版社まで製品を取りにいく時間がないのである. Visual Basic 6.0のバージョンアップの詳細については本誌でもとりあげられている.大きなトピックとしてはDHTMLアプリケーション開発のサポートと,OLE DBを中心としたUniversal Data Accessのサポートがあげられる. どちらも,たいしたトピックではない.DHTMLはWebアプリケーションを作らないユーザーには関係がないし,Webアプリケーションの開発者にとっても,JavaやActiveXほどの柔軟性はない.Internet Explorer 4.0でしか動作しない閉鎖性も大きな問題である. ADOに集約されるOLE DBのサポートも,本質的には進化ということができるのだが,現在のADOのサポートレベルでは,既存のデータアクセスコンポーネントであるRDOの方がより現実的な選択だということができる. その他,新しく作成が可能になったIISアプリケーションは,ASPとサーバー側のActiveXコンポーネントなどの従来のテクノロジーによっても同等のものを開発することができる.IISアプリケーション自体が,ASPとActiveXコンポーネントなのだから当然である. つまり,今回のバージョンアップは,Visual Basic 5.0ですでに可能だったアプリケーション開発に,新しい選択肢を提供したということにとどまる.Visual Basic 2.0から4.0にバージョンアップしたときにはデータベースアクセスという魅力的なアイテムが付与されたし,4.0から5.0にかけてはActiveXコンポーネントの開発機能とネイティブコンパイラの搭載という本質的な機能アップが用意されていた. |
| もちろん,ひとつの結果を得るために複数の方法を提供することは,プログラム開発環境の重要なバージョンアップ要件である.Microsoftが提供するプログラミング方法が,その不安定な実装レベルにもかかわらず,今日まで何とか実用に耐えてきたのは,同じことを実現するために,常に複数の方法を用意されてきたからである.正常に動作するはずの方法が実用にならず,代替えの方法を検討した経験は多い.もちろんこういったプログラミングの記述に関する柔軟性は,チーム開発における標準化の妨げになる.それでも現実的な問題として,単一の方法しか用意されなかったとしたら,Visual Basicのような高水準なツールを使ったWindowsシステム開発は,必要にして十分な品質を維持できないだろう. |
| しかし,今回のバージョンアップが魅力の少ないものになってしまったのは,新しく追加された選択肢が,新規に規定されたアーキテクチャに応えるためだけのものに過ぎないからである.ユーザーはより高速で高機能なデータベースアクセスを要求しているのであって,OLE DBのような汎用的なデータアクセスアーキテクチャを望んでいるわけではない.それと同様にユーザーは強力なWebアプリケーションの開発を望んでいるのであって,DHTMLという新しい方法に魅力を感じているわけではない. 開発者がツールのバージョンアップに対して望むのは,何よりも,今までに作れなかったアプリケーションが作れるようになることである.あるいは,より性能のいいアプリケーションが少ない手間で作成できるようになることである.そのような要望をVisual Basic 6.0は満たしていない.ADOにしてもDHTMLにしても,パワーとしては現時点のところRDOやActiveXコントロールに劣るのである. |
| しかし,DHTMLはともかく,ADO−OLE DBがアーキテクチャとしてRDO−ODBCに劣るわけではない.それは現時点での実装レベルが旧アーキテクチャの完成度に及ばないということに過ぎない.つまり,Visual Basic 6.0が魅力に欠けるのは,過渡期の製品だからだということができる.ならば,より洗練されたADOが搭載されるはずのVisual Basic 7.0に期待することにしよう. Visual Basic 6.0を中心にVisual Studio 97は,最近のMicrosoftを象徴しているプロダクトだということができる.新しいアーキテクチャを志向し,実用性をある程度犠牲にしている.これは実用性のためにアーキテクチャなど考慮しなかった,従来の同社の方針とは大きく異なっている.今までは,それがMicrosoft製品の魅力だったわけだから,大きく方針の変更をしたことになる.これはプロダクトが複雑化してくるにあたって,今までのような行き当たりばったりの拡張限界がきたからだろう.最近のMicrosoftの製品をみていると,学者によってアーキテクチャの再構築を図っていることがよくわかる.しかし,Microsoftのような市場中心の文化をもつ企業に,アカデミックなアーキテクチャがどれだけ馴染むかは疑問である. それでも,最近のMicrosoftが彼らなりに努力していることは確かだし,その成果はよい方向に出ているように思う.またMicrosoftにとっても,市場を牽引し,デファクトスタンダードの立場を維持するのに,他の方法がないことも確かである.Visual Studioの次のバージョンとWindows NT 5.0に期待しよう.もちろん,過大な期待は禁物である. |
| さて他の機能はさておき,Visual Basic 6.0で何よりも興味深いのはOLE DBのサポートである.OLE DBは雑誌等で何度も紹介されてきたが,今ひとつそのメリットが理解できなかった.今回Visual Basic 6.0に添付された資料を見ていると,この新しいアーキテクチャが確かにある可能性を提供していることがわかる. 従来,データベースアプリケーションというと,市販のデータベースエンジンにアクセスするためのアプリケーションが基本だった.多くの開発者にとって,今後もそれは変わらないだろう.それは,現時点において,リレーショナルデータベースがデータを格納するためのもっとも現実的な方式だからである. OLE DBが提供する最大の可能性は,データサーバーを開発する能力である.しかも,クライアントからのアクセス方法は標準化されているので,クライアントアプリケーションを作成するために新しいスキルを習得する必要は比較的少ない. |
| この場合のデータサーバーがアクセスするデータは,必ずしも,リレーショナルデータベースである必要も,表形式のデータである必要もない. たとえば,フォルダ名を指定(リクエスト)すると,該当フォルダに存在するファイル名を返すデータサーバーを作成したとする.クライアントアプリケーションはサーバーにリクエスト(フォルダ名)を送ることで,その結果をRecordsetオブジェクト(あるいはそれに該当するユーザー定義オブジェクト)として受け取ることができる.取得した結果(ファイル名のリスト)を操作するには,従来のレコードセットの操作方法と変わらない. ここまで,データサーバーという言い方をしてきたが,OLE DB用語では,このようなサーバーをOLE DBプロバイダといい,それに対してクライアントアプリケーションをOLE DBコンシューマという.マルチユーザーに対応するか否かはサーバー側の実装次第である.たとえば,WebサーバーはWebサイト上にあるデータ(ファイル)をマルチユーザーに対して提供するデータサーバーである.もちろん,WebサーバーはOLE DBプロバイダとして実装されているわけではない.しかし,WebサーバーとWebブラウザの連携が提供するものと同等の機能を,OLE DBプロバイダとOLE DBコンシューマとして実装することも可能である. |
| しかし,汎用的なプログラミングスタイルでアクセスできるデータサーバーを作成する機会は,多くのユーザーにはないはずである.特殊なデータにアクセスするプログラムを書く場合でも,専用のルーチンを使ってアクセスするスタイルをとるだろう.何もわざわざ,複雑な実装を行なってまで,実績のないレイヤーを重ねる必要はない.これは,COMが提供するメリットの限界にも似ている.COMはWindows標準のコンポーネントアーキテクチャであるが,COMコンポーネント化しないと作成できないプログラムというものはほとんどない.得られる随一のメリットはプログラミングインターフェイスの汎用性,再利用性である.OLE DBプロバイダの唯一のメリットも同様,データアクセスの方法の汎用性しかない. |
| では,一般ユーザーにとって,OLE DBのメリットとは何なのだろうか? それは,OLE DBをサポートしたサードパーティ製品が発売されるようになったときだ,ということができる.一般ユーザーが受けるCOMのメリットが市販のActiveXコントロールによってもたらされているのと同様である. 先にあげた例のように,ファイルシステムにアクセスするようなOLE DBプロバイダが市販されれば,開発者はRecordsetオブジェクトを操作する要領でファイル操作ができるようになる.あるいは,POPサーバーにアクセスするOLE DBプロバイダが提供されれば,同様の処理で着信メールの処理を行なうことができるのである. |
| 本連載で作成しているDB Serverを,OLE DBプロバイダとして実装すればいいのではないか,ということに気づいた方もいるかもしれない.その通りである.今まではデータの取得結果をテキストで表示するだけだったが,OLE DBプロバイダとして実装することで,Microsoft SQL Serverにアクセスする場合のような,汎用的なプログラミングスタイルで取得データを扱うことができる.Visual Basic 6.0のOLE DBプロバイダ開発機能は,まさに本連載のためにあるようなものだ.これこそ,待った甲斐があるというものである. 今回のサンプルであるType6 DB ServerとDB Client(図1)では,まだ,OLE DBプロバイダとして実装するまでにはいたっていない.しかし,新機能を利用することで取得データをRecordsetオブジェクトに格納し,フォームに配置したテキストボックスに連結している.レコードの移動ボタンをクリックすることで,カレントレコードを移動することもできる.今までのように,複数行表示のテキストボックスに取得結果を表示するだけのインターフェイスよりも,格段の進歩だということができるだろう.将来的にサーバー自体をOLE DBプロバイダとして実装するか,あるいは専用のデータアクセスコンポーネントをOLE DBプロバイダとして実装するかは未定である.それを判断するには,Visual Basic 6.0の新機能について私のもっている知識はまだ少なすぎる. | |
![]() 図1:Type6 DB Client(テキストボックスと結果セットが連結されている) |
| 今回からは,クライアントの実装を進めてゆく.そのためType6は,サーバー側の変更はほとんどない.しかし,一部のデータ形式を変更しているため,Type6 DB Clientを正常に動作させるためには,Type6 DB Serverを使用する必要がある. バージョンアップをするためにはDBsvr.vbpをVisual Basic 6.0で開いて,バイナリ互換を維持したままコンパイルしなおす必要がある.バイナリ互換をとるモジュールはサンプルフォルダにあるDBsvr.exeでもいいし,Type5 DB ServerのDBsvr.exeでもいい.インターフェイスは変更されていないからだ. クライアントアプリケーションには,新しくActiveXコンポーネント(MakeRecordsetCls)が追加されている.将来の再利用性を考えてコンポーネント化したが,単にVisual Basicのクラスとして実装しても問題はない.このコンポーネントは,DB Serverが送り返した取得結果(文字列)を引数として渡すと,Recordsetオブジェクトを作成するMakeRecordset関数を含んでいる.DataSourceBehaviorプロパティの値にvbDataSourceを指定しているので,レコードセットをフォームのコントロールと連結することも可能である. リスト1はMakeRecordsetClsクラスの全コードリストである.このクラスを含むAKIZUKI_DB_Clientプロジェクトには,このクラスしか含まれていない.このクラスではADOのRecordsetオブジェクトを使用するため,Visual Basicの参照設定で「Microsoft ActiveX Data Object 2.0 Library」を指定する必要がある.図2で指定されているMicrosoft Data Source InterFacesはDataSourceBehaviorプロパティの値をvbDataSourceに設定すると自動的に指定される. | |
![]() 図2:AKIZUKI_DB_Clientプロジェクトの参照設定 | |
| 空のレコードセットを作成し,レコードセット作成の準備をしているのが,MakeRecordset関数中の以下の部分である. |
| With dsRecordset .Fields.Append "Au_ID", adLongVarChar, 10, adFldRowID .Fields.Append "Author", adChar, 50, adFldUpdatable .Fields.Append "Year Born", adInteger, 5, adFldUpdatable .CursorType = adOpenKeyset .LockType = adLockOptimistic .Open End With |
| Type6では,まだ,汎用的にレコードセットを作成することはできず,BIBLIO.MDBのAuthorsテーブル専用である.汎用的なルーチンを作成するためには,サーバーから値を受け取る時に値と一緒に項目の名前やサイズ,形を受け取る必要がある.今後の課題のひとつだということができるだろう. RecordsetオブジェクトのFieldsプロパティ(コレクション)のAppendメソッドを使用して,Recordsetオブジェクトの項目を追加している. 実際の値の代入を行なっているのは,次の部分である. |
| Option Explicit Public dsRecordset As ADODB.Recordset | <中略> Dim fld As ADODB.Field | <中略> Do While intPosRow > 0 dsRecordset.AddNew For Each fld In dsRecordset.Fields | <値を切り出す処理> | fld.Value = Val(strField) | <中略> Next dsRecordset.Update dsRecordset.MoveFirst If strRow = "Send Complate" Then Exit Do End If Loop |
| まず,新しいレコードをAddNewメソッドによって追加し,引数として渡された取得結果から区切り記号によって切り出した値をFieldオブジェクトのValueプロパティに設定する.1レコード分(この場合は3項目)の処理を終えたら,RecordsetオブジェクトのUpdateメソッドを実行すればレコードの追加は完了する. 次のClass_GetDataMemberプロシージャはイベントプロシージャである.GetDataMemberイベントはDataSourceBehaviorプロパティの値にvbDataSourceを指定した場合のみ利用可能である.このサンプルの場合,GetDataMemberイベントはBindingCollectionオブジェクトのDataSourceプロパティに,このMakeRecordsetClsオブジェクトが指定された場合に発生する. |
| Private Sub Class_GetDataMember (DataMember As String, Data As Object) Set Data = dsRecordset End Sub |
| このプロシージャでは,引数のDataオブジェクトにMakeRecordset関数で作成したRecordsetオブジェクトを設定している. |
| Option Explicit Public dsRecordset As ADODB.Recordset Private Const CPOS As String = ";" ' 区切り記号の指定 Public Function MakeRecordset(strParam As String) Dim fld As ADODB.Field Dim strRow As String Dim strField As String Dim intPos As Integer Dim intPosRow As Integer Set dsRecordset = New ADODB.Recordset With dsRecordset ' Au_ID を主キーとして設定します .Fields.Append "Au_ID", adLongVarChar, 10, adFldRowID .Fields.Append "Author", adChar, 50, adFldUpdatable .Fields.Append "Year Born", adInteger, 5, adFldUpdatable .CursorType = adOpenKeyset .LockType = adLockOptimistic .Open End With intPosRow = InStr(strParam, vbCrLf) strRow = Left(strParam, intPosRow - 1) strParam = Right(strParam, Len(strParam) - Len(strRow) - Len(vbCrLf) - Len(vbCrLf)) strRow = "" intPosRow = 0 If InStr(strParam, vbCrLf) <> 0 Then intPosRow = InStr(strParam, vbCrLf) strRow = Left(strParam, intPosRow - 1) strParam = Right(strParam, Len(strParam) - Len(strRow) - Len(vbCrLf)) End If Do While intPosRow > 0 dsRecordset.AddNew For Each fld In dsRecordset.Fields ' 区切り記号が見つかったら,左側が項目の値 If InStr(strRow, CPOS) <> 0 Then ' 区切り記号まで位置を移動します intPos = InStr(strRow, CPOS) ' 値をstrField変数に割り当てます strField = Left(strRow, intPos - 1) Else ' 区切り記号が見つからなければ,最後のフィールドです strField = strRow End If If fld.Type = adInteger Then fld.Value = Val(strField) Else fld.Value = strField End If ' 処理済の文字列を操作対象レコードから取り除きます strRow = Right(strRow, Len(strRow) - intPos) intPos = 0 Next dsRecordset.Update dsRecordset.MoveFirst intPosRow = InStr(strParam, vbCrLf) strRow = Left(strParam, intPosRow - 1) If strRow = "Send Complate" Then Exit Do End If strParam = Right(strParam, Len(strParam) - Len(strRow) - Len(vbCrLf)) Loop End Function Private Sub Class_GetDataMember(DataMember As String, Data As Object) Set Data = dsRecordset End Sub |
![]() 図3:Clientプロジェクトの参照設定 |
| 先に作成したコンポーネントをクライアントアプリケーションから利用しているのが,クライアントアプリケーションのDataArrivalイベントプロシージャである(リスト2). 次のコードは,返された値の最初のレコードセットを最後まで受けとった時点で実行される.MakeRecordsetClsオブジェクトが実体化され,MakeRecordsetメソッドが呼び出されてRecordsetオブジェクトが作成される.Recordsetオブジェクトを受け取り,コントロールに連結するためにはBindingCollectionオブジェクトを参照設定する必要がある(図3).BindingCollectionオブジェクトのDataSourceメソッドにMakeRecordsetClsオブジェクトを指定したときに,先に説明したGetDataMemberイベントが,MakeRecordsetClsオブジェクト内で発生する. |
| If InStr(resultSet, "Result Start") > 0 And InStr(resultSet, "Send Complate") > 0 Then Set mkRs = New MakeRecordsetCls mkRs.MakeRecordset (resultSet) Set colBind = New BindingCollection Set colBind.DataSource = mkRs |
| 次のコードはRecordsetオブジェクトの各項目とフォーム上のテキストボックスの連結を行なっている. |
| colBind.Add txtAu_ID, "Text", "Au_ID" colBind.Add txtAuthor, "Text", "Author" colBind.Add txtYearBorn, "Text", "Year Born" |
| 連結された項目は,Recordsetオブジェクトのカレントレコードが移動した場合などに,自動的に更新される.リスト3のレコード移動処理において,表示の更新処理が記述されていないことに注意してほしい. |
| Private mkRs As MakeRecordsetCls Private colBind As BindingCollection Private Sub Winsock2_DataArrival(ByVal bytesTotal As Long) Dim strData As String ' クライアントDBsvr(ActiveXexe)からデータを受け取る Winsock2.GetData strData, vbString resultSet = resultSet & strData txt_data.Text = resultSet ' txt_data.Text = txt_data.Text & strData If InStr(resultSet, "Result Start") > 0 And InStr(resultSet, "Send Complate") > 0 Then Set mkRs = New MakeRecordsetCls mkRs.MakeRecordset (resultSet) Set colBind = New BindingCollection Set colBind.DataSource = mkRs colBind.Add txtAu_ID, "Text", "Au_ID" colBind.Add txtAuthor, "Text", "Author" colBind.Add txtYearBorn, "Text", "Year Born" cmdNext.Enabled = True cmdBack.Enabled = True End If mlngEndTime = timeGetTime() lbl_Time.Caption = (mlngEndTime - mlngStartTime) / 1000 End Sub |
| Private Sub cmdBack_Click() mkRs.dsRecordset.MovePrevious If mkRs.dsRecordset.BOF Then mkRs.dsRecordset.MoveNext End If End Sub Private Sub cmdNext_Click() mkRs.dsRecordset.MoveNext If mkRs.dsRecordset.EOF Then mkRs.dsRecordset.MovePrevious End If End Sub |
| 注意しなければならないのは,このサンプルの場合,直接のデータソースになっているのはDB Server自体ではなく,DB Serverが送信したテキストデータだということである.もちろん,テキストデータはDB Serverが返した値であるわけだから,見かけ上はDB Serverがデータソースになっている形になる. 現時点ではOLE DBをまだ十分に理解しているわけではないので,リモートデータベースにアクセスする場合,このサンプルのように独自プロトコルを実装しなければならないのか,あるいは,ODBC経由以外の標準的な方法があるのかどうかはよくわからない. いずれにしても,Visual BasicでOLE DBプロパイダの開発ができるメリットは,本連載にとって大きな意味がある.クライアントライセンスがフリーの,高性能なデータベースサーバーを,標準的なプログラミングインターフェイスから使用できれば,読者にとっても意味があるはずである. 今後の課題としては,Recordsetオブジェクトからの更新機能,新規レコードの追加機能などが考えられる.このあたりはデータ編集用のSQLの自動生成をする必要があるので,それほど単純ではないが,どうしても必須の機能だということができるだろう. サーバー側もマルチススレッドの対応の他に,ストアドプロシージャを実装できるような仕掛けがあれば,より実用的になるはずである. 今のところ,C/Sデータベースシステムといえば,OracleやMicrosoft SQL Serverなどのサーバー製品を使うのが一般的である.これらのデータベース製品は高価である他に,運用のための専門のスキルが必要である.比較的扱いやすいデータベースサーバー製品であるSybase SQL Anywhere(Sybase Adaptive Server)はパッケージの価格自体は低価格だが,クライアントのライセンスフィーは決して安くはない. 現在Windows上で,とくに安くC/Sデータベースシステムを構築する方法は,Active Server PagesとJetデータベースエンジンを利用する方法である.この方法はちろんWebアプリケーションとして実装することもできるし,以前に紹介したように専用クライアントアプリケーションを使ってアクセスすることもできる.この連載でも,クライアントの実装がひと通り終了したら,そのクライアントからActive Server Pagesを経由してサーバーのデータベースにアクセスできるような方法を紹介したいと考えている. |