|
Tech Note 27: NS Basic/Palm を Microsoft .NET と共に使う Part 2September 18, 2003 |
[本稿はRobert Chartier氏が15seconds.comに書かれた記事を翻訳したものです]
この記事では、Palm Blogアプリケーションのデータを様々なアップロードサイトにアップロードするための、Palmの同期プロセスを使った接続方法を説明します。
Part 1では、データをアップロードする3つの方法(WebLogApi、Ftp、Http)を用意すると説明しました。ここではWebLogApiのみをこの記事でカバーします。他の2つの方法に関しては、私のコードを見て頂くか、または別の方法をプロジェクトに追加して頂くのもよいでしょう。
同期プロセスを理解するのは比較的簡単です。お持ちのPalmデバイスをケーブルを使ってデスクトップまたはラップトップへ接続します。コンピュータ側ではHotSync Managerが動作していることを確認します。さらに、特定のアプリケーション用に同期プロセスの制御を許容してくれる「コンジット」(HotSync Managerのプラグインみたいなもの)が必要です。
Part 1では、"Creator ID"について、アプリケーションとデータベースを認識する特有の識別記号であると説明しました。同期プロセス中、HotSync Managerは入ってくるデータを処理するために、どのコンジットを呼ぶべきなのかを判断します。この判断は、Creator IDが基になっています。"PBlg"を我々のアプリケーションのCreator IDにしましたので、HotSync Managerがこの特定の識別記号を見つける度に、我々のコンジットを呼んでくれます。
コンジットを作成する時、 Conduit Developer Kitをダウンロードしてインストールする必要があります。これはPalmのサイト(http://www.palmos.com/dev/tech/conduits/)からダウンロードできます。私がダウンロードして使っているのは、Windows用のCDK 4.0.3です。さらにPalm COM Installer Updateをダウンロードしインストールします。これは、我々が作るCOM Conduitsに関するいくつかのバグ修正が含まれています。多くの役立つ情報が含まれていますので、CDKに含まれているドキュメントを読んでいただくことをお薦めします。
Visual Studio .NET (VS .NET)を開き、 "PBlgConduit"と名前をつけて、新しいC# Windowsアプリケーションを作ります。私がWindowsアプリケーションを使うことにしたのは、データの同期とアップロードの状態を見せるデフォルトフォームが使えるためです。オプションとしてプログレスバーやラベルをデフォルトフォームに追加し、ユーザーに対して状態を示すことも良いでしょう。この記事をできるだけシンプルにすることと、コードを短く保つことを考え、私はユーザーインターフェース要素は一切修正していません。
コンジットがHotSync Managerと通信するには、CDKから2つのライブラリをインポートする必要があります。これらは2つとも、 %install%\CDK403\Common\Binにあります(%install%はあなたがCDKをインストールするために選んだパス)。例えば私の場合、c:\CDK403\Common\Bin\に収まっています。あなたのプロジェクト内で、"ComDirect.dll"と"ComStandard.dll"の両方にリファレンスを足します。COM Interopは、VS .NETが自動的に面倒見るはずです。
最後にしなければならないことは、このプロジェクトが"Palm\"フォルダー(全てのPalmファイルのインストールフォルダー)に作られていることを確認して下さい。これはデバッグが正しく行われるためです。
これまでに、VS .NETのセットアップ用に必要最低限は用意できました。これから、データを処理するための、クラスやプロジェクトを作成していきます。それには、Palmデータベースを我々の管理された環境にインポートするためのプロジェクトも含まれています。
このステップでは、Part 1の中で作ったUDTを保持するための複数のクラス(基本的にデータ層)を作ります。さらに、Palmデータベースをデータ層に簡単にインポートするための、再利用可能なプロジェクトを作ります。そして最後に、WebLogAPIを処理するために既存のプロジェクトに連結します。
VS .NET内のデフォルトプロジェクトで、"DataTier.cs"と名前をつけたクラスを作ります。これはデータ層の3つ全てのクラスをホールドするために使います。Figure 1は、これら3つのクラスの最終出力です。各クラスには、Palmデータベースそのものに連携したいくつかのプロパティがあります。ここで各クラスのプロパティの順番を意識することが重要です。同期プロセスからデータのインポートに移行する時、我々はこれらに依存します。
Figure 1 List of BlogEntry, BlogSetting, sTypes Types
using System; namespace PBlgConduit.DataTier { ///<summary> /// Used to be the managed representation of the BlogEntry UDT from the Palm Database /// Type BlogEntry /// subject as String 'subject of the entry /// dDate as String 'date of the entry /// entry as String 'the actuall full text entry /// settingsName as String 'matching settings name /// End Type ///</summary> publicclass BlogEntry { /*app specific attributes*/ string key; string subject; string dDate; string entry; string settingsName; publicstring Key{get{return key;}set{key=value;}} publicstring Subject{get{return subject;}set{subject=value;}} publicstring DDate{get{return dDate;}set{dDate=value;}} publicstring Entry{get{return entry;}set{entry=value;}} publicstring SettingsName{get{return settingsName;}set{settingsName=value;}} /*Palm DB Specific attributes for each record*/ private PDStandardLib.ERecordAttributes eAttributes; privateint nIndex; privateint nCategory; privateobject vUniqueId; privatebyte[] raw; publicbyte[] Raw{get{return raw;}set{raw=value;}} public PDStandardLib.ERecordAttributes EAttributes{get{return eAttributes;}set{eAttributes=value;}} publicint Index{get{return nIndex;}set{nIndex=value;}} publicint Category{get{return nCategory;}set{nCategory=value;}} publicobject UniqueId{get{return vUniqueId;}set{vUniqueId=value;}} public BlogEntry() { } public BlogEntry(string Subject, string DDate, string Entry, string SettingsName) { subject=Subject; dDate=DDate; entry=Entry; settingsName=SettingsName; } } ///<summary> /// Used to represent the managed version of the BlogSetting UDT /// Type BlogSetting /// Name as String 'name of the setting, key /// isDefault as Integer 'if this is the default setting /// sType as Integer 'type of setting it is /// username as String 'username to use during auth /// password as String 'password to use during auth /// port as Integer 'what port is the remote service running on /// domain as String 'what domain/ip is the remote server /// path as String 'file path to the save location /// filename as String 'ftp file name, /// 'http variable post name For xml upload /// 'or url end point /// End Type ///</summary> publicclass BlogSetting { string key; string name; int isDefault; string sType; string username; string password; int port; string domain; string path; string filename; publicstring Key{get{return key;}set{key=value;}} publicstring Name{get{return name;}set{name=value;}} publicint IsDefault{get{return isDefault;}set{isDefault=value;}} publicstring SType{get{return sType;}set{sType=value;}} publicstring Username{get{return username;}set{username=value;}} publicstring Password{get{return password;}set{password=value;}} publicint Port{get{return port;}set{port=value;}} publicstring Domain{get{return domain;}set{domain=value;}} publicstring Path{get{return path;}set{path=value;}} publicstring Filename{get{return filename;}set{filename=value;}} /*Palm DB Specific attributes for each record*/ private PDStandardLib.ERecordAttributes eAttributes; privateint nIndex; privateint nCategory; privateobject vUniqueId; privatebyte[] raw; publicbyte[] Raw{get{return raw;}set{raw=value;}} public PDStandardLib.ERecordAttributes EAttributes{get{return eAttributes;}set{eAttributes=value;}} publicint Index{get{return nIndex;}set{nIndex=value;}} publicint Category{get{return nCategory;}set{nCategory=value;}} publicobject UniqueId{get{return vUniqueId;}set{vUniqueId=value;}} public BlogSetting() { } public BlogSetting(string Name,int IsDefault,string SType,string Username,string Password,int Port,string Domain,string Path,string Filename) { name=Name; isDefault=IsDefault; sType=SType; username=Username; password=Password; port=Port; domain=Domain; path=Path; filename=Filename; } } }
次に、新規にC# Class Libraryプロジェクトを"PalmDBImporter"ソリューションに追加します。これはPalmデータベースを上のデータ層クラスに簡単にインポートするための、シンプルなクラスを含んでいます。これを新規プロジェクトとして作成する唯一の理由は、汎用的なPalmデータベースのインポーターであり、将来のソリューションにこのプロジェクトを再利用できるからです。この新規プロジェクトに、プロジェクト リファレンスを足すことを忘れないで下さい。Figure 2は、このクラスのソースコードです。
Figure 2: Palm Database Importer
using System; namespace PalmDBImporter { ///<summary> /// Class used to import the byte[] from the Palm Synch process and bind it to a Class, based on NULL \0 values and reflection ///</summary> publicclass Importer { ///<summary> /// called with an instantiated class and the byte[] of data. It will attempt to move the data into the class based on its public properties. ///</summary> ///<param name="Class"></param> ///<param name="data"></param> ///<returns></returns> publicstaticbool BindToClass(object Class, byte[] data) { try { //get a list of all the available properties System.Reflection.PropertyInfo[] props = Class.GetType().GetProperties(); int pCounter=0; int bCounter=0; //create a temp buffer to hold current data byte[] propData = newbyte[data.Length]; //if we actually have any public properties if(props!=null && props.Length>0) { //grab the current property System.Reflection.PropertyInfo currentProp = props[pCounter]; //iterate over the entire length of the data for(int x=0;x<data.Length;x++) { //if it is a null value, then we hit a field terminator if(data[x]==0) { //grab the bytes from the data into our temp buffer byte[] newData = newbyte[bCounter]; newData = GetBytes(propData, 0, bCounter); //convert the bytes to a string string val = System.Text.ASCIIEncoding.ASCII.GetString(newData); //set the value into the property try { //we are only gonna handle 3 data types right now //boolean, string and numbers string propType=currentProp.PropertyType.FullName.ToLower().Replace("system.",""); object[] propValue=GetValue(propType, val); Class.GetType().InvokeMember(currentProp.Name, System.Reflection.BindingFlags.SetProperty, null, Class, propValue); }catch(Exception exc) { string f = exc.ToString(); //do nothing } pCounter++; //if we have any more properties, lets continue if(pCounter>props.Length) { currentProp = props[pCounter]; propData = newbyte[data.Length]; bCounter=0; } else { break; } } else { //shove the current value into the temp buffer propData[bCounter]=data[x]; bCounter++; } } } returntrue; }catch(Exception e) { returnfalse; } } ///<summary> /// Fix up the string to match HTML output for Weblogs ///</summary> ///<param name="str"></param> ///<returns></returns> publicstaticstring FixString(string str) { str = str.Replace("\r\n", "<br />"); str = str.Replace("\r", "<br />"); str = str.Replace("\n", "<br />"); return str; } publicstaticobject[] GetValue(string propType, string val) { object[] propValue=newobject[]{null}; try { #region switch block for all data types switch(propType) { case "boolean": case "bool": propValue[0] = System.Boolean.Parse(val); break; case "float": propValue[0] = float.Parse(val); break; case "decimal": propValue[0] = decimal.Parse(val); break; case "double": propValue[0] = double.Parse(val); break; case "int16": propValue[0] = int.Parse(val); break; case "int32": propValue[0] = int.Parse(val); break; case "int64": propValue[0] = long.Parse(val); break; case "single": propValue[0] = short.Parse(val); break; case "long": propValue[0] = long.Parse(val); break; case "short": propValue[0] = short.Parse(val); break; case "uint": propValue[0] = uint.Parse(val); break; case "uint16": propValue[0] = uint.Parse(val); break; case "uint32": propValue[0] = ulong.Parse(val); break; case "uint64": propValue[0] = ulong.Parse(val); break; case "ulong": propValue[0] = ulong.Parse(val); break; case "ushort": propValue[0] = ushort.Parse(val); break; default: propValue[0]=FixString(val); break; } #endregion }catch{} return propValue; } ///<summary> /// used to get a range of bytes out of an existing byte[] block ///</summary> ///<param name="data"></param> ///<param name="start"></param> ///<param name="count"></param> ///<returns></returns> publicstaticbyte[] GetBytes(byte[] data, int start, int count) { byte[] b = newbyte[count]; int ctr=0; //copy over the bytes for(int x=start;x<count;x++) { b[ctr]=data[x]; ctr++; } return b; } } }
基本的にこのコードで行っていることは、各レコードのために、全てのデータに繰り返しアクセスすることです。 "blogdb"レコードの1つを例に取ってみると、それは保管されている1つの"Entry" UDTです。各エントリー用のbyte[]は固定されていますので、各レコードの各フィールドを表すために、nullで終る文字列値を使うことができます。null値に当たった時、そのフィールドは終わりであることが分かります。その値を、カレントプロパティーにはめ込みます。
これはクラスのプロパティを順番にUDTのプロパティに強制的にマッチさせています。これが行われている限り、データベースのインポートはスムースに行くはずです。
コメントに注意を払いながら、少し時間を掛けて、コードを把握して下さい。reflectionに関して良く分からない場合は、reflectionに関する私の過去の記事を参考にして下さい。http://www.15seconds.com/issue/020618.htm
Palmデバイスから入って来るデータの取り扱いと、生データ(byte[])のコードへの変換が済みました。次のステップでは、CDKに含まれるConduit Configurationツールを使って、我々のコンジットの登録の仕方と、同期プロセス中のコンジットのテストの仕方を見せます。
CDKの中に、我々が必要とする"CondCfg.exe"というツールがあります。"CDK403\Common\Bin"フォルダーの中を探し、それを実行して下さい。Creator IDと名前の説明を伴った、既にインストールされてるコンジットが表示されます。我々のアプリケーション用の新しいコンジットを追加してみて下さい。Figure 3は、"Conduit Information"スクリーンで、あなたがセットしなければならない全てのアイテムです。
Figure 3: New Conduit Information
最初にConduit Typeに"Component"を選び、次に"COM Client"をあなたのVS .NETのdevenv.exe実行ファイルへと設定して下さい。これによって、HotSync ManagerにVS .NETを開かせますので、我々のソリューションを読込むように指定でき、コンジット同期プロセスのデバッグを可能にしてくれます。HotSync Managerは、VS .NETを閉じるまでアイドル状態になります。もちろんこれは同期プロセス全てをストップしますので、後で、PBlgConduit WinFormアプリケーションの出力である、我々のコンパイルしたアプリケーションへと変更します。
次に、入ってくるデータをデータ層へ結び付けなければなりません。それを行うコードは、Form1_Load()イベントハンドラーの中に、新しいクラス"PalmBlogWorker.cs"を作ることにしました。入ってくるデータベース、レコード、フィールドへ繰り返しアクセスし、適切なオブジェクトを読込み、最後に正しいサイトにデータを掲載することが役目です。
Figure 4: PalmBlogWorker.cs class
using System; using System.Runtime.InteropServices; //need to import .net's InteropServices namespace namespace PBlgConduit { publicclass PalmBlogWorker { /*Settings properties*/ PDStandardLib.PDRecordAdapter blogSettings; //represents the query for our database PDDirectLib.PDDatabaseQuery settingsQuery = new PDDirectLib.PDDatabaseQuery(); //all of the stored settings on the palm System.Collections.ArrayList settings = new System.Collections.ArrayList(); public System.Collections.ArrayList Settings{get{return settings;}} int dirtyCountSettings=0; publicint DirtyCountSettings{get{return dirtyCountSettings;}} int archiveCountSettings=0; publicint ArchiveCountSettings{get{return archiveCountSettings;}} int deleteCountSettings=0; publicint DeleteCountSettings{get{return deleteCountSettings;}} int secretCountSettings=0; publicint SecretCountSettings{get{return secretCountSettings;}} /*Entries properties*/ PDStandardLib.PDRecordAdapter blogrecord; //entries database PDDirectLib.PDDatabaseQuery query = new PDDirectLib.PDDatabaseQuery(); //entries collection System.Collections.ArrayList entries = new System.Collections.ArrayList(); public System.Collections.ArrayList Entries{get{return entries;}} int dirtyCount=0; publicint DirtyCount{get{return dirtyCount;}} int archiveCount=0; publicint ArchiveCount{get{return archiveCount;}} int deleteCount=0; publicint DeleteCount{get{return deleteCount;}} int secretCount=0; publicint SecretCount{get{return secretCount;}} ///<summary> /// open all the settings entries in the database and import them ///</summary> privatevoid ImportSettings() { int nIndex=0; object vUniqueId=null; int nCategory=0; PDStandardLib.ERecordAttributes eAttributes=PDStandardLib.ERecordAttributes.eDirty; object vData=null; long nCount=0; blogSettings.IterationIndex = 0; try { //attempt to read an the first item out of our database vData = blogSettings.ReadNext(out nIndex,out vUniqueId,out nCategory,out eAttributes); }catch(Exception exc) { exc.ToString(); } try { //loop for each settings item while(!(blogSettings.EOF)) { if(vData!=null) { //create a place holder class for the new settings PBlgConduit.DataTier.BlogSetting setting = new PBlgConduit.DataTier.BlogSetting(); //using our importer bind the byte[] to our settings class PalmDBImporter.Importer.BindToClass(setting, (byte[])vData); //set the miscel properties setting.EAttributes=eAttributes; setting.Index=nIndex; setting.UniqueId=vUniqueId; setting.Category=nCategory; //increment the miscel counters -not really needed //but nice to have if you plan on expanding the app more if(eAttributes==PDStandardLib.ERecordAttributes.eDirty) dirtyCountSettings++; if(eAttributes==PDStandardLib.ERecordAttributes.eArchive) archiveCountSettings++; if(eAttributes==PDStandardLib.ERecordAttributes.eDelete) deleteCountSettings++; if(eAttributes==PDStandardLib.ERecordAttributes.eSecret) secretCountSettings++; setting.Raw=(byte[])vData; //add the current settings to our collection settings.Add(setting); nCount++; } //Read the next record vData = blogSettings.ReadNext(out nIndex,out vUniqueId,out nCategory,out eAttributes); } }catch(Exception){} } ///<summary> /// open all the blog entries in the database and import them ///</summary> privatevoid ImportEntries() { int nIndex=0; object vUniqueId=null; int nCategory=0; PDStandardLib.ERecordAttributes eAttributes=PDStandardLib.ERecordAttributes.eDirty; object vData=null; long nCount=0; blogrecord.IterationIndex = 0; try { //read the first record, if any vData = blogrecord.ReadNext(out nIndex,out vUniqueId,out nCategory,out eAttributes); }catch(Exception exc) { exc.ToString(); } try { //loop for each record while(!(blogrecord.EOF)) { if(vData!=null) { //create the entry placeholder PBlgConduit.DataTier.BlogEntry entry = new PBlgConduit.DataTier.BlogEntry(); //bind the data to the entry class PalmDBImporter.Importer.BindToClass(entry, (byte[])vData); //set miscel items entry.EAttributes=eAttributes; entry.Index=nIndex; entry.UniqueId=vUniqueId; entry.Category=nCategory; if(eAttributes==PDStandardLib.ERecordAttributes.eDirty) dirtyCount++; if(eAttributes==PDStandardLib.ERecordAttributes.eArchive) archiveCount++; if(eAttributes==PDStandardLib.ERecordAttributes.eDelete) deleteCount++; if(eAttributes==PDStandardLib.ERecordAttributes.eSecret) secretCount++; entry.Raw=(byte[])vData; //add new entry to our collection of entries entries.Add(entry); nCount++; } //Read the next record vData = blogrecord.ReadNext(out nIndex,out vUniqueId,out nCategory,out eAttributes); } }catch(Exception){} } ///<summary> /// Main caller for worker ///</summary> public PalmBlogWorker() { //import all the entries //query and grab only the blogdb database entries blogrecord=(PDStandardLib.PDRecordAdapter)query.OpenRecordDatabase("blogdb", "PDDirect.PDRecordAdapter", PDDirectLib.EAccessModes.eRead | PDDirectLib.EAccessModes.eShowSecret | PDDirectLib.EAccessModes.eWrite); ImportEntries(); //import all the settings //query and grab only the blogSettings database entries blogSettings=(PDStandardLib.PDRecordAdapter)query.OpenRecordDatabase("blogSettings", "PDDirect.PDRecordAdapter", PDDirectLib.EAccessModes.eRead | PDDirectLib.EAccessModes.eShowSecret | PDDirectLib.EAccessModes.eWrite); ImportSettings(); //upload all the items UploadEntries(); } publicvoid UploadEntries() { //if we have any ditry entries, meaning any entries that we havnt dealt with yet if(this.entries.Count>0 && DirtyCount>0) { //for each entry for(int x=0;x<entries.Count;x++) { string results = "";//worker.UploadEntry(entry, settings); PBlgConduit.DataTier.BlogEntry entry = (PBlgConduit.DataTier.BlogEntry)entries[x]; //if it is dirty if(entry.EAttributes==PDStandardLib.ERecordAttributes.eDirty) { //find the associated settings for(int y=0;y>settings.Count;y++) { PBlgConduit.DataTier.BlogSetting setting = (PBlgConduit.DataTier.BlogSetting)settings[y]; if(setting.Name==entry.SettingsName) { //upload the entry with the given settings results = this.UploadEntry(entry, setting); break; } } //upload it //if we have no errors during upload, delete the record if(results!="") { //delete the entry from the db DeleteRecord(entry.UniqueId, "blogdb"); } } } } else { //Nothing new to upload. do nothing right now } } publicstring UploadEntry(PBlgConduit.DataTier.BlogEntry entry, PBlgConduit.DataTier.BlogSetting settings) { //can handle multiple upload types, but right now we only //want to use the WebLogApi //implement more if you like string ret=""; if(settings.SType=="WebLogApi") { ret = UploadMetaWebLogAPI(entry, settings); } else { return "Nothing done"; } return ret; } publicstring UploadMetaWebLogAPI(PBlgConduit.DataTier.BlogEntry entry, PBlgConduit.DataTier.BlogSetting settings) { //use JDSolutions XmlRPC library to handle the WebLogApi uploads //it can be downloaded at: http://www.jondavis.net/JDSolutions/XmlRpc/readme.htm //ive included the full download with this zip //create a new xmlrpcclient JDSolutions.XmlRpc.XmlRpcClient xrc = new JDSolutions.XmlRpc.XmlRpcClient(); //create xmlrpc strings to hold our auth data JDSolutions.XmlRpc.Elements.XmlRpcString blogid = new JDSolutions.XmlRpc.Elements.XmlRpcString(entry.Subject); JDSolutions.XmlRpc.Elements.XmlRpcString username = new JDSolutions.XmlRpc.Elements.XmlRpcString(settings.Username); JDSolutions.XmlRpc.Elements.XmlRpcString password = new JDSolutions.XmlRpc.Elements.XmlRpcString(settings.Password); JDSolutions.XmlRpc.Elements.XmlRpcBoolean publish = new JDSolutions.XmlRpc.Elements.XmlRpcBoolean(true); //Post is an object i needed to create in order to handle the actual post data being sent //It is located in the MetaBlogApi.cs file included in this project //load up the new post Post post=new Post(); post.dateCreated=System.DateTime.Parse(entry.DDate); post.description=entry.Entry; post.title=entry.Subject; //conver our post class to an xmlrpcstruct JDSolutions.XmlRpc.Elements.XmlRpcStruct ST2 = (JDSolutions.XmlRpc.Elements.XmlRpcStruct)JDSolutions.XmlRpc.XmlRpcTools.ConvertElement(post, true); //gather the parameters into our object[] object[] p = newobject[]{blogid, username, password, ST2, publish}; string s=""; try { //call the method on the client, passing all needed data s = (string)xrc.Call(settings.Domain,settings.Port, settings.Path, "metaWeblog.newPost", p); }catch(Exception e){ s = ""; } return s; } ///<summary> /// Delete the record based on uniqueid from the given db ///</summary> ///<param name="UniqueId"></param> ///<param name="database"></param> ///<returns></returns> publicbool DeleteRecord(object UniqueId, string database) { try { if(database.ToLower()=="blogdb") blogrecord.Remove(UniqueId); else blogSettings.Remove(UniqueId); returntrue; }catch(Exception exc) { string e = exc.ToString(); returnfalse; } } } }
少し時間を掛けて上のコードを理解してみて下さい。 私はXML RPCライブラリを使っていますので、このライブラリに関しても理解しておく必要があります。実際これは、データをあなたのサーバーにライブで掲載します。
このコンジットを完全にテストするには、デバイスからデータを抜き出す必要があるので、あなたのPalmデバイスへPalmBlogアプリケーションをアップロードし、項目と設定をいくつか入力して下さい。もし同期プロセスに時間が掛かるようでしたら、(ChartiRフォルダーのバックアップを取った後)デバイス上のデータを全て消し、NSBRuntimeとPalm Blogアプリケーションのみを同期してみるのもいいかもしれません。同期プロセスが速くなります。
この時点でソリューションがビルドされることを確認して下さい。エラーが出る場合、私のサンプルソリューションを参考にして、何が欠けているのが探し出し、エラーを取り除いて下さい。ビルドが成功すると、コンジットのデバッグが行えます。デバイスをクレードルに置き、同期プロセスを始めて下さい。
HotSync Managerが、我々のCreator IDを持つデータを見つけると、コンジットの設定"Com Client"で指定したアプリケーション(VS .NET IDE)を実行します。VS .NETが読み込みを終えたら、我々のPBlgConduitアプリケーションを選びます。ここからデバック機能を利用して、アプリケーションの中に踏み込むことができます。予想通りのパスをたどっていることと、データが正しい目的の場所にアップロードされていることを確認して下さい。
コンジットが完了し、結果に満足できたら、Conduit Configurationツールの"Com Client"を、"Palm\"フォルダーに入っている"PBlgConduit.exe"を指すように変更して下さい。
これまでにこのシリーズでは、Palm OS用の簡単なアプリケーションの作り方と、Palmで作られたデータをPC上のソリューションへの移し方を習いました。このシリーズの最後では、VS .NETに付いてくる開発ツールを使って、インストール処理にフォーカスを当てます。