Delphi Tips
>> Index
● 01/22 短いファイル名を長いファイル名に変換したい(COM版)。
● 03/04 自作アプリにスペルチェックの機能をつけたい
● 03/04 正規表現の使える検索・置換えライブラリ
● 03/04 春分、秋分、夏至、冬至の求め方
● 08/22 プロパティ値を文字列に変換/逆変換
● 07/24 集合型変数の内部構造が知りたい/数値として処理したい。
● 07/10 浮動小数点数を整数に丸めるときの注意
● 06/27 ファイルの更新日時を得る
● 06/25 Delphi の格言
● 06/25 Ini ファイルに published プロパティを保存する方法
● 05/17 HTMLタグ表記の大文字・小文字変換を行う
● 05/17 文字列から括弧の中のみを削除する
● 05/17 列挙型(Enum)の値と文字列との変換
● 05/17 文字列や画像データをリソースに埋め込むためのコンポーネント
● 12/27 VBのMIDステートメント
● 06/02 トークンの切り出し
● 04/05 メモリマネージャのマルチスレッド対応 -- IsMultiThread
● 04/05 スレッドローカル変数 -- threadvar の使い方
● 09/08 整数値が奇数かどうかの判定
● 05/26 DLLの初期化・終了処理
● 04/06 Currency型の小数演算結果が不正
● 03/28 あるアドレスを基点とした、変位のアドレスの参照
● 10/14 終了処理の中の例外を見逃すな !
● 10/12 Pascal 版 auto_ptr -- Free の必要の無い高機能ポインタ
● 10/07 System ユニットの _ で始まる特殊ルーチンの呼び方
● 10/07 大きなソースブロックのコメントアウト
● 09/21 TListを特定のクラスのListにする方法
● 09/19 Sqr()の結果が負になる!?
● 09/16 Pascal で文字列を効率良く扱う(例:文字列を逆順にする)。
● 09/15 漢数字で位取り表示
● 09/15 子に override されたメソッドを孫クラスから呼び出す
● 09/09 文字列を TDateTime に変換する
● 09/09 経過日数を取得する
● 09/09 1時間後を取得する
● 09/05 数値の2進数への変換で効率的な方法
● 08/30 Pascal 版の yacc & lex
● 08/17 PASCAL ソースの整形ツール
● 08/14 Delphi2/3.x における Cardinal 型の妙な定義
● 08/14 Object Pascal のコーディングスタイル
● 08/13 TColor 値を文字列に変換する
● 07/14 基本的な階層プロパティ定義の例
● 07/09 @ 演算子の働き
● 05/14 文字数のカウント方法
● 05/08 改行コードの違いについて
● 02/11 ディスパッチインターフェイスとデュアルインターフェイスについて
● 02/11 和暦を西暦に直したい
● 02/11 可変長レコードの扱い方
● 02/08 *.h ファイルから *.pas ファイルを作るコンバータ
● 02/08 親クラスのプライベートフィールドにアクセスする
● 02/08 time_t を TDateTime に変換する
● 02/08 関数のパラメータを省略可能にしたい
● 02/08 Delphi3 の TStringList.CommaText の不具合
● 02/08 文字列の切り分け
● 02/08 New/Dispose に Pointer 型のポインタを渡すと。。。
最終更新: 6879 日前
0273 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/24 西坂良幸 rev 1.3 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2006/01/22 <> 編集
短いファイル名を長いファイル名に変換したい(COM版)。
エクスプローラは内部的には、短い名前を使っているようです。
ということは、シェルのCOMインターフェースのメソッドが使えるということです。
[Tips:233]のCOM版を考えてみましょう。
uses ShlObj,Comobj,ActiveX;
function ShortToLongFileName(const ShortName: string): string;
var
Desktop: IShellFolder;
pIDList: PITEMIDLIST;
NameS: String;
NameW: WideString;
Len : integer;
Buffer: array[0..MAX_PATH] of Char;
// 以下は値は使わない
pDummy: PCHAR;
pchEaten, Attributes: ULONG;
begin
pIDList := Nil;
// フルパス化
Len := GetFullPathName(PChar(ShortName), 0, PChar(result), pDummy);
SetLength(NameS, Len);
GetFullPathName(PChar(ShortName), Len, PChar(NameS), pDummy);
// ワイド文字列に転換
NameW := NameS;
// IShellFolderを生成(解放は自動)
OleCheck(SHGetDesktopFolder(Desktop));
// IDリストをえる
OleCheck(Desktop.ParseDisplayName(0, Nil, PWideChar(NameW), pchEaten, pIDList, Attributes));
if not SHGetPathFromIDList(pIDList, Buffer) then
raise EConvertError.Create('ファイルを変換できません。');
Result := StrPas(Buffer);
end;
WideStringを使うことを覗けば、IShellFolderのParseDisplayNameメソッドとSHGetPathFromIDList
を使うだけです。[Tips:233]よりすっきりしましたか。
※ 使用できるOS、バァージョンに注意して下さい。
参照: [Delphi-ML:7322] <その他Windows関連> <Windows> <ファイル>
0046 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2004/03/04 osamu 編集
自作アプリにスペルチェックの機能をつけたい
Spell Checker(辞書付き)というコンポーネントが、http://www.cs.monash.edu.au/~vtran/にあります。
Delphian World MLの過去ログにこれに関する話題があるそうです。
http://www.delphianworld.com/
参照: [Delphi-ML:19276] <文字列>
0119 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2004/03/04 osamu 編集
正規表現の使える検索・置換えライブラリ
正規表現を使った文字列探索/操作 : Rexp016.lzh
Perl 互換正規表現ユニット : BRegExp.lzh
DLは Delphian World で、
http://www.delphianworld.com/direct.html?id=MI0036
それか、この手のDLLがいろいろありますからそれらを使うという手がラクだと思います。
VWXW.DLL(Wz Editorで使われている)
作者のM.Ishidaさんのページ
http://www2h.meshnet.or.jp/~ishida/
jre.dll(秀丸エディタで使われている)
http://www.yamada-labs.com/software/spec/jre/
basp21.dll
http://www.hi-ho.ne.jp/babaq/
参照: [Delphi-ML:22272] <文字列>
0221 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/08/26 西坂良幸 rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2004/03/04 osamu 編集
春分、秋分、夏至、冬至の求め方
水野さんが明快なアイデアをだしています。[Delphi-ML:38594]
春分、秋分、夏至、冬至の求め方は、単純に1997年の春分、夏至、秋分、冬至の日時に、地球が太陽の周りを回る周期(365.2422日)を足したり引いたりするだけのものです。
ただ、ここ20〜30年くらいの春分、秋分の日は、上の式で求めたものと同じだそうです。
実際は、計算して人間(政府)が決めるらしいですね。
カレンダーなどの作成に大変便利です。
下記のコードは、時刻部を省略し、日付のみに変えてあります。
type
TSeason = (seVernalEquinox, seSummerSolstice, seAutummnalEquinox, seWinterSolstice);
function JapanHoliday4(myYear: Integer; mySeason: TSeason): TDateTime;
var
myDate: TDateTime;
begin
case Integer(mySeason) of
0: myDate:= StrToDateTime('97/03/20 22:55') + 365.2422 * (myYear - 1997); //春分
1: myDate:= StrToDateTime('97/06/22 03:55') + 365.2422 * (myYear - 1997); //夏至
2: myDate:= StrToDateTime('97/09/23 08:56') + 365.2422 * (myYear - 1997); //秋分
3: myDate:= StrToDateTime('97/12/21 18:49') + 365.2422 * (myYear - 1997); //冬至
end;
Result:= Int(myDate);
end;
またコンポーネントでは、
Delphian World < http://www.delphianworld.com/ >;
に「祝祭日カレンダー」というのがあります。
秋分・春分計算もしてくれる様です。
参照: [Delphi-ML:38539] [Delphi-ML:38594] <日時>
0346 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2003/08/22 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2003/08/22 osamu 編集
プロパティ値を文字列に変換/逆変換
published なプロパティを Ini に書き込みたい・から読み出したいというような時、プロパティ値を文字列に変換する必要が生じます。
Font プロパティを Ini/Registry に保存したい という投稿はFAQに近いですが、この記事はそういった要望への回答となります。
Enum/Set だけではなく、TColor のように「Enum ではないけど文字で書きこまれることがあるプロパティ」にも対応しています。
uses Classes, TypInfo;
function PropertyToString(obj: TPersistent; PropName: string): string;
var IntToIdent: TIntToIdent;
Ident: string;
begin
Result:= GetPropValue(obj, PropName);
if PropType(obj, PropName)=tkInteger then begin
IntToIdent := FindIntToIdent(GetPropInfo(obj, PropName).PropType^);
if Assigned(IntToIdent) then
IntToIdent(GetPropValue(obj, PropName), Result);
end;
end;
procedure StringToProperty(s: string; obj: TPersistent; PropName);
var IdentToInt: TIdentToInt;
i: Integer;
begin
if PropType(obj, PropName)==tkInteger then begin
IdentToInt:= FindIdentToInt(GetPropInfo(obj, PropName).PropType^);
if Assigned(IdentToInt) and IdentToInt(s, i) then begin
SetPropValue(obj, PropName, i);
end else begin
SetPropValue(obj, PropName, s);
end;
end else begin
SetPropValue(obj, PropName, s);
end;
end;
参照: [Delphi-ML:75958] <文字列>
0277 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/28 西坂良幸 rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2003/07/24 西坂良幸 編集
集合型変数の内部構造が知りたい/数値として処理したい。
集合型の変数は、フラッグの集まりです。
例えば、
type
TDbit = 0..25;
TDbits = set of TDbit;
と定義し、
Var D:TDbit;
D := [1,3,4,6]; の場合
内部的には,00000000 00000000 00000000 01011010B
とビット表現されています ~~~~~~~~~
したがって、Integer(D)とキャスト可能で、この値は90です
集合で使うバイト数は次の式で計算します。
ByteSize = (Max div 8) - (Min div 8) + 1
※ 最大255要素ですから、最大32バイト
Min と Max は集合の基本型の下限と上限です。
特定の要素 E が何バイト目かは次の式でわかります。
ByteNumber = (E div 8) - (Min div 8)
そのバイト内で何ビット目かは次の式でわかります。
BitNumber = E mod 8
※ E は要素の順序値を表します。
GetLogicalDrives関数は、 現在利用可能なディスクドライブを表すビットマスクを返しますので、これで、試してみましょう。返り値のビット位置0 (最下位ビット) はドライブAに対応します。
バイトサイズ → (25 div 8) - (0 div 8) + 1 = 4
で、4バイト、DWord型やLongint型が使えます。
TDBitsの最小値は → 0
0 mod 8 → 0 ですから、そのまま対応できます
procedure TForm1.Button1Click(Sender: TObject);
var
d: TDbit;
drvs: TDbits;
DName:string;
begin
ListBox1.Items.Clear;
// マシンのドライブをえる
DWORD(drvs) := GetLogicalDrives;
for d := Low(TDbit) to High(TDbit) do
begin
if d in drvs then
begin
DName := Chr(Integer('A') + d) + ':\';
// ドライブタイプをえる
case GetDriveType(PChar(Char(Ord('A')+d)+':\')) of
DRIVE_REMOVABLE: DName := DName + 'リマーブルデスク';
DRIVE_FIXED: DName := DName + 'ハードデスク';
DRIVE_REMOTE: DName := DName + 'ネットワークデスク';
DRIVE_CDROM: DName := DName + 'CDROMデスク';
DRIVE_RAMDISK: DName := DName + 'ラムデスク';
end;
ListBox1.Items.Add(DName);
end;
end;
end;
次に、
Type
TDrive = 'A'..'Z';
TDrives = Set of TDrive; を使えばどうでしょうか。
TDrivesの最小値は → 'A' 順序値 65
65 mod 8 → 1 ですから、
返り値の0ビットは、TDravesの1ビット目に対応します。
procedure TForm1.Button1Click(Sender: TObject);
var
d: TDrive;
drvs: TDrives;
DName:string;
begin
ListBox1.Items.Clear;
// マシンのドライブをえる−−左に1ビットシフトする
DWORD(drvs) := GetLogicalDrives shl 1;
for d := Low(TDrive) to High(TDrive) do
begin
if d in drvs then
begin
DName := Char(d) + ':\';
// ドライブタイプをえる
case GetDriveType(PChar(d +':\')) of
DRIVE_REMOVABLE: DName := DName + 'リマーブルデスク';
DRIVE_FIXED: DName := DName + 'ハードデスク';
DRIVE_REMOTE: DName := DName + 'ネットワークデスク';
DRIVE_CDROM: DName := DName + 'CDROMデスク';
DRIVE_RAMDISK: DName := DName + 'ラムデスク';
end;
ListBox1.Items.Add(DName);
end;
end;
end;
集合型を内部表現で使うのは、注意が必要です。
なるべく集合型として使いましょう。
なお、集合型の演算子として +,*,-,IN が定義されていますので、
not は、 全定義 - 値
xor は、 和集合 - 積集合で代用できます。
要素数は、4バイトまでの集合なら以下のようなぐあいですか?
function GetElements(Drvs: TDrives):DWORD;
var
i:TDrive;
begin
result := 0;
for i := Low(TDrive) to High(TDrive) do
begin
inc(result,DWORD(Drvs) and 1);
DWORD(Drvs) := DWORD(drvs) shr 1;
end;
end;
*リマーブルデスクって・・・(ぷ
参照: [Delphi-ML:9105] [Delphi-ML:42690] <計算>
0073 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2003/07/10 yamamoto 編集
浮動小数点数を整数に丸めるときの注意
Delphiには四捨五入用の関数として Round() がありますが、この関数は「Banker's Rounding」(銀行家の丸め)をするため、普通の四捨五入とは結果が異なることがあるため注意が必要です。
具体的には、引数がちょうど ???.5 となるとき、通常の四捨五入では答は 1 繰り上げた数になりますが、Round() では結果の一の位が偶数になるように切り上げ/切り捨てが行われます。(例: 0.5 は 0,1.5 は 2 になる)[Delphi-ML:4727][Delphi-ML:12693]から始まるスレッドも参考になります。
これに対して Trunc() はいつも小数部分を切り捨てて返しますので、普通の四捨五入をするには、
function Roundoff(X: Extended): Longint;
begin
if x >= 0 then Result := Trunc(x + 0.5)
else Result := Trunc(x - 0.5);
end;
のような関数を作って使用します。
Professional Edition 以上では、Math ライブラリに Floor()/Ceil() の関数もあります。
Int() 関数は Trunc() と同じ答を Extended 型で返します。
参照: [Delphi-ML:4727] [Delphi-ML:12693] [Delphi-ML:20194] [Delphi-ML:20205] <計算>
0173 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/03/08 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2003/06/27 osamu 編集
ファイルの更新日時を得る
Windows 的な方法としては、FindFirst/FindClose を使って、得られた TSerchRec を読みます。
別解として、PASCAL の FileAge 関数を使った方法を、ひきさんが以下のホームページで解説してくださっています。
[Delphi壁の穴]-[その二:システムを覗く]
http://hp.vector.co.jp/authors/VA009712/take/delphi/kabesys.htm#datetimefile1
http://hp.vector.co.jp/authors/VA009712/take/delphi/kabesys.htm#datetimefile2
参照: <日時> <ファイル>
0341 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2003/06/25 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2003/06/25 osamu 編集
Delphi の格言
[Delphi-ML:68385] から始まったツリーなんですが、まとめるに当たって編集者の個人的な好みが反映されています。ご容赦。
1) リソースの取得と開放は try〜finally〜end を使うべし。[Delphi-ML:68385]
2) Assert を使いまくれ。[Delphi-ML:68385]
3) メソッドを override するとき、override キーワードが抜けていないか常にチェックせよ。[Delphi-ML:68385]
4) Except で例外を処理するとき、raise で例外を再生成する必要がないか常にチェックせよ。[Delphi-ML:68385]
5) with文を使用するのは控えること[Delphi-ML:68385][Delphi-ML:68402]
6) フォームは自動生成ではなく自分で明示的に生成しよう![Delphi-ML:68386][Delphi-ML:68402]
7) AnsiString, WideString, Interface, Variant, 動的配列を含むレコードを動的に確保するときは New, Dispose を使うか、動的配列でメモリを確保せよ[Delphi-ML:68387][Delphi-ML:68423]
8) Application.ProcessMessages はその危険性を充分認識して使え。[Delphi-ML:68387]
9) VCL のソースをよく読もう。[Delphi-ML:68387]
10) RECORD に AnsiString/WideString/DynArray を不用意に使うな。[Delphi-ML:68391]
11) RECORD や ARRAY は PACKED が必要か考えろ。[Delphi-ML:68391]
12) 1行処理でも begin 〜 end は必ず付けよう。[Delphi-ML:68401]
13) 「名は体を表す」 変数名やクラス名、コンポーネント名は意味のわかる単語で。[Delphi-ML:68401]
14) バリアント変数の型変換はなるべく関数経由で。[Delphi-ML:68402]
15) try 〜 exceptはエラー情報の損失を最小に。[Delphi-ML:68402]
16) プロパティと同一名をメソッドの仮引数名に使わない。[Delphi-ML:68402]
17) イベントハンドラ内に細かい処理を書かない。[Delphi-ML:68402]
18) 他のフォーム内のコンポーネントは直接触らず、メソッドかプロパティ経由で。[Delphi-ML:68402]
13) 状態変化を伴うメソッド(C++での non-const method)は、なるべく関数でなく手続きに[Delphi-ML:68402]
14) プロジェクトで開発するときは基底モジュールを用意してフォームの継承を活用する[Delphi-ML:68408]
15) TThreadからVCL(TFormとか)を参照するときは必ずSynchonize! [Delphi-ML:68411]
16) public で 非virtual なメソッドは極力再定義するな。[Delphi-ML:68420]
17) オブジェクトが上位から継承する仮想メソッドを呼び出すときは inherited をつけて呼び出すか、そうしないかをよく考えよ。[Delphi-ML:68420]
18) デストラクタは、必ず destroy という名にして、必ず仮想メソッドにする。[Delphi-ML:68420]
19) コンストラクタは protected にしてはいけない[Delphi-ML:68420]
20) New で確保したメモリを破棄するときは、New に渡したポインタと同じ型のポインタを Dispose に渡せ[Delphi-ML:68423]
21) コメントは必要最小限にして、極力コードの可読性を高めよ[Delphi-ML:68429]
22) デフォルト引数は使わず、オーバーロードを使え。[Delphi-ML:68433]
23) {ObjExportsAll On} を忘れずに。[Delphi-ML:68433]
24) 名前だけが異なる複数のコンストラクタは作らない。[Delphi-ML:68433]
IDEの格言
1) アプリケーションをリリースするときは、map ファイルを作るのを忘れずに。[Delphi-ML:68385]
1b) 外に出したようなリリース版は、*.dcu をすべて保存しておいて Delphi を起動して「エラーの検索」を活用する[Delphi-ML:68572]
2) たくさんウィンドウを開いているときは ALT+0 を活用せよ。[Delphi-ML:68385]
3) コンパイルオプションは Ctrl+O+O でソースに埋め込むとIDE のコンパイルオプションの設定に左右されないソースが作れる。[Delphi-ML:68385]
4) デフォルトのコンパイルオプションの変更はトラブルの元。[Delphi-ML:68385]
5) Delphi をインストールしたら「環境オプション]→[設定]→[自動保存の設定]→[エディタファイル]をチェックしよう。[Delphi-ML:68420]
コンポーネントの格言
1) OnExit イベントでダイアログボックスを表示するな。[Delphi-ML:68385]
2) コンポーネントの Loaded メソッドが呼ばれることを仮定したコンポーネントを作ってはいけない。[Delphi-ML:68385]
3)自動生成されるコンポーネント名をそのままでほったらかすな。意味のわかりやすいコンポーネント名に変えろ。[Delphi-ML:68398][Delphi-ML:68402]
番外編
1) 『プログラミング作法』を読もう。[Delphi-ML:68404]
http://www.ascii.co.jp/books/detail/4-7561/4-7561-3649-4.html
人間関係の格言???[Delphi-ML:68543]
1.感情的に物事を判断しない。
2.例え正しい事でも、言い方や伝え方には気を使う。
3.コーディングスタイルには、ある種の宗教的要素が存在する事を認めて無駄な議論にならないように。
(みんな、自分のスタイルが正しいと信じている。)
4.ユーザーがどのように使い運用するのかを常に考える。
(ユーザーとのコミュニケーション無しに役立つソフトはできない。)
5.最初から完全なソフトウェアを目指そうとしてはいけない。
(実際に運用してみないと分からないもんです。
役立つソフトであれば仕様変更は必ずあります。必要最低限の機能を早くリリースするように心がけましょう。)
参照: [Delphi-ML:68385] <開発環境>
0338 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2003/06/25 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2003/06/25 osamu 編集
Ini ファイルに published プロパティを保存する方法
published プロパティを Ini ファイルなどに保存するためにテキストに/テキストから変換する方法を [Delphi-ML:75958] に載せました。
例えば、Font.Style プロパティを [fsBold] などとして保存することができます。
参照: [Delphi-ML:75958] <アプリケーション>
0326 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2002/05/17 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2002/05/17 osamu 編集
HTMLタグ表記の大文字・小文字変換を行う
コードはSJISを仮定します。
HTMLのコメント部分、""などで囲まれたパラメータ部分などは変換しません。
速度を気にするのであれば IsDBCSLeadByte は避けるべきです。自分で文字テーブルを作れば速くなります。ByteType の方は最適化されていたと思います。通常の用途では速度が気になることは無いかもしれませんね。
function ChangeHTMLTagCase(s:string; Lower:Boolean):string;
const
sNormal = 0;
sInComment= 1;
sInBracket= 2;
sInQuote = 3;
sInDQuote = 4;
sAfterEq = 5;
var
p: PChar;
state: Integer;
begin
state:= 0;
Result:= s;
UniqueString(Result);
p:= PChar(Result);
while p^<>#0 do begin
if IsDBCSLeadByte(Ord(p^)) and ((p+1)^<>#0) then begin
Inc(p, 2);
end else begin
case state of
sNormal:
if p^='<' then
if StrLComp(p, '<!--', 4)=0
then state:= sInComment
else state:= sInBracket;
sInComment:
if p^='-' then
if StrLComp(p, '-->', 3)=0 then
state:= sNormal;
sInBracket:
case p^ of
'''': state:= sInQuote;
'"': state:= sInDQuote;
'=': state:= sAfterEq;
'>': state:= sNormal;
'A'..'Z': if Lower then p^:= Chr(Ord(p^)+32);
'a'..'z': if not Lower then p^:= Chr(Ord(p^)-32);
end;
sInQuote:
case p^ of
'''': state:= sInBracket;
end;
sInDQuote:
case p^ of
'"': state:= sInBracket;
end;
sAfterEq:
case p^ of
'''': state:= sInQuote;
'"': state:= sInDQuote;
' ': state:= sInBracket;
'>': state:= sNormal;
end;
end;
Inc(p);
end;
end;
end;
参照: [Delphi-ML:66840] <WWW> <文字列> <通信>
0325 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2002/05/17 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2002/05/17 osamu 編集
文字列から括弧の中のみを削除する
特に、全角の括弧と半角の括弧に対応した方法を紹介します。一旦WideStringに直しているところが味噌で、こうすることによってコードの見通しが非常に良くなります。
function DeleteInBracket(s:string):string;
const
ZenAki = $FF08;
ZenToji= $FF09;
HanAki = $0028;
HanToji= $0029;
var
PrevPos,Len,BTimes,i:integer;
ss:WideString;
begin
ss := s;
i := 1; PrevPos := 1;Len := Length(ss);BTimes := 0;
result := '';
while i < Len+1 do begin
case Ord(ss[i]) of
ZenAki,HanAki:begin
Inc(BTimes);
if BTimes = 1 then result := result+Copy(ss,PrevPos,i-PrevPos);
end;
ZenToji,HanToji:begin
Dec(BTimes);
if BTimes = 0 then PrevPos := i+1;
end;
end;
inc(i);
If BTimes < 0 then Break;
end;
if BTimes = 0 then
result := result+Copy(ss,PrevPos,i-PrevPos)
else if BTimes < 0 then begin
ShowMessage('カッコが閉じていません-- ( が足りません' );
result := result+Copy(ss,PrevPos,i-PrevPos);
end else begin
ShowMessage('カッコが閉じていません-- ) が足りません' );
end;
end;
また、こういった文字列処理には正規表現を使うとかなり楽ができます。たとえば、
http://www2.big.or.jp/~osamu/Delphi/MyLibrary.htm
の BRegExp ユニットを使えば、
uses BRegExp;
function RemoveParentheses(text: string): string;
begin
Result:= text;
while brx.Subst('s/(\(|()[^()()]{3,1024}(\)|))//gk', Result) do;
end;
とするだけでできてしまいます。
s/// の k というオプションは、BREGEXP.DLL に固有のもので、正規表現中の SJIS を SJIS として認識してくれるので、2バイト文字にも特殊な処理はいらないです。
参照: [Delphi-ML:66865] <文字列>
0324 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2002/05/17 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2002/05/17 osamu 編集
列挙型(Enum)の値と文字列との変換
TBrushStyle や TPenStyle などの値を文字列として取り出したい、またはそのような文字列からもとの値に戻したい場合、TypInfoユニットにあるGetEnumNameやGetEnumValueを使用します。
uses TypInfo;
function AlignmentToString(Value: TAlignment): String;
begin
Result := GetEnumName(TypeInfo(TAlignment),Ord(Value));
end;
こんな感じですね。
参照: [Delphi-ML:66981] <文字列>
0320 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2002/05/17 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2002/05/17 osamu 編集
文字列や画像データをリソースに埋め込むためのコンポーネント
プログラム内部で使いたい文字列や画像のデータを、フォーム上に置いた非ビジュアルコンポーネントに保存しておくと、アプリケーションと別にファイルを配布する必要が無くなり、便利な場合があります。
そういった用途に使えるコードが[Delphi-ML:67285]のスレッドに紹介されています。ただし、画像データなどは文字列にエンコードされますので、そのままファイルを持つ場合に比べ、ファイルサイズは大きくなってしまいます。
[Delphi-ML:50664]で中村さんが紹介されたコンポーネントは任意のバイナリデータを内部に持てるようです。
参照: [Delphi-ML:67285] <画像> <文字列>
0313 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2001/12/05 濱野 rev 1.5 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2001/12/27 濱野 編集
VBのMIDステートメント
Delphi6でVB互換の関数などが追加されました。
Mid関数もそのひとつですが、部分文字列の挿入
であるMidステートメントはありません。
S = "1234567890"
Mid(S, 3, 5) = "ABC"
そこで逆Mid関数ともいうべきMidステートメントを
シミュレートする手続きを作ってみました。
procedure MidInsert(var S1: String; P, N: Integer; S2: String);
var
L: Integer;
begin
L := Length(S1);
if N > (L - P + 1) then
N := (L - P + 1);
if N > Length(S2) then
N := Length(S2);
Delete(S1, P, N);
Insert(Copy(S2, 1, N), S1, P);
end;
{ テスト例: }
procedure TForm1.Button1Click(Sender: TObject);
var
S: String;
begin
S := '1234567890';
MidInsert(S, 5, 3, 'ABC');
ShowMessage(S);
end;
Delphiの文字列型は配列と同様の扱いも出来るのでDelete, Insert, Copyを
使わない別の方法もあります。一時オブジェクトが作成されない分、
こちらの方が高速に動作するかもしれません。
procedure MidInsert(var S1: String; P, N: Integer; S2: String);
var
L, I: Integer;
begin
L := Length(S1);
if N > (L - P + 1) then
N := (L - P + 1);
if N > Length(S2) then
N := Length(S2);
StrMove(Pchar(@S1[P]), PChar(S2), N);
end;
参照: <文字列>
0309 D1D2D3 D4 D5 D6 D7 3.1 95 98 作成: 2001/05/25 濱野 rev 1.6 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2001/06/02 K.Takaoka 編集
トークンの切り出し
Delphiのライブラリにはstrtokのようなトークンの切り出し
を行なう関数や手続きが無い為、自作することになります。
以下のコードはCopyとIsDelimiter関数を使ったトークンの
切り出し例です。
{$APPTYPE CONSOLE}
program Typing;
uses
SysUtils;
procedure TypingToken(S, Delims: String);
var
TokenIn : Boolean; // 進行中であるかのフラグ
TokenS, TokenLen, // トークンの開始位置と長さ
I: Integer;
begin
TokenIn := False;
TokenS := 0;
TokenLen := 0;
for I := 1 to Length(S) do
begin
if not IsDelimiter(Delims, S, I) then begin
if TokenIn then
Inc(TokenLen)
else begin
TokenS := I;
TokenLen := 1;
TokenIn := True
end
end else begin
if TokenIn then begin
Writeln(Copy(S, TokenS, TokenLen));
TokenIn := False;
TokenLen := 0;
end
end
end;
if Tokenlen > 0 then
Writeln(Copy(S, TokenS, TokenLen));
end; { TypingToken }
const
Delims = #9#10#13'[](){};, ';
var
Buf: String;
begin
while not EOF(Input) do begin
Readln(Buf);
TypingToken(Buf, Delims)
end
end.
※リダイレクトしない場合、最後に[Ctrl]+[Z]を入力
して下さい。
参照: <文字列>
0284 D1 D2 D3 D4 D5 D6 D7 3.195 98 作成: 1999/10/12 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2001/04/05 濱野 編集
メモリマネージャのマルチスレッド対応 -- IsMultiThread
以下,
http://www.borland.co.jp/interbase/tech/ib_udf/delphi_udf_2.html
からの抜粋です。Delphi のメモリマネージャをマルチスレッドで利用する際に IsMultiThread グローバル変数を使わなければならないことを解説しています。
>>>>>>>>>>>>>>>> ここから
スレッドセーフティの問題を解決するために、Inpriseはメモリ管理ルーチンをクリティカル・セクションにラップすることによってメモリ・マネージャをスレッドセーフにしました。クリティカル・セクションについて説明することは本稿の範囲を外れているので詳しくは述べませんが、共用リソースへ秩序正しくアクセスできるようにするものであるとだけ考えておいてください。
Inpriseはその際に、クリティカル・セクションはあまりよく知られていない変数IsMultiThreadがTrueに設定されている場合しか使用されないという規則を設定しました。(IsMultiThreadは、すべてのDelphiユニットによって暗黙的に使用されるユニット 'System.pas' で定義されます。)
つまりこういうことなのです。Delphiは確かにスレッドセーフですが、それは開発者がそのように指示した場合だけです。アプリケーションやライブラリで複数のスレッドを扱う可能性がある場合は、必ずIsMultiThreadをTrueに設定しなければなりません。そうでないと、そのアプリケーションやライブラリはスレッドセーフになりません。(重要:IsMultiThreadはTThreadオブジェクトを使用した場合は暗黙的に設定されます。)
マルチスレッド環境でIsMultiThreadをTrueに設定することの必要性は、いくら強調してもしすぎることはありません。
参照: [Delphi-ML:43304]
0285 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/10/12 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2001/04/05 濱野 編集
スレッドローカル変数 -- threadvar の使い方
http://www.borland.co.jp/interbase/tech/ib_udf/delphi_udf_3.html
に解説が載っています。
参照:
0306 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2000/08/25 ITO, Sakuya rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2000/09/08 ITO, Sakuya 編集
整数値が奇数かどうかの判定
Delphiで整数値が奇数かどうかの判定をするには Pascal の標準関数 Odd を使用して次のようにします。
if Odd( int ) then
奇数のときの処理
else
偶数のときの処理
参照:
0301 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2000/05/26 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2000/05/26 osamu 編集
DLLの初期化・終了処理
D3 のヘルプでは DLL の終了処理に ExitProc を使うように書かれていますが、ExitProc のヘルプには ExitProc が下位互換性のためだけに残してあるものである旨の記述があります。実際(?)、ExitProc はパッケージの使用時に正常に動作しないようです。
このような問題を回避するためには、System ユニットにある DLLProc という変数を使います。
library MyLibrary;
uses Windows, SysUtils;
...
procedure DLLEntry(ul_reason_for_call: DWORD);
begin
case ul_reason_for_call of
// これらの値の意味については、Win32SDK の
// DLLEntryPoint の HELP を参照してください。
DLL_PROCESS_ATTACH:
;
DLL_PROCESS_DETACH:
;
DLL_THREAD_ATTACH:
;
DLL_THREAD_DETACH:
;
end;
end;
begin
DllProc := @DLLEntry;
DLLEntry(DLL_PROCESS_ATTACH);
end.
なお、初期化部分でエラーを発生させて DLL のロードを失敗させたい場合には、
(1) 初期化コードで System.ExitCode を 0以外に設定する。
(2) 初期化コード内で例外を起こす。
のいずれかを行います。
参照: [Delphi-ML:39301] [Delphi-ML:39147] <バグ> <DLL>
0299 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2000/04/06 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2000/04/06 osamu 編集
Currency型の小数演算結果が不正
Currency型で小数点以下の計算をすると、環境によっては結果が期待した値にならない場合があるそうです。
[例]
procedure TForm1.Button1Click(Sender: TObject);
var
num1, num2: Currency;
ret: string;
begin
num1 := StrToCurr('888888888888888.8888');
num2 := StrToCurr('0.8888');
ret := FormatCurr('#,##0.0000', num1 - num2);
ShowMessage(ret);
end;
この結果が『888888888888888.0128』になります。
この問題を解決するには、Currency や Extended 型の演算を行う前に、Set8087CW(Default8087CW); なるおまじないを入れておけばよいらしい。プロジェクトソースの一行目にでも書いておきましょう。
未確認ながら Delphi4 では治っていると言う報告があります。
[例]
begin
Set8087CW(Default8087CW);
num1 := StrToCurr('888888888888888.8888');
参照: [Delphi-ML:47431] <バグ>
0298 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 2000/03/25 濱野 rev 1.3 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 2000/03/28 濱野 編集
あるアドレスを基点とした、変位のアドレスの参照
バイト列を処理する場合、あるアドレスを基点に
そこから+nバイト目を数バイトずつ処理したい場合があります。
特に文字処理や画像処理ではこの様なことは頻繁に行われます。
Cであれば(p+1)* = '\0'; といったアドレッシングを行えますが、
Pascalでは強引な型キャストでも行わない限りこのような書き方は
行えません。
Pchar(Integer(p) + n)^ := #0;
のようになってしまいます。Integerとポインタのサイズが同じ
であるので、このような書き方でも通ります。
しかし、WORD,DWORDなどもっと大きな型の場合SizeOfなどを使っ
て型のサイズを意識した書き方を行わなければなりません。
PWORD(Integer(pw) + Sizeof(WORD) * n)^ := 0;
このような場合、必要なサイズ分の配列のポインタ型を定義して
やり、その変数に代入することでかなり近い事が行える様になり
ます。
例では、'Delphi 'という文字を、10回繰り返し配列
にセットしています。
type
CharArray = array[0..6] of char;
PCharArray = ^CharArray;
var
p: array[0..100] of Char;
C: PCharArray;
I : Integer;
begin
C := @p;
for I := 0 to 9 do
begin
C^[0] := 'D';
C^[1] := 'e';
C^[2] := 'l';
C^[3] := 'p';
C^[4] := 'h';
C^[5] := 'i';
C^[6] := ' ';
Inc(C);
end;
C^[0] := #0;
Edit1.Text := StrPas(p);
参照:
0286 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/10/14 K.Takaoka rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/10/14 K.Takaoka 編集
終了処理の中の例外を見逃すな !
Object Pascal の構造化例外処理の 1 つである try 〜 finally には思わぬメモリリークの可能性があります。
ObjectPascal言語ガイドには次のように記述されています。
-begin-
生成された例外が finally で処理されない場合,その例外は try...finally 文を越えて伝わり,try 節で既に生成されている例外は失われます。したがって,ほかの例外の伝播の妨げにならないよう,ローカルに生成された例外はすべて finally 節で処理するようにしてください。
-end-
つまり、
try
保護ブロック
finally
終了ブロック
end;
という記述の中で、保護ブロックの中で例外が発生した場合に、さらに終了ブロックで補足されない例外が発生してはいけないことになります。
A = TA.Create;
try
A.Start;
A.MethodCall; // ここで例外が起こる可能性がある !
finally
A.Stop; // ここでも例外が起こる可能性がある !
A.Free;
end;
ではなく
A = TA.Create;
try
A.Start;
A.MethodCall; // ここで例外が起こる可能性がある !
finally
try
A.Stop; // ここでも例外が起こる可能背がある !
except
// A.Stop が失敗したときに必要な処理があればココへ
end;
A.Free;
end;
などと、finally 節内での例外を補足し処理しなければなりません。
# 補足し処理するので try 〜 except 構文を用います。
参照: [Delphi-ML:41492] [Delphi-ML:41495] []
0283 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/10/12 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/10/12 osamu 編集
Pascal 版 auto_ptr -- Free の必要の無い高機能ポインタ
[Delphi-ML:41732] で、C++ での auto_ptr にあたる高機能ポインタを Delphi 上で実現する方法を中村@NECさんが紹介してくださっています。使用後に Free することに煩わされること無く、TObject/record/MemoryBlock/string/自動配列 を動的に確保して利用することができます。
これを使うと、さまざまなリソースを変数と同じ寿命にしたり、レコードやオブジェクトが破棄されるときに、レコードやオブジェクトからポイントされているさまざまな資源を自動破棄できたりします。
finally より強力です。
技術的には、Interface の参照カウントと、System._Dispose を有効活用しています。_Dispose に付いては [Tips:282] も参照。
例:使用した レコードを自動破棄する
uses AutoPointer;
type
TXXRecord = record
str: string;
:
:
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var pRecord: ^TXXRecord;
AutoPtr: IAutoPtr;
begin
New(pRecord);
AutoPtr := TAutoPtr.Create(pRecord, TypeInfo(TXXRecord));
:
:
end; // ここで レコードが自動破棄される
参照: [Delphi-ML:41732] [Tips:282]
0282 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/10/07 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/10/07 osamu 編集
System ユニットの _ で始まる特殊ルーチンの呼び方
_Dispose など、System ユニットには _ で始まる特殊なルーチンがいくつかある。これを無理やり自分で呼び出したいときには,
procedure __Dispose(p: Pointer; typeInfo: Pointer);
asm
call System.@Dispose
end;
のようにする。
そんなん何につかうん?
と言う人は [Tips:283] を見てみよう。
参照: [Delphi-ML:41727]
0279 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/10/07 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/10/07 osamu 編集
大きなソースブロックのコメントアウト
コーディング中に数十行にわたるコードを一気にコメントアウトしたくなること、ありません?そんな時、どうしましょ?
コード中で {} を使っていなければ、{} でくくればおしまいですが,{} が入っていると、コメントがそこで途切れてしまいます。
以下のような方法を紹介します。
1) {$IFDEF} を使う
{$IFDEF DUMM_DUMMY_DUMMY}
ちょっとコメントアウト
{ コメントアウトしたいコード }
..
{$ENDIF}
2) (* *) を使う
(* や *) は、{ や } と同等に使えますが,コンパイラはこれらを区別するため,コード中に { } を使ってあっても、 (* や *) を使っていなければ,これが便利です。
(* ちょっとコメントアウト
{ コメントアウトしたいコード }
...
*)
参照: [Delphi-ML:34048]
0254 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/15 おばQ rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/21 おばQ 編集
TListを特定のクラスのListにする方法
みなさん、TListって使ってます?
TListはItemsプロパティで、いろんなオブジェクトにアクセスできる
とても便利なコレクションクラス(リスト)です。
これを利用すればコンポーネント配列なんて特に必要なくなりますし、
自分で作ったクラスが保持できます。
しかし、こういうコーディングしていませんか?
TMyClass( TList.Items[i] ).MyClassProperty
実際には特定のクラスのみItemsに保持したい場合が多いのに
TListはPointerを保持する汎用的なListなので
キャストしないとプロパティやメソッドが使えません。
TListのItemにアクセスするたびに
毎回キャストしなくてはいけないのであれば、大変めんどうですし
コーディング量も増えて綺麗ではありません。
そういう場合はTListを継承してしまいましょう。
とてもあっさりと可能です。
type節に追加します
TMyClassList = class(TList)
private
function Get(Index: Integer): TMyClass;
procedure Put(Index: Integer; const Value: TMyClass);
public
property Items[Index: Integer]: TMyClass read Get write Put; default;
end;
実装部には
{ TMyClassList }
function TMyClassList.Get(Index: Integer): TMyClass;
begin
Result := TMyClass( inherited Get(Index) );
end;
procedure TMyClassList.Put(Index: Integer; const Value: TMyClass);
begin
inherited Put( Index, Value );
end;
TMyClassは適当に変えてください。
これだけで、TListを使うのとほとんど同様に
TMyClassList.Items[i].MyClassProperty
という風にスッキリと使えます。
また、TListを継承する時ついでに
TMyClassList = class(TList)
public
destructor Destroy; override;
…
destructor TMyClassList.Destroy;
var
i: Integer;
begin
for i := 0 to Self.Count - 1 do
Self.Items[i].Free;
inherited Destroy;
end;
と実装しておくとListが破棄される時に
所有するItemも一緒に破棄されるので
結構便利に使えるでしょう。
参照:
0024 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.5 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/19 西坂良幸 編集
Sqr()の結果が負になる!?
Sqr()関数は引数にIntegerを渡すと結果にIntegerが返ってきます。
ヘルプで
function Sqr(X: Real): Real;
と書いてあるにもかかわらずです。
Sqr()関数は引数が整数型だと結果は整数型で返り、
引数が実数型だと結果は実数型で返ります。
これが仕様です。
なお、整数型を渡した場合、オーバーフローのチェックは行われません。そのため渡す値によっては
sqr(x)+sqr(y)
の結果が負になる場合があり、これを実行時に検出することはできません。自乗した場合の結果が Integer の制限を越えることが予想できる場合には、引数を実数型にキャストして渡す必要があります。
参照: [Delphi-ML:7250] <バグ> <計算>
0234 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/04 西坂良幸 rev 1.8 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/16 osamu 編集
Pascal で文字列を効率良く扱う(例:文字列を逆順にする)。
MLの全体がコーディングの勉強ですが、[Delphi-ML:42129] から始まる一連のスレッド「文の逆」は、Pascal で文字列(文)を効率良く扱う様々なコーディングの例が示されています。いろいろな方法があることがわかりますね。勉強になります。
実装で気をつけるのは、2バイト文字と、CR/LF の順を入れ替えないようにしなければならないというところです。
(1) 何も考えないで作る
function StrReverse(s: string): string;
var i: Integer;
LeadBytes: set of Char;
begin
i:= 1;
Result:= '';
LeadBytes:= SysUtils.LeadBytes + [#13];
while i<=Length(s) do begin
if s[i] in LeadBytes then begin
Result:= Copy(s,i,2) + Result;
i:= i+2;
end else begin
Result:= Copy(s,i,1) + Result;
i:= i+1;
end;
end;
end;
この関数が、ループ中で何百回も一度に呼ばれるとは考えにくいので、実はこれで十分かもしれませんが、後学のために工夫の余地を考えてみることにしましょう。
(2) [Delphi-ML:42142] 基本的な形です。(ただし、処理速度を考慮しています。)
function StrReverse(const s: string): string;
var
i, L: integer;
begin
L := Length(s);
SetLength(result, L);
i := 1;
while i <= L do
begin
if (s[i] in SysUtils.LeadBytes) or
((s[i] = #13) and (S[i+1] = #10))
then
begin
result[L-i] := s[i];
result[L-i+1] := s[i+1];
Inc(i, 2);
end
else
begin
result[L-i+1] := s[i];
Inc(i);
end;
end;
end;
コード(1)も同じなんですが、実はこれデータによっては s[Length(s)+1] を参照してしまいます。不正な文字列の場合は無視するにしても、改行を常に #13#10 と仮定して(1)のように比較部分を単純化するか、i=Length(s) のときには s[i+1] を参照しないか、どちらかにしないと意味が無いです。
ま、それは置いといて、、、
文字列の生成回数を減らす(Copy 関数は値を返すのに string を生成する)
文字列の足し算の回数を減らす(足し算も内部で新しい文字列を作る)
関数の呼び出しを最小限にする(Length の値を L にキャッシュ)
i:=i+1 の代わりに Inc 関数を使う(C での ++ 演算子と同等)
引数に const をつける(これも文字列の生成を押さえられる)
などが高速化部分。効果が大きい順に並べてみました。今回の目的には後ろの3つはたいした違いは生みません。特に、Length 関数のコストはそれほど大きくないことは覚えておいても良いと思います。逆に文字列の連結は時間がかかるので注意。
(3) [Delphi-ML:42157] ポインタの使い方の見本のような例ですね。
function StrReverse(const s: string): string;
var
p, pn, pr: PChar;
begin
SetLength(Result, Length(s));
p := PChar(s);
pr := PChar(Result) + Length(s);
while p^ <> #0 do begin
if (p^ = #13) and ((p+1)^ = #10) then
pn := p + 2
else
pn := CharNext(p);
System.Move(p^, (pr-(pn-p))^, pn-p); Dec(pr, pn-p);
p := pn;
end;
end;
コード(2)から、さらに [ ] の使用を無くして高速化を図ったものです。Pascal の [ ] は C の [ ] に比べてかなり時間がかかります。CharNext という関数も覚えていてよさそうですね。でも高々2バイトの転送に System.Move を使うよりは、(2) のように SysUtils.LeadBytes を使って判定して1バイトずつ ^ で転送した方がずっと速いでしょう。
2バイト文字の判断で2バイト目が #0 でないことを確認しないと、不正なデータが与えられた時に、メモリを破壊してシステムをハングアップさせる危険性があります。p_end:=PStr(s)+Length(s); としておいて、while の比較を、p<=p_end とすればハングアップを防ぎつつさらに高速化にもなりますね。
(4) [Delphi-ML:42148] ポイントは再帰ですね。
function StrReverse(const s: string): string;
var
L: integer;
begin
result := '';
L := Length(s);
if L = 0 then
exit;
if IsDBCSLeadByte(Byte(s[1])) then
result := StrReverse(copy(s, 3, L-2)) + s[1] + s[2]
else
result := StrReverse(copy(s, 2, L-1)) + s[1];
end;
コードはすっきりしますが、速度的には(1)よりずっと悪いです。なにより、長〜い文字列を渡すと再帰が深くなりすぎて、スタックオーバーフローを起こしかねないので、この目的には使えません。えっと、この例だけ改行文字を考慮していないですね。
(5) [Delphi-ML:42155] WideStringを使う発想です。
function StrReverse(s: WideString): WideString;
const
WCharCR : WideChar = #$0d;
WCharLF : WideChar = #$0a;
var
i : Integer;
begin
SetLength(Result, Length(s));
for i:=1 to Length(s) do begin
if (s[i] = WCharCR) and (s[i+1] = WCharLF) then begin
s[i] := WCharLF; s[i+1] := WCharCR;
end;
Result[Length(s)-i+1] := s[i];
end;
end;
WideString を使うことで、2バイト文字を考えなくても良くなります。もし、改行文字が含まれないことが分かっていれば、if 文は必要無くなりますから、非常にすっきりしたコードになります。WideString <-> string の変換はキャストの必要無く自動で行われます。変換は時間がかかりますが、初めと終わりの2回だけなので、この場合には気にする必要は無いでしょう。美しいコードです。
(6) [Delphi-ML:42173] 文字列の両端から交換していこうというアイディア
function StrReverse(const s: string): string;
var
len, i : integer;
L, R : pchar;
tmp : char;
p : pchar;
begin
Result := s;
len := length(s); if len = 0 then exit ;
p := PChar(Result);
L := p; R := L + len - 1;
while L < R do
begin
if (L^ in Sysutils.LeadBytes) or
((L^ = #13) and ((L+1)^ = #10)) then
begin
tmp := L^; L^ := (L+1)^; (L+1)^ := tmp;
inc(L, 2);
end
else
inc(L);
end;
L := p; R := L + len - 1;
for i := 1 to (len div 2) do
begin
tmp := L^; L^ := R^; R^ := tmp;
inc(L); Dec(R);
end;
end;
うーん何やら複雑。改行文字や2バイト文字が無くて、なおかつ関数が
procedure StrReverse(var s: string): string;
のように、直接引数を書きかえるものなら、メリットはあるかもしれません。。。この場合には、ほぼ最後の for 文だけになってしまいます。
基本的に、ループの中で何度も繰り返し呼ばれるので無ければ、実行速度を求めて読みにくいコードを書くのは愚とされます。したがって、この問題に限って言えば、(1) や (5) のように分かりやすく書けるものがお勧めです。
ただし、実行効率を考慮しないといっても限度があり、(4) のように、長い文字列を渡すとスタックオーバーフローを起こすようなコードは、いかに見やすくても不適当でしょう。
あなたならどう書きますか? アイデアある方はどんどん加筆して下さい。
参照: [Delphi-ML:42134] <文字列>
0261 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/15 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/15 osamu 編集
漢数字で位取り表示
数字を漢数字で位取り表示(4桁単位)する関数を作ってみました。
function RhKnjNumber(const Num: Currency): string;
begin
Result := FormatFloat('####"兆"####"億"####"万"####円', Num);
while True do {余計な文字を取り去る}
if ByteType(Result, 1) = mbLeadByte then
Result := Copy(Result, 3, Length(Result) - 2)
else
Break;
end;
参照: [Delphi-ML:34500] <文字列>
0259 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/15 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/15 osamu 編集
子に override されたメソッドを孫クラスから呼び出す
TClassA というクラスがあって、TClassB がその子供 TClassC がさらにその子供であるとします。
TClassB が override した TClassA の手続き AProc を、TClassB 内から呼び出すには、inherited AProc; と書けば良いのは皆さんご存知だと思いますが、TClassC の中から呼び出すにはちょっとしたテクニックが必要になります。
type
TClassA = class
protected
procedure AProc; virtual;
end;
TClassB = class(TClassA)
protected
procedure AProc; override;
end;
とし、
type
TClassC = class(TClassB)
protected
procedure AProc; override;
end;
という場合です。これには、ダミーのクラス TClassD を使って、以下のようにします。
procedure TClassC.AProc;
type
TClassD = class(TClassA) end; {4番目のダミークラス}
TAProc = procedure of object; { AProc の型 }
var
method: TMethod;
begin
with method do begin
Data := Self;
Code := @TClassD.AProc;
end;
TAProc(method);
// TClassC 特有の処理
end;
スコープをうまく操作して「Delphi を騙してる」感じです。
参照: [Delphi-ML:2958] [Delphi-ML:34246]
0250 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/09 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/09 osamu 編集
文字列を TDateTime に変換する
文字通り StrToDateTime という関数もあるんですが、これは yyyy/mm/dd などの形式にしか対応していません。ちょっとひねって VarToDateTime を使えば、ほぼどんな形式でもうまく変換できます。string -> Variant は自動で変換されるので、そのまま文字列を渡すだけで使えます。
MyDateTime := VarToDateTime('平成1年1月1日午後1時1分1秒');
全角数字もOKですが、さすがに '平成九年九月九日' は無理でした。
VarToDateTime('1999/1/2') = VarToDateTime('1/2/1999');
の結果は、True になりました。
注意点として、VerToDateTime は StrToDateTime と違い、ShortDateFormat グローバル変数を見ていないようです。
ShortDateFormat:='DD/MM/YY';
ShowMessage(FloatToStr(VarToDateTime('2/1/1999')-VarToDateTime('平成11年2月1日')));
の結果は 0 になります。
参照: <文字列> <日時>
0251 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/09 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/09 osamu 編集
経過日数を取得する
> 1998年12月3日 − 1998年11月29日 = 4日
> という答えが欲しいのですが、年・月をまたがっても、
> 日数が帰って来るようにするにはどうしたらよいでしょう?
TDateTime 型は日数を単位とする double 値 (1899/12/30 0:0:0 からの経過日数) なので、Trunc して引き算すれば望みの結果が得られます。
Days := Trunc(Now) - VarToDateTime('昭和20年8月15日');
あるいは、D3 以前ならば、
Days := Trunc(Now) - EncodeDate(1945,8,15);
参照: [Delphi-ML:33577] <日時>
0249 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/09/09 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/09 osamu 編集
1時間後を取得する
TDateTime 型は日数を単位とする double 値 (1899/12/30 0:0:0 からの経過日数) なので、一時間後の時刻を得るには 1/24 を足してやれば良いです。1時間20分後などを得るには
a := Now + StrToTime('1:20:00'); // 現時刻の1時間20分後
b := Now + EncodeTime(1, 20, 0, 0); // これも1時間20分後
EncodeDate、DecodeDate、EncodeTime、DecodeTimeを使うと環境に依存しないので、a より b がおすすめです。
参照: [Delphi-ML:33577] <日時>
0225 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/08/28 西坂良幸 rev 1.4 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/09/05 藤中理香 編集
数値の2進数への変換で効率的な方法
ML[Delphi-ML:32617]〜[Delphi-ML:32720]のスレッドにいろいろな変換関数例が示されています。
ベンチワークしたわけではありませんが、効率ということでは、中川さんのコードがお勧めでは?
「10進から2進数への変換に標準関数はありませんが、デルファイではTBitsという
似て非なるクラスがあります。(定義はclassesの中)
そこを見てみますと、インラインアセンブラが使われています。ということで、題材的に
インラインアセンブラが向いているのではないかと思い作ってみました。」[Delphi-ML:32720]中川
以下、中川さんの例示コードをIntToHex型パラメータの関数にしたものです。(4桁ごとのスペースはありません)
function IntToBin(Int: LongInt; Digits: Integer = 32): string;
// 変換の関数
procedure ToBin(Int: LongInt; x: pointer);
asm
push ebx; // ebxは使用不可なので保存
mov edx,x; // 文字列の先頭アドレス
mov eax,int; // 変換する整数
mov ecx,32; // 32回のループのセット
@MAWASU:
mov bl,'0'; // とりあえず文字「0」とする
sal eax,1; // 整数を左シフトして、1ビットを取り出す
jnc @TOBU; // ビットが0ならそのまま
inc bx; // ビットが1ならば文字を「1」にする
@TOBU:
mov [edx],bl; // 文字を書き込む
inc edx; // 文字列のポインタを進める。
loop @MAWASU; // これを繰り返す
mov bl,0; // 文字列の最後は空文字
mov [edx],bl; // 最後の空文字の書き込み
pop ebx; // ebxを呼び戻す
end;
var
Buff: array[0..63] of Char;
begin
if (Digits> 1) and (Digits <= 32) then
begin
ToBin(Int, @Buff);
result := StrPas(Buff + 32 - Digits);
exit;
end;
raise EConvertError.Create('第二パラメータが範囲外です。');
end;
// テスト
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text := IntToBin(16,8);
Edit2.Text := IntToBin(-1);
Edit3.text := IntToBin(Integer($F0F0F0F0));
end;
参照: [Delphi-ML:32720] <計算>
0229 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/08/30 osamu rev 1.3 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/08/30 osamu 編集
Pascal 版の yacc & lex
Turbo PASCAL 版の yacc と lex のセットです。
http://www.idiom.com/free-compilers/LANG/Lex-1.html
http://www.filelibrary.com/Contents/DOS/77/17.html
のあたりに、lyprg.zip とか、 lex_yacc.zip とかいう名前で、
転がっています。
数式評価ルーチンくらいならこれで簡単に書けますね。
どっかに Object Pascal のパーサ転がってませんかね?
参照: <開発環境>
0205 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/08/17 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/08/17 osamu 編集
PASCAL ソースの整形ツール
Tips Lounge で花井さんが紹介してくれました。Delphi Super Page
http://sunsite.icm.edu.pl/delphi/
にある delforex.zip というファイルです。D2-D4 にインストールして使えるエキスパートで、他人の書いた汚い(?)ソースをワンキーで自分好みに変換することが出来ます。便利々々。
参照:
0089 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/08/14 osamu 編集
Delphi2/3.x における Cardinal 型の妙な定義
>Delphi3.1のWindows.pasの中で、
> DWORD = Integer; と定義されていますが、
> DWORD = Cardinal; ではないのでしょうか。
>
>また、ヘルプ及びマニュアルに
> Cardinal 0〜2147483647 符号なし 32 ビットと書かれていますが、
> 0〜4294967295 ではないのでしょうか.。
残念ながらマニュアルの通りです。
また Cardinal 型の変数には マイナス値や 2147483647 を超える定数値を代入することは出来ませんが、「オプション」の「範囲チェックエラー」をオフにしておくと 計算の結果負数を持つことは出来ます。
例えば
var i: Cardinal;
:
:
i := 10;
i := i - 20;
if i >= 0 then ShowMessage('Plus')
else ShowMessage('Minus');
は Minus と表示されます。つまり Integer とほとんど変わりません。
[Delphi-ML:23120]によれば、Delphi4 ではちゃんと Cardinal=(0..2^32-1) になっているようです。
参照: [Delphi-ML:20567] [Delphi-ML:23120] <バグ>
0180 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/05/10 osamu rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/08/14 osamu 編集
Object Pascal のコーディングスタイル
> 最近 Delphi でプログラムを書いているのですが、インデントの仕方や
> begin, end をどこに書いたらいいのか迷っています。
以下の書籍にコーディングスタンダードが記載されています。購入していないので何章かは忘れました。
「Delphi 4 プログラミング技法 Vol.1 Win32プログラミングテクニック 」
ザビエル・パチェコ+スティーブ・テイクセラ(日向 俊二 訳)
ボーランドの開発者も使用しているらしく、VCLやサンプルプログラムでは、大半がこのガイドにしたがっています。
また、以下のサイト(著者のサイト)に同様の内容があります。(但し英語)
Delphi 4 Developer's Guide
http://www.xapware.com/ddg/
また、[Delphi-Doc:00906]で中村@NECさんが、Borland の Delphi チームが実践しているコーディングスタイルを紹介したページを報告してくださいました。
http://www.borland.com/techvoyage/articles/DelphiStyle/StyleGuide.html
参照: [Delphi-ML:38485]
0203 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/08/13 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/08/13 osamu 編集
TColor 値を文字列に変換する
レジストリに TColor 型の値を格納したいような場合、ColorToString 関数を使えば TColor から AnsiString に変換できるので、あとは TRegistry の WriteString で書き出してやればいいと思います。
反対に AnsiString から TColor に戻すのは StringToColor 関数を使えばいいです。
詳しくはヘルプを見てください。
参照: [builder:17839] <文字列> <描画>
0200 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/07/14 久保田 宏光 rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/07/14 久保田 宏光 編集
基本的な階層プロパティ定義の例
// 子にするクラスの宣言
// ・TPersistentから継承する
// ・プロパティ内に、さらに他のクラスを定義する場合はAssignでコピーする。
// 直接代入を許さないように留意する(SetListメソッドを参照)
// メソッドをprotectedにする場合はvirtualにする。
// また、隠蔽したい場合はprivateに置く。
// ※protected/privateが良く分からない場合はprotected/virtualが無難
// ・クラスにAssignを実装する必要がある。
type
TChild = class(TPersistent)
private
FList:TStrings;
public
constructor Create;
destructor Destroy; override;
procedure Assign(Source:TPersistent); override ;
protected
procedure SetList(Value:TStrings); virtual;
published
property List :TStrings read FList write SetList;
end;
//親側のクラス宣言
//・こちらでも、Childプロパティの書き込みにSetChildを実装する。
type
TParent = class(TComponent)
Private
FChild:TChild;
public
constructor Create(AOwner:TComponent); override;
destructor Destroy; override;
protected
procedure SetChild(Value:TChild); override;
published
property Child :TChild read FChild write SetChild;
end;
//実現部(子)
//・抽象クラスで宣言したプロパティは、こちらで実装クラスとして
// create/freeする。
//・プロパティのwriteはメソッドになっている。間違っても
// List := TStringList.Create
// ~~~~ などとしないように。
//・コンポーネントがTStrings内でObjectsを使用している場合のみ、
// free時に解放する。
//
// 例 )
// for i:=0 to FList.count - 1 to
// FList.Objects[i].Free;
// FList.Free;
constructor TChild.Create;
begin
inherited Create;
FList := TStringList.Create;
end;
destructor TChild.Destroy;
begin
FList.Free;
inherited Destroy;
end;
procedure TChildSetList(Value:TStrings);
begin
if Value <> FList then
FList.Assign(Value);
end;
Procedure TChild.Assign(Source:TPersistent);
begin
if Source is TChild then
FList.Assign(TChild(Source).List);
inherited Assign(Source);
end;
//実現部(親)
//・FChildをcreate/freeするのを忘れないように。
constructor THyperText.Create(AOwner:TComponent);
begin
inherited Create(AOWner);
FChild := TChild.Create;
end;
destructor THyperText.Destroy;
begin
FChild.Free;
inherited Destroy;
end;
参照: <コンポーネント開発>
0197 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/07/09 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/07/09 osamu 編集
@ 演算子の働き
Hidetoshi NAKAI さんは書きました:
> すごく初歩的な質問だと想うのですが、変数の前に
> "@" (アットマーク) を付加すると、どういう効果が
> あるのでしょうか?
初歩的でもないですよ。結構知らない人がいます。
手続き(メソッド)型でない変数の場合:
その変数を指すポインタを返します。ポインタの型は
{$T-} の状態では Pointer 型に、{$T+} の状態では
^[変数の型] になります。
手続き型(メソッド型)の変数の場合:
変数が手続きの呼び出しと解釈されることを抑止し、
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
手続き(メソッド)へのポインタが入っている変数として
扱うことを強制します。従って変数のポインタを 取得
したい場合は @@A という具合に @ を2個書きます。
手続き名、関数名、メソッド名の場合:
手続き、関数、メソッドもエントリポイントを指す
ポインタ(メソッドポインタ)が返ります。
詳しくは @ 演算子のヘルプを見てください。
参照: [Delphi-ML:32704]
0186 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/05/14 おばQ rev 1.2 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/05/14 おばQ 編集
文字数のカウント方法
普通にLength関数で文字数をカウントすると半角文字の数になります。例えばLength('あいうえおABCDE')は15です。
全角文字を1文字としてカウントするにはWideStringを使います。Length(WideString('あいうえおABCDE'))は10です。
WideStringにキャストするより高速な手法としてByteToCharLenを使う方法もあります。
ByteToCharLen('あいうえおABCDE', Length('あいうえおABCDE'))は10です。
参照: <文字列>
0181 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/05/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/05/08 osamu 編集
改行コードの違いについて
テキストを扱っていると、何かと改行コードについて気になります。
改行コードは Windows では CRLF、Macでは CR のみ、Unix 系では LF のみで示されます。制御コードとして CR が復帰 LF が改行と呼ばれます。Delphiでは CR=Char(13) または #13、LF=Char(10) または #10 として指定できます。
Label 等ではテキストの改行を行う場合、CRLF, CRのみ, LFのみ、どの場合でも改行が行えますので
Label.caption := '1行目'#10'2行目'という表現も可能です。
また、改行コードが、CR のみ、LF のみ、LFCR になっているテキストを CRLF シーケンス(つまりWindows標準形式)に変換する関数としてAdjustLineBreaks というものがあります。逆に CRLF から、CR のみLF のみ、に変換するには
function DeleteCR(S: string): string;
begin
while pos(#13,S)<>0 do
Delete(S,pos(#13,S),1);
Result := S;
end;
このようなものを使ってください。DeleteLFも作れるでしょう
(D4以降ではStringReplace関数が用意されていますのでそちらを使いましょう)
参照: [Delphi-ML:9035] <文字列>
0157 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/11 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/11 osamu 編集
ディスパッチインターフェイスとデュアルインターフェイスについて
> さて、マニュアルを読んでみました。ですが、結構読みづらいです(@ @)。
> そこで、ちょっとほぐしてみました。
> (解釈に間違いがあればご指摘お願いいたします)
とのことですので、とりあえず覗いてみましょう。
参照: [Delphi-ML:30627] [Delphi-ML:30649] [Delphi-ML:30684] <Windows>
0151 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/11 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/11 osamu 編集
和暦を西暦に直したい
こんなのはどうでしょう?
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key<>#13 then
Exit;
Key:= #0;
with Edit1 do
try
Text:= FormatDateTime('yyyy/m/d',VarToDateTime(Text));
SelectAll;
except
on EVariantError do begin
SelectAll;
raise Exception.Create('入力が不正です');
end;
end;
end;
98/8/21 h10/8/21 s24-10-2 m1/1/1 8/21 8/21/98 98/5 1996-8/21
等 ほとんど何でもありです。半角、全角関係なしです。
参照: [Delphi-ML:25313] [Delphi-ML:25315] <文字列> <計算> <日時>
0149 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/11 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/11 osamu 編集
可変長レコードの扱い方
> 今、DirectPlayの送信部分を制作していますが、
>
> HRESULT Send(DPID idFrom, DPID idTo, DWORD dwFlags,
> LPVOID lpData, DWORD dwDataSize);
>
> 上記のようになっていて、送信データ部分を構造体のポインタで渡す
> ようになっています。現在は、
>
> TTextMessage = record
> dwType: DWORD; { dwType は絶対に必要 }
> Msg :array[0..255]of char;
> end;
>
> としてstring型からchar型に変換してから送受信をしていますが、
> 255文字以上送信できませんし、短い場合は無駄が生じるように思います。
> このmsgの部分を可変サイズにする方法はないでしょうか?
必要な量だけヒープから確保するのがよろしいかと思います。
具体的な案としては、以下のような手順が考えられます。
1. 送信用に AllocMem等で 必要量だけ確保
2. 確保した領域を編集しやすいように 予め用意しておいた型をかぶせ、編集
3. 送信
4. 確保領域の開放
例)
const
Giga = (1024*1024*1024);
type
// 編集用の型
PTextMessage = ^TTextMessage;
TTextMessage = record
dwType: DWORD;
Msg: array[0..1*Giga -1] of Char;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
pBuf: Pointer;
pMsg: PTextMessage;
begin
pBuf := AllocMem(Sizeof(pMsg^.dwType) + 4092);
// 型をかぶせて編集
pMsg := pBuf;
with pMsg^ do
begin
dwType := $12345678;
StrCopy(Msg, PChar(StringOfChar('A', 4091)));
end;
... // 送信等
FreeMem(pBuf);
end;
参照: [Delphi-ML:25091] <メモリ>
0126 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/08 osamu 編集
*.h ファイルから *.pas ファイルを作るコンバータ
最終的にご自分で変換するときの為に、サポートツールをご紹介します。
これらは、*.h -> *.pas への自動変換を行ってくれます。
それぞれに得手不得手がありますので、おいしいとこだけ つまんでください。(^^)
1. 熊木さんがこのMLで配付なさっていた H to Pas Converter
[Delphi-ML:11748] が最新版?
おっと、最新版は黒田 Dycoon さんのページにあるそうです。
http://www.ceres.dti.ne.jp/~dycoon/
2. Command-line HeadConv C DLL header converter (HeadConv.exe [HeadConv.zip])
Dr.Bob's Delphi Clinic
http://www.drbob42.com/delphi/default.htm
(ページ左のリンク "Files to Download" から取得できます。)
3. Header Translator (htrans.exe [htr034b.zip])
Alexander Staubo's Web Page --Technical stuff
http://www.mop.no/~alex/
参照: [Delphi-ML:23153] [Delphi-ML:22638] [Delphi-ML:11748] <開発環境> <C++>
0069 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/08 osamu 編集
親クラスのプライベートフィールドにアクセスする
中田さんが「邪悪な」方法を紹介してくれてます。
利用する方はくれぐれも気を付けて下さい。
参照: [Delphi-ML:20017]
0088 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/08 osamu 編集
time_t を TDateTime に変換する
time_t は、 int (4バイト)で、GMT の 1970年1月1日0時0分0秒からの経過時間を秒単位で表したもので、どうやら2038年1月19日3時14分07秒で破綻するようです。
function time_tToDateTime(vtime_t : Integer) : TDateTime;
const
cDeltaDate : Integer = 25569; // 1970/01/01 の TDateTime値
cSecPerMint : Integer = 60;
cSecPerHour : Integer = (60*60);
cSecPerDay : Integer = (60*60*24);
var
hh, mm, ss : Word;
n : Integer;
begin
vtime_t := vtime_t - cSecPerHour * 9; // JST=+9
n := vtime_t mod cSecPerDay;
hh := n div cSecPerHour;
n := n mod cSecPerHour;
mm := n div cSecPerMint;
ss := n mod cSecPerMint;
Result := (vtime_t div cSecPerDay) +
cDeltaDate + EncodeTime(hh, mm, ss, 0);
end;
参照: [Delphi-ML:18520] <計算> <日時>
0030 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/08 osamu 編集
関数のパラメータを省略可能にしたい
R> 関数のパラメータを省略可能なものにしたいのですが、どうしたら良いのでしょうか。
R> また、パラメータの個数を動的に決められるものにするには、どうしたら良いのでしょうか。
Delphiで可変数パラメータを使うには、array of constを使うのが一般的だと思います。
function Test(Values: array of const): string;
var i:Integer;
begin
Result:='';
for i:=Low(Values) to High(Values) do
with Value do
case VType of
vtInteger:
Result:=Result+IntToStr(VInteger);
vtChar:
Result:=Result+VChar;
vtString:
Result:=Result+VString^;
vtObject:
if VObject is TComponent then
Result:=Result+(VObject as TComponent).Name;
...
end;
end;
Test(['Testing ',123,' ',Self);
あってるかな?
ちなみに、C++Builder3/Delphi4 以降では、
procedure AProcedure(a: Integer; b: Integer= 3);
のような形で省略可能なパラメータを定義できるそうです。
参照: [Delphi-ML:17729] [Delphi-ML:19255]
0033 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/08 osamu 編集
Delphi3 の TStringList.CommaText の不具合
個々のアイテムが空白などを含む場合にのみダブルクオートで括るはずが、必要の無いときまでダブルクオートが使われる。
[対応策]
class.pas の 2253 行目から4行だけ示します。"" で包む必要があるなら
包む処理をする部分です。
S := Get(I);
P := PChar(S);
while not (P^ in [#0..' ','"',',']) do P := CharNext(P);
if (P <> #0) then S := AnsiQuotedStr(S, '"');
// ^^ ここ正しくは P^ 。
// ポインタと文字を比べるんじゃなーい!!
参照: [Delphi-ML:8468] <文字列> <バグ>
0022 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/08 osamu 編集
文字列の切り分け
>> 以下のような文字列dumStringにおいて、その中に含まれている引数term3だけを得たい。
>>
>> dumString := 'program term1 term2 term3 term4 term5';
GetTokenIndex(dumString,' ',4)でterm3を返します。
{---------------------------------------------------------------------}
function RemoveToken(var s:string;delimiter:string):string;
var p:Integer;
begin
p:=Pos(delimiter,s);
if p=0 then Result:=s
else Result:=Copy(s,1,p-1);
s:=Copy(s,Length(Result)+Length(delimiter)+1,Length(s));
end;
{---------------------------------------------------------------------}
function GetTokenIndex(s:string;delimiter:string;index:Integer):string;
var i:Integer;
begin
Result:='';
for i:=0 to index do
Result:=RemoveToken(s,delimiter);
end;
{---------------------------------------------------------------------}
RemoveTokenはsをdelimiterで切り分けたときの一番始めの部分を返し、sから取り除きます。sにdelimiterが含まれないときにはs全体を返し、sは空文字列になります。
GetTokenIndexは、sをdelimiterで切り分けたときのindex番目の部分を返します。一番始めの部分はindex=0です。sは変更されません。
indexが範囲外の場合には空文字列が返ります。
delimiterはstring型になっているので、#13#10や' : 'などで切り分けるのもOKです。
参照: [Delphi-ML:12110] <文字列>
0006 D1 D2 D3 D4 D5 D6 D7 3.1 95 98 作成: 1999/02/08 osamu rev 1.1 B1 B3 B4 B5 B6 B7 NT3 NT4 2K XP 更新: 1999/02/08 osamu 編集
New/Dispose に Pointer 型のポインタを渡すと。。。
New や Dispose は与えられたポインタの型によって、正しく初期化/解放の処理をしますが、これは、Delphi がコンパイル時にポインタの型情報を隠れた引数としてこれらの関数に与えているからです。
従って、差している先のデータがなんであれ、Dispose はポインタの型を信じ込んでデータの解放を試みます。
すると、次のコードはあっという間にメモリを使い果たします。
var pString1:pString;
pUnknown1:pointer;
i:Integer;
begin
for i:=0 to 2000 do begin
New(pString1);
SetLength(pString1^,10000000);
pUnknown1:=pString1;
Dispose(pUnknown1);
end;
end;
ただし、MemGet/MemFree と同様、ポインタの直接差すメモリブロックのサイズに関しては、Dispose が実行時に取得するため、次のコードは、メモリリークを起こしません。
type
TTest = array [0..1000000] of Double;
pTest = ^TTest;
var
pTest1: pTest;
pUnknown1: pointer;
begin
for i:=0 to 2000 do begin
New(pTest1);
pUnknown1:=pTest1;
Dispose(pUnknown1);
end;
end;
参照: [Delphi-ML:18664] <メモリ>
[新規作成] [最新の情報に更新]
How To
Lounge
KeyWords
Osamu Takeuchi osamu@big.or.jp
Tips
Delphi
Home