デスクトップ アプリケーションからモバイル アプリケーションへの Object Pascal コードの移行

提供: Appmethod Topics
移動先: 案内検索

Object Pascal におけるマルチデバイス アプリケーションについての考慮事項 への移動

このトピックでは、Object Pascal モバイル コンパイラ(iOS デバイス用、iOS シミュレータ用、Android 用)を使用できるように既存の Object Pascal コードを以降する方法を説明します。


Object Pascal モバイル コンパイラでサポートされていないデータ型を削除

サポートされていない以下の型のいずれかを使用しているコードは削除するか、代わりの型を使用するように書き換えなければなりません。

WideString、AnsiString、ShortString、AnsiChar、PAnsiChar、PWideChar、Openstring

デスクトップ アプリケーションで使用する型
(1 から始まるインデックス方式)

モバイル アプリケーションで使用する型
(0 から始まるインデックス方式)

System.WideString

System.String

System.AnsiStringSystem.ShortStringSystem.RawByteStringSystem.UTF8String

使用をやめます。

"バイト配列" を使用することを検討します。

System.AnsiChar

System.CharSystem.ByteSystem.UInt8

System.PAnsiCharSystem.PWideChar

System.SysUtils.TStringBuilderSystem.StringSystem.MarshaledString

System.Openstring

"バイト配列" を使用することを検討します。

サポートされていないこれらの型の置き換えの詳細については、以下で説明します。

WideString

場合によっては、WideStringString に置き換えることができます。

何らかの理由で WideString をモバイル プラットフォームで使用する必要がある場合は、それをマーシャリングしなければなりません。それには、4 バイト長を扱い、Unicode 文字シーケンスと文字列の 2 つの NULL 終端文字を処理します。ShortString の使用例については、コード例「ShortStringToString(Object Pascal)」を参照してください。

AnsiString および ShortString

このグループには、UTF8StringRawByteString などの "派生" 型や、以下のような構文を使った明示的な長さの ShortStrings が含まれます。

type TStr = string[127];

AnsiStringShortString のインスタンスは、元のコードでどう使用されているかに応じて削除または変更します。 場合によっては、"バイトの配列"(System.Types.TByteDynArray など)で十分なこともあります。

多くの場合は、以前の形式を必要に応じてデコードおよびエンコードしなければなりません。AnsiString を使用している箇所のほとんどは、デフォルトの String 型に直接置き換えることができます。この型で外部に保存されたほとんどの情報では、ストリームまたはストリームを使用するクラス(TStringList や類似クラスなど)を使用しています。これらのクラス型は、バイト オーダー マーク(BOM)をサポートしており、必要に応じて自動的にデコードされます。変換が必要な場合は、TEncoding クラスを使用すると、バイト列を直接取得できます。実際、TStringList の基底クラスである TStrings では、TStrings.SaveToFileTStrings.LoadFromFile などのメソッドで TEncoding インスタンスを明示的に指定できます。そのため、プログラムでは、プログラム外部にあるストレージに最終的に必要なエンコーディングにかかわらず、通常の String 型を使用できます。

短い文字列を使用していたコードの場合は、TEncoding を使用すると、現在の String 型で使用されている UTF16 文字表現と従来の短い文字列の 8 ビット ANSI 表現の違いを管理できます。

コード例「ShortStringToString(Object Pascal)」を参照してください。

AnsiChar

AnsiChar の代わりに、(Wide)Char または Byte(UInt8) を使用します。

元のセマンティクスに応じて、以下のように対応が異なります。

  • 元のセマンティクスが 'Character' の場合は、Char と Unicode 変換を使用します。
  • 元のセマンティクスが 8 ビットの記憶域の場合は、Byte 型を使用します。

PAnsiChar および PWideChar

これらの型が Ansi/Unicode 文字列へのポインタの場合は、PAnsiChar/PWideChar 型の代わりに、String または TStringBuilder オブジェクトを使用します。

元のセマンティクスが API 呼び出しに関係している場合は、代わりに API のマーシャリング機能を使用します。通常は、System.MarshaledString で十分です。

Openstring

System.Openstring は以前の言語要素です。現在、System.Generics.Defaults では OpenString 型を使用していますが、これは、Appmethod ではそれ以外に使用されることはまずありません。

多くの場合、コード例「ShortStringToString(Object Pascal)」の ShortStringToString() 関数に見られるように、"バイト配列" を OpenString の代わりに使用できます。この例で使用されている "バイト配列" はオープン配列パラメータとなっており、任意の長さのバイト配列を受け取ることができます。宣言された任意のサイズの文字列を OpenString で扱えるのとちょうど同じです。

http://www.drbob42.com/uk-bug/hood-03.htm を参照してください。

0 から始まるインデックス方式の文字列を使用

Object Pascal モバイル コンパイラの場合、文字列は 0 から始まるインデックス方式を採用しています。さらに、今後、文字列は不変(一定)になる見込みです。

Object Pascal コンパイラで採用されている文字列のインデックス方式
Object Pascal コンパイラ 文字列のインデックス方式

Object Pascal モバイル コンパイラ:


  • iOS デバイス
  • iOS シミュレータ
  • Android

0 から始まる方式

(文字列の先頭文字の開始インデックスが 0)

Object Pascal デスクトップ コンパイラ:


  • Win32
  • Win64
  • Mac OS X

1 から始まる方式

(文字列の先頭文字の開始インデックスが 1)

文字列が 1 から始まるインデックス方式を採用しているか可変であることを前提としているコードはすべて書き直すことをお勧めします。

  • インデックスが 0 から始まる文字列: 1 から始まるインデックスで文字列の文字要素にアクセスしている場合は、0 から始まるインデックスを使用するようにコードを書き直します(例を以下に示します)。
  • 不変文字列: 不変文字列内の文字を変更する場合は、2 つ以上の部分に分割したあとそれらを結合するか、TStringBuilder を使用します。
    たとえば、次のようなよくある操作(文字列内にインデックスを付け、その文字列の一部を変更する)を不変文字列に対して行うことはできません。
    S[1] := 'A';
上記のような文字列操作を使用した場合、Object Pascal モバイル コンパイラから「W1068 直接編集での文字列の変更は将来的にサポートされない可能性があります(Object Pascal)」という警告が出力されます。いずれ、この警告はエラーになります。現時点では、[<プロジェクト名> のプロジェクト オプション]ダイアログ ボックスの[ヒントと警告]ページでこの警告をエラーに変換できます。

モバイル アプリケーションおよびデスクトップ アプリケーションでは TStringHelper を使用して文字列を処理することを推奨

クラスまたはレコード ヘルパの System.SysUtils.TStringHelper は、文字列を扱う場合やプラットフォームに依存しないコードを作成する場合に役に立ちます。 TStringHelper は、すべての環境(デスクトップとモバイル)で使用できます。 TStringHelper では自動変換を行うため、0 から始まるインデックス方式の文字列でも 1 から始まるインデックス方式の文字列でも TStringHelper を使用できます。 内部的には、TStringHelper の関数とプロパティはすべて、インデックスが 0 から始まることを常に想定しています。

1 から始まるインデックス方式の文字列を扱う RTL 関数の一部は、以下の表に示すように、TStringHelper の関数に直接置き換えることができます。

Object Pascal RTL 関数
(1 から始まるインデックス方式)

TStringHelper の関数
(0 から始まるインデックス方式)*

System.Pos
  TStringHelper.IndexOf
System.Delete
  TStringHelper.Remove
System.Copy
  TStringHelper.Substring
System.SysUtils.Trim
TStringHelper.Trim
* ヘルパ関数は、1 から始まるインデックス方式の文字列でも 0 から始まるインデックス方式の文字列でも正常に動作します。

このトピックには、上記で推奨されているすべての置き換え(Delete から Remove への変更を除く)の例が示されています。
以下のサブトピックでは、1 から始まる文字列のインデックス方式から 0 から始まるインデックス方式にコードを移行するのに必要な変更について説明しています。

不変文字列の検査

不変文字列を検査するには、次のいずれかを行います。

  • コンパイラ指令 {$WARN IMMUTABLE_STRINGS <ON|ERROR>} を設定する。
  • [ヒントと警告]ページで、[直接編集での文字列の変更は....]の警告を[True]か[エラー]に設定する。

文字列を直接編集すると、"W1068 直接編集での文字列の変更は将来的にサポートされない可能性があります" という警告/エラー メッセージが表示されます。

インデックスが 1 から始まる文字列をインデックスが 0 から始まる文字列に変換する例

インデックスが 1 から始まる文字列をすべてのプラットフォームで動作するように変更する方法の例を以下に示します。

 function Trim(const S: string): string;
 var
   I, L: Integer;
 begin
   L := Length(S);
   I := 1;
   if (L > 0) and (S[I] > ' ') and (S[L] > ' ') then Exit(S);
   while (I <= L) and (S[I] <= ' ') do Inc(I);
   if I > L then Exit('');
   while S[L] <= ' ' do Dec(L);
   Result := Copy(S, I, L - I + 1);
 end;

TStringHelper.Chars を使用して文字列内の文字にアクセスする

CharsTStringHelper の有用なプロパティです。

Chars[Index]

この読み取り専用プロパティを使用すると、文字列のすべての文字にアクセスできます。Object Pascal モバイル コンパイラの場合、文字列のインデックスは常に 0 から始まることを忘れないでください。

Chars プロパティを使用して個々の文字にアクセスする例

 function Trim(const S: string): string;
 var
   I, L: Integer;
 begin
   L := S.Length - 1;
   I := 0;
   if (L > -1) and (S.Chars[I] > ' ') and (S.Chars[L] > ' ') then Exit(S);
   while (I <= L) and (S.Chars[I] <= ' ') do Inc(I);
   if I > L then Exit('');
   while S.Chars[L] <= ' ' do Dec(L);
   Result := S.SubString(I, L - I + 1);
 end;

System.Low および System.High を使用して文字列の最初および最後のインデックスにアクセスする

文字列に適用される Object Pascal 組み込みルーチン High および Low を使用できます。

  • Low(s) は、インデックスが 0 から始まる文字列の場合は 0 を返しますが、インデックスが 1 から始まる文字列の場合は 1 を返します。
  • High(s) は、インデックスが 0 から始まる文字列の場合は Length(s) - 1 を返しますが、インデックスが 1 から始まる文字列の場合は Length(s) を返します。

文字列の最初のインデックスを検出するには、以下のコードを使用します。

Low(string)

たとえば、次のようなよく使用される for 文は、

for I := 1 to Length(S) do

次のような for 文に置き換えることができます。

for I := Low(S) to High(S) do

もう 1 つの例としては、s = ''(空文字列)の場合:

  • インデックスが 0 から始まる文字列の場合: Low(s) = 0 および High(s) = -1
  • インデックスが 1 から始まる文字列の場合: Low(s) = -1 および High(s) = 0

System.Pos 関数を TStringHelper.IndexOf に置き換える

System.Pos 関数は、インデックスが 0 から始まる文字列ではなくインデックスが 1 から始まる文字列を扱います。 Pos の代わりに、TStringHelper.IndexOf を使用できます。 IndexOf 関数は、Value パラメータ(Char か文字列のどちらか)で指定された文字列が見つかった場合は、その文字列の 0 から始まるインデックス位置を返し、そうでない場合は -1 を返します。



例:

 s := 'The quick brown fox jumps over the lazy dog'; // s is a string type variable.
 WriteLn(Pos('ow', s));    // 13
 WriteLn(s.IndexOf('ow')); // 12

メモ: TStringHelper.IndexOf 関数は .NET での実装と似ています。ただし、Value で指定された文字列が空の場合、.NET 版では 0 を返しますが、Object Pascal RTL 版では -1 を返す点が異なります。

System.Copy 関数を TStringHelper.Substring に置き換える

System.Copy 関数は、インデックスが 0 から始まる文字列ではなくインデックスが 1 から始まる文字列を扱います。Copy の代わりに、以下のように TStringHelper.Substring を使用できます。

  function TStringHelper.Substring(StartIndex: Integer; Length: Integer): string;

Substring 関数は、対象文字列内の StartIndex で始まる指定された長さの部分文字列と同等の文字列を返します。StartIndex が対象文字列の長さ以上の場合、Substring は空の文字列を返します。Length が 0 以下の場合、Substring は空の文字列を返します。


例:

 s := '123456789'; // s は文字列型変数
 writeln( Copy(s, 2, 3) );     // 234
 writeln( s.Substring(1, 3) ); // 234

メモ: TStringHelper.Substring 関数は .NET での実装と似ています。ただし、.NET では、StartIndexLength がこのインスタンス内ではない位置を示している場合、または、StartIndex または Length が 0 より小さい場合、ArgumentOutOfRangeException 例外を発生させる点が異なります。 一方、Object Pascal RTL 版では例外は発生しません。 上記条件の場合、Substring は空の文字列を返します。

配列型を更新

すべての配列宣言を更新して動的配列にします。 以下のいずれかを使用します。

var x: array of Integer;
x: TArray<Integer>;

特定の長さで宣言された配列が含まれている構造体(レコード)を外部関数に渡さなければならない場合があります。これは、構造体の内部で "直接" 宣言された文字配列の場合に特に当てはまります。このような場合に限り、以下の宣言が可能です。

type
   rec = record
    Flags: Integer;
    Chars: array[MAX_PATH] of Char;
  end;

外部関数が配列を直接引数にする場合は、代わりに動的配列を使用します。 UTF8 文字配列の場合:

try-except ブロックで関数呼び出しを使用してハードウェア例外が必ず捕捉されるようにする

iOS デバイスのコンパイラでは、try ブロックにメソッドまたは関数の呼び出しが含まれている場合にのみ、except ブロックでハードウェア例外を捕捉できます。 これは、コンパイラの LLVM バックエンドに関係する相違点です。このバックエンドでは、try ブロックでメソッド/関数が呼び出されない場合は戻ることができません。

たとえば、ハードウェア例外を捕捉できる try-except ブロックの構成方法は以下のとおりです。

var
  P: ^Integer = nil;

procedure G1;
begin
  P^ := 42;
end;

begin
  try
    G1;
  except
    writeln('Catch:G1 - pass');
  end;
end.

インライン関数の場合は、たとえソース コード ブロックに関数呼び出しが含まれているように見えても、実際はそうではない可能性があります。LLVM でマシン命令が生成される時までにはインライン化処理が既に行われており、try ブロック内にはもう関数呼び出しは含まれていません。

アセンブリ言語ではなくアトミック組み込み関数を使用

Object Pascal モバイル コンパイラでは、組み込みアセンブラをサポートしていません。 メモリ値の交換、比較および交換、インクリメント、デクリメントをアトミックに行う必要がある場合は、新しいアトミック組み込み関数を使用できます。

アトミック操作は、マルチスレッド ロック プリミティブを実装するのに使用され、いわゆる "ロックフリー" 構造の実装に必要なプリミティブを提供します。必要な操作は、標準関数または "組み込み" 関数として実装されます。

マルチプラットフォーム アプリケーションでは、アトミック組み込み関数を AUTOREFCOUNT か NEXTGEN の条件シンボルのどちらかの {$IFDEF} 内で使用できます。

アトミック組み込み関数

Object Pascal モバイル コンパイラでサポートされているアトミック組み込み関数は以下のとおりです。

自動参照カウント

Object Pascal モバイル コンパイラでは、クラスの自動参照カウント(ARC)を使用していますが、これは、Object Pascal デスクトップ コンパイラで使用されている参照カウント方式とは異なります。ただし、すべての Object Pascal コンパイラではインターフェイス、文字列、動的配列の ARC をサポートしているため、事実上、Object Pascal モバイル コンパイラでは ARC の対象をクラスまで拡張しているにすぎません。ARC には、メモリの自動的な管理および破棄が組み込まれています。

メモ: ARC をサポートしている Object Pascal コンパイラの場合、互いに参照し合うオブジェクト インスタンスは、参照の一方に弱い参照の属性を付けなければ、メモリを事実上ロックするおそれがありま

ARC と弱い参照の詳細については、下記を参照してください。

関連項目