こける Wired-プログラム未整理知識 99/06/12

私が今まで、プログラミングをしてきて失敗したり、驚いたりしたことからのミニ知識を書いてみました。
ほとんど、WindowsでDelphiのことです。
書けたところから発表していきます。多分いつまでたっても増築中のページになるでしょう。
ここの読み方ですが、なにせ「未整理知識」、何かを探して読むところじゃありません。(^^;;
流し読みして「今度気をつけよぅ」と思っていただくのが良いかと思います。

[お持ちかえりパック]


タイトル一覧

'99/06/12 Index
[published propertyの実行時検索]

'98/08/10 Index
[ドライブコンボボックス+ディレクトリリストボックスでエラーしたら]

'98/05/17 Index
[NT4でフックがかからない?]

'97/10/07 Index
[Delphi3のwinsock.pasが非互換]

'97/09/28 Index
[SHBrowseForFolderのHelpのBug?] [StringToOleStrの解放] [スレッド終了時の注意]

'97/09/07 Index
[Win95プロセス列挙] [Delphiでのサブクラス化]

'97/06/29 Index
[デスクトップのDC] [サブクラス化で失敗]

'97/06/22 Index
[published節のprocedureとfunction] [スレッドからイベントを得る] [大きなBitmapを印刷する] [Delphi2.0のDdeClientConvコンポーネントのWaitFlg] [Delphi2.0でstringをPCharでキャストする] [Delphi1.0でWin95のプリンタドライバを作製できる] [DLLの「printfデバッグ」] [グローバルアトム]


published propertyの実行時検索
DelphiのVCLの中に、TypInfoユニットというのがある。
DelphiのRTTI、即ち実行時型情報についてのユニットだ。
面白そうだが、使った事が無かったので詳細が分からない。
であれば勉強の為に何か作ってみるか、というのでTKPersistRecordを作ってみた。
こいつは、IniFileにアクセスするのを手助けするクラスで継承して使う。
継承先でpublishedのプロパティを記述すると、そのプロパティ名をキー、default節をデフォルト値にしてiniFileにアクセスするのだ。
例えば、以下のような記述になる。
type
TMyClass=class(TKPersistRecord)
published
  // GetAsInt/StrもSetAsInt/StrもTKPersistRecordで定義済み
  property IntProp: integer index 1 read GetAsInt write SetAsInt default 8;
  property StrProp: string index 2 read GetAsStr write SetAsStr;
};
で、使う時には
  MyClassObj:=TMyClass.Create('Section');
  i:=MyClassObj.IntProp;
とかするだけ。これでdefaultはちゃんと"8"になるし、IniFileにアクセスできる。

これを作るのに、TypInfo.pasを調べたり実験したりしたのでそれを紹介する。

TKPersistRecordでは、親クラスのメソッドから子供のクラスで追加したプロパティのプロパティ名を取得する必要がある。検索するキーはindex。
TypInfoユニットによれば、最初にPTypeInfo型の情報を取得しなければならない。
さて、このPTypeInfoをどうやって取得するか、というのがDelphiのHelpには無い。
調べてみると、TObject.ClassInfoがそれらしい。
で、TObject.ClassInfoを取得してみるのだが、何故かnilが返ってくる。(?_?)
んなばかな。というので、絶対に使っているはずのTComponent.ClassInfoを調べてみる。こちらはちゃんと情報が返ってくる。(?_?)
しかし、ソースを調べてもTComponentで何かしている様子はない。
TComponentの親は? というので調べてみると、TPersistentのクラス宣言の前後に{$M+}と{$M-}という記述があった。
DelphiのHelpを見ると、{$M+}が「実行時型情報を生成する」という意味だった。
なるほど、これを書かないとClassInfoがnilなんだな、ってんでさっそく追加する。
これで、ClassInfoに情報が返ってきた。

TKPersistRercordで必要なのは全プロパティの名前だ。
使えそうなのは、GetPropInfosかGetPropList。
GetPropInfosが全プロパティの情報を取得するもので、GetPropListが選択した型のプロパティ(string型のプロパティとか)を取得する。
GetPropListでも良かったのだが、どちらかというとGetPropInfosの方が用途に向いていそうだ。
GetPropInfosの型宣言は以下の通り。
procedure GetPropInfos(TypeInfo: PTypeInfo; PropList: PPropList);
どうやら、PropListにコピーしてくれるようだ。
しかし、PropListにはどれだけのサイズを用意すれば良いのかわからない。
どうにも全プロパティの数というのを求める関数は用意されていない様だ。
どうやって使えってんだこれ?

TPropData.PropCountというのがくさい。
しかし、TPropDataというのは宣言されているだけで、これを求めるものがない。
では、TTypeData.PropCountは?
これはクラス型の時の情報のようだが...
悩んでいても仕方ない。どっかにサンプルが無いだろうかと探してみる。
と、GetPropListがGetPropInfosを使っている。
どうやって個数を...と見てみると、まさにTTypeData.PropCountを使っている。
もともと、TObject.ClassInfoはクラス型の情報である。
TObject.ClassInfoの型はPTypeInfo。PTypeDataとは違う。
PTypeInfoからPTypeDataを引きずり出す関数がある。
function GetTypeData(TypeInfo: PTypeInfo): PTypeData;
これで、なんとかなった。

さて、まとめておきましょう。

まず、全プロパティを取得したいクラスの宣言。
{$M+}
type
TXXX=class
...
end;
{$M-}

var obj:TObject;
で、これに先ほど宣言したクラスのオブジェクトが入っている事にします。
uses TypInfo;
これを忘れずに。

で、まず、プロパティ数を求めます。
var PropCount:Integer; if obj.ClassInfo=nil then Raise Exception.Create('オブジェクトにクラス情報がありません'); PropCount:=GetTypeData(obj.ClassInfo)^.PropCount; // obj.ClassInfo そして領域確保。
var PropList: PPropList;
GetMem(PropList,sizeof(Pointer)*PropCount);
PropListは、プロパティ情報へのポインタの配列へのポインタ。
面倒? (^^;

で、プロパティ情報の取得。
GetPropInfos(obj.ClassInfo,PropList);
そしたら、後はプロパティ情報が入っています。
var i:integer;
for i:=0 to PropCount-1 do begin
  PropList^[i]^ // プロパティ情報。TPropInfo型

  PropList^[i]^.PropType // プロパティの型情報 PTypeInfo
  PropList^[i]^.PropType^.TTypeKind  // プロパティの型の分類(整数系とか)

  PropList^[i]^.Index // GetやSetのメソッドにIndexを使っているなら。
  PropList^[i]^.Default // プロパティのデフォルト。
			// LongInt型。これがDelphiの制限だわな。
  PropList^[i]^.Name // プロパティ名。
end;
他にもGetやSetのメソッドのアドレスとかありますが、GetOrdPropとかそういう関数を使った方が便利そうなので、必要ないでしょう。

使い終わったら、PropListの領域解放を忘れずに
FreeMem(PropList);

ドライブコンボボックス+ディレクトリリストボックスでエラーしたら
これはNiftyで出てた質問で、結構知られていないようなので紹介します。

DelphiやBorlandC++Builderのコンポーネントにドライブコンボボックス、ディレクトリリストボックス、ファイルリストボックスというのがあります。これらはプロパティにお互いを設定すると自動的に連動して、ドライブとディレクトリ、ファイル名を表示するコンポーネントです。
で、これらを連動するようにしておいて、FDの入っていないドライブを指定するとどうなるかというと、例外が発生するわけです。普通なら例外のおきそうな部分にDelphiならtry/except、BorlandC++Builderならtry/catchを仕掛けるのですが、この場合はプロパティで勝手に連動している為ユーザが作ったコードの部分では例外が発生しておらず、tryブロックを仕掛けられないのです。
これを防ぐのにとっさに思い付くのは自動的に連動させるのではなく、ドライブコンボボックスのOnChangeでファイルリストボックスのDriveプロパティを変更する事です。これならユーザが作った部分で例外が発生しますからそこにtryブロックを仕掛ければ良いわけです。
しかしせっかく連動するプロパティが有るのに使わないのはもったいない。
そもそもこれじゃこのプロパティ使えないじゃないの。何考えてるんだボーランド(今はインプライズ)。って?
いや、そうではないのです。ちゃんと使えるんですわ。
DelphiにもBorlandC++BuilderにもApplicationオブジェクトがあります。このApplicationオブジェクトにもイベントがありまして、そのイベントの一つにOnExceptionというのがあるんです。これはユーザが捕まえそこなった例外をApplicationオブジェクトが捕まえてイベント化してくれているのです。
つまりユーザがtryブロック仕掛けなくても最終的にここで捕まるわけですね。
なのでこのOnExceptionのイベントハンドラを作成して、この中で発生した例外を確認し処理すれば良いのです。
Applicationオブジェクトはオブジェクトインスペクタには出てきません。困った事に。
なので自分で全部記述する必要が有ります。
フォームの宣言に以下の宣言を付けます。
AppOnException(Sender:TObject; E:Exception);
で、implementation節に以下のようなハンドラを追加します。
procedure TForm1.AppOnException(Sender:TObject; E:Exception);
begin
  { Eを確認してファイルアクセスエラーだったらエラー処理をする }
end;
後は、FormのOnCreateあたりでApplicationのOnExecptionに結び付けます。
procedure TForm1.FormCreate(Sender:TObject);
begin
  Application.OnException:=AppOnException;
end;
と、まぁこのように可能なのですが、アプリケーションのイベントを書くのと連動プロパティを使わずに書くのとどっちが楽かというと...
やっぱ連動プロパティを使わないほうが楽ですかね。
アプリケーションもオブジェクトインスペクタに出して欲しいですね。
NT4でフック出来ない?
お仕事でNT4のFAXソフト用プリンタドライバを作成した時の話。
NT4のプリンタドライバってのは、とことん権限の無い状態で動作しているようだ。
ネットワークへのアクセスがほとんど出来ない。(;_;)
そこで悪いことを考えた。
「そうや、ProgramManにフック掛けてProgramManにやらしたらどうやろ?」
取り敢えず、テストプロを作って実行。
「ほほほ、うまく動くやないの。なら本番のプリンタドライバに組み込んだれ。」
しかし、フックがかからない。何故じゃー?
英文のHelpと格闘することしばし。
判ったことは、「そんなセキュリティホールを残すような真似はしてませんぜ」ってこと。
同じ権限に属するプログラムにしかフックはかからない。
Delphi3のwinsock.pasが非互換
Delphi3を使うようになって、順次Delphi2の時に作った自作ライブラリをDelphi3に移植する作業中のことである。
大体のライブラリは、そのままコピーできたし、いくつかのコンポーネントは必要が無くなった。
不要になったのはステータスバーとかそのようなものだ。
互換のために残したかったのだが、名前がバッティングすることもあり、あっさりあきらめた。
ほとんどの部分に互換性があったのだが、躓いたのもある。
winsockユニットである。
まさか、これが変わるとは思ってもなかったのだが、実際にはいくつかの変更があった。
一つは、定数の宣言が増えたことである。これ自体は上位互換なので問題はない。
問題が出たのは、関数宣言が変更になったことだ。
accept等の関数は、大元のC言語用ヘッダでは「ポインタ渡し」の引数が存在する。
Delphi2では、そのまま「ポインタ渡し」だったのだが、Delphi3からは「参照」に宣言が変更されている。
確かに、Delphi2でのwinsockユニットのこの宣言は、「参照にしてほしいなぁ」と思ったので、改良ではあるのだ。
が、非互換になったため、Delphi2時点で作成したコンポーネントが「コピーするだけ」では無くなってしまった。
コンパイルエラーが出る分変更は楽ではある。

同じ非互換なら、C言語用ヘッダで「char*」になっている「汎用ポインタ」の宣言を「PChar」から「Pointer」にして欲しかった。(;_;)

SHBrowseForFolderのHelpのBug?
IShellFolderとかのCOMなAPIを使ってみようと思い、SHBrowseForFolderをコンポーネントにしてみた。
(発表しないのは、すでに完成度の高いものがあるから。このコンポーネントは単に勉強用である。)
で、一応の完成はしたのだが、この時にはまったのは、HelpのBugである。
SHBrowseForFolderはWin95からのAPIなので、日本語のヘルプが存在しない。
で、苦労して英語のHelpを読み下したのだが、記述に間違いがある。良く読むと間違いであると判る部分もあるのだが。
それは、SHBrowseForFolderからのコールバック関数の記述の部分だ。
コールバック関数の第3第4引数は、それぞれlParamとlpDataなのだが、説明文中にはlpDataがメッセージ毎に変わるとかいてある。
しかし、実際にはlParamがメッセージ毎に変わるパラメータで、lpDataがアプリケーション定義の値、即ちSHBrowseFoFolderに設定したlParamの値である。
確かに、lParamとlpDataの説明にはそう書いてあるのだが、第2パラメータであるuMsgの説明では逆になっている。
また、引数名からすると、これはBugだとしか思えない。
#lParamに設定した値がlParamではなくlpDataに現れるってのは何かの間違いだろう。
また、第1引数のhwndの説明文もwparamとlparamの説明が逆であるので注意が必要だ。

StringToOleStrの解放
同じく、SHBrowseForFolderを扱うルーチンで失敗したことについて。
SHBrowseForFolderというかIShellFolderを扱う場合、文字コードの変換を行う必要があり、この変換ルーチンがStringToOleStrである。
Delphi2のHelpにはないが、Delphi3のHelpに載っている。ルーチン自体はどちらにもある。
このルーチンは内部的にSysAllocStringLenというAPIでメモリーを確保しており、用が終わったら解放する必要がある。
解放するAPIは、SysFreeStringというAPI。
Delphiのライブラリの中で確保しているメモリなのだが、Delphiのライブラリにこれを解放する物はないし、Helpにも説明が無い。
忘れがちというか、気が付かないかもしれないので注意しましょう。(^^;;

スレッド終了時の注意
Delphi2以降では、スレッドを扱うためのクラスTThreadがあり、これを使うとスレッドが簡単に扱える。
しかし、このスレッドのHelpに「WaitForを使うとき、Synchronizeとぶつかると凍結するかもしれません」という注意が書いてある。
WaitForは、その中でメッセージを受け取らない、一方Synchronizeはメインスレッドがメッセージを受け取るまで終了しない、という訳だ。
で、私は「WaitForさえ使わなければいいんだ」と思っていたのだが、ソースコードを追いかけていると、それでも駄目なことが判ってしまった。
なんと、TThreadのデストラクタで「WaitFor」を呼んでいる。更にOnTerminateはSynchronizeを使っている。
ということは、メインスレッドでFreeするときは完全にスレッドの終了を確認してから行うか、OnTerminateを含めてSynchronizeを使わないようにするかである。
(終了の確認のつもりで、OnTerminateの中からFreeしてはいけない。そこではまだ少し早いのだ。)
スレッドを中止してさっさと終わってFreeもしたいってのは頻繁に必要になるし、スレッドでの処理結果を受け取るのにOnTerminateを使わないってのも苦しい。
(結果を受け取るのに、以前の話で紹介したPostMessageを使うのなら安全である。)
結局、FreeOnTerminateを使ってスレッド側でデストラクタを呼ぶようにすれば良いようだ。これならWaitForは関係ないし、OnTerminateもSynchronizeも使える。
ここまで、神経質に見る必要はないのかもしれないが、とりあえずこうしとけば凍結の原因を一つ無くすことができる。

実は凍結させないだけならまだ方法はある。
SuspendしたままFreeしてしまえば、WaitForは呼ばれないのだ。
しかし、これを使うと今度はメモリリーク?が発生する。
TThreadに属するメモリは消えるが、スレッド自体はサスペンドのままシステム上に残ってしまう。
つまり、一つハンドルが開いたままになる。
普通のアプリケーションなら問題はないかもしれないが、常駐タイプのアプリケーションで頻繁にこんなことが起こるととてもまずい。
これがいやなら、ちゃんとResumeしてから終了させないといけない。

Win95プロセス列挙
Win95で複数のファイルの印刷を行うツールを作っていたときのこと。
一種のランチャーのようなものなので、デバッグするとき他のプロセスを監視したかった。
しかし、Win32SDKのヘルプを探しても実行中のすべてのプロセスを検索するすべは見つからない。
Delphiのライブラリソースを探してみると、それらしいToolHelp32というのが見つかった。
でも、Helpにも無いため、使い方が分からない。スナップショットって何だろう?
DDKを検索してやっと判った。

ToolHelp32を使えば、現在実行中の全プログラムに関するヒープ、プロセス、スレッド、モジュールの状態が判る。
これらの情報は、「ある瞬間のコピー」である。
この「ある瞬間のコピー」がスナップショット。

ToolHelp32では、まずこのスナップショットをとらなければならない。
スナップショットを取るのは、以下の関数。
function CreateToolhelp32Snapshot(dwFlags, th32ProcessID: DWORD): THandle;
返値が、「スナップショットのハンドル」である。
引数のdwFlagsは、「何のスナップショットが取りたいか」。プロセスだけで良いとか、スレッドもとか。
ビットパターンなので複数一気にってことが可能。
th32ProcessIDの方は、「ターゲットのプロセスID」。普通は無視されるので何でもよい。ヒープの状態を知りたいときだけ設定する。
ここで作ったスナップショットは、CloseHandleでクローズする。

スナップショットが取れたら、後はそのスナップショットを「XXX32First」と「XXX32Next」で読み出していくだけ。
この辺はファイルを検索するときの「FindFirst」「FindNext」と同じような使いかた。
(実際の関数名や引数なんかは、Tlhelp32ユニットのソースを確認してください)

これは、Win95のみだそうでWinNTでは使えません。

Delphiでのサブクラス化
Delphiで「他のWindowControlに機能を付加する」タイプのコンポーネントの作り方を紹介する。
他のWindowControlに機能を付加するコンポーネントは、部品として結構面白い。
なぜなら継承とはまた違う形で、完成されたコンポーネントをカスタマイズできるから。
この手のコンポーネントを私は「修飾型」と呼んでいる。(Tucker!氏発案の「修飾」より)
「修飾型コンポーネント」は、ターゲットのWindowControlをサブクラス化することで機能を付加する。
例えば、こけるWiredで公開している「ドロップ機能付加コンポーネント」は、WM_DROPFILESを処理するようにサブクラス化してドロップ機能を付加する。

Delphiでは、ウィンドゥプロシージャがC+SDKとは違う形になっている。
C+SDKなら、以下のような形をしているはず。
function WndProc(Handle:HWND;MsgID,wParam:Integer;lParam:LongInt):LongInt;
しかし、Delphiのウィンドゥプロシージャはメソッドになっており、引数も違う。
procedure WndProc(var Msg:TMessage) of object;

この為、Delphiのウィンドゥプロシージャは、そのままの形でAPIに渡すことができない。
サブクラス化するというDelphi関数は存在しないため、C+SDKタイプのウィンドゥプロシージャに変換しなければ、サブクラス化をするためのAPIを呼び出せない。

このDelphiのウィンドゥプロシージャからC+SDKのウィンドゥプロシージャへ変換するDelphi関数が存在する。
function MakeObjectInstance(Method: TWndMethod): Pointer;
引数はDelphiのウィンドゥプロシージャであり、返値がC+SDKのウィンドゥプロシージャへのポインタである。
このC+SDKのウィンドゥプロシージャに変換されたものを「オブジェクトインスタンス」と呼んでいるようだ。
この変換によっていくらかのメモリを使用するため、「オブジェクトインスタンス」は使用後はこのメモリを解放してやらなければならない。
この解放する関数が以下のものである。
procedure FreeObjectInstance(ObjectInstance: Pointer);

オブジェクトインスタンスは、C+SDKのウィンドゥプロシージャとして使えるのでAPIに渡すことができる。
この時使うAPIは以下のものである。
function SetWindowLong(Handle:HWND;nIndex:Integer;newLong:LongInt):LongInt;
このAPIを使って、Windowの「ウィンドゥプロシージャへのポインタ」を書き換える。
この時のnIndexはGWL_WNDPROCという定数を使えばよい。
返値は、以前の値である。これは後で使用するので、保存しておかなければならない。
(GetWindowLongで別に取得してもいい。)
どう使うかといえば、この以前のウィンドゥプロシージャを使って、従来の動きに対応するのである。

サンプルは「ドロップ機能付加コンポーネント」のソースで、判ると思います。

デスクトップのDC
失敗に終わったデスクトップコンポーネント作成中の話。
GetDC(GetDesktopWindow)とやった時に出てくるHDCに描画しても何も表示されない。
GetWindowDC(GetDesktopWindow)とやった時に出てくるHDCに描画するとスクリーン全体に表示してしまう。
うーん変だなと思い、WinSightを使って調べてみた。スクリーン全体をWindowとするのは、という感じで。
そうすると、Program Managerってのがいる。おやぁ?
Hiddenでない子Windowを順次追っかけていくと、SysListView32というのが最後だ。
なるほど、そういやデスクトップってのはリストビューそっくりだ。
ちょっとWindowサイズがスクリーンよりも小さい。
私はTaskBarを出しっぱなしにしているのだ。そのサイズ分だけ小さいようだ。
このDCを使って描画すれば...
惜しい、デスクトップ上のアイコンの上にまで表示してしまう。

サブクラス化で失敗
アクティブデスクトップでも自作しようかと思い、デスクトップコンポーネントを作ろうとした。
まず考えたのが、デスクトップのサブクラス化だ。
何も考えずに、サブクラス化しようとしたら、SetWindowLongを呼び出したところでエラーになる。
エラーコードは「その機能は実装されていない、WinNTだけかもしれない」である。
あれ?と思ったが、「プロセス毎にアドレス空間が分離されている」事を忘れていた。
DLLからじゃないと、サブクラス化できそうに無い。
DLLが必要なら簡単なコンポーネントになりそうにないし、この線はあきらめた。(^^;;

こういうのをやるにはフックのほうが良さそうだ。やはりDLLはいるのでコンポーネント化しないけど。

published節のprocedureとfunction
Delphiのpublished節は、基本的に「オブジェクトインスペクタに表示させる」プロパティを記述するところだ。
しかし、ここにはprocedureやfunctionを記述することができる。何かに使えるのか?
ちょっと使いみちを思い付いた。

Delphi1.0や2.0では多重継承ができない。(Delphi3ではインタフェース継承という形で可能)
つまり、TPanelからの派生クラスとTBevelからの派生クラスで、自分で付け加えたあるインタフェースを統一的に扱う手段が無い。
しかし、published節に定義されたものならば、実行時にメソッドの「名前による」検索が可能だ。検索結果からこれを呼び出すこともできる。
この手を使えば、ちょっと厄介だが「インタフェース多重継承」が可能である。
異種リストにつないでおいて、自分で追加したインタフェースを持つ別のクラスから派生したオブジェクトでも統一的にメッセージが出せる。

試してみたいが、それが欲しいケースを思い付かない為、実験は保留中。(^^;;
(多重継承が欲しいケースってこんな時なんだが、Delphi使ってて多重継承が欲しいと思ったケースがほとんど無い。)

スレッドからイベントを得る
スレッドを使って何かの処理を行う場合、そこで発生したイベントをどうやってメインスレッドでうけとるか。
スレッドを使ってファイルコピーを行っている時も、その進捗ぐらいは表示したいとか。
Delphi2.0ならSynchronizeメソッドもあるわけだが、何等かの理由でこれを使いたくない場合に私がよく使っている手を紹介する。

メインスレッドで不可視のWindowを作成し、これのWindowHandleをスレッド作成時に渡す。
スレッドはメインスレッドに何か伝えたいことがあれば、このWindowHandleを使ってPostMessageすれば良い。
Synchronizeに比べて、「パラメータ(lParamとwParam)が渡せる」のと、「スレッドは待たなくていい」のが利点。
大量のパラメータを扱いたければ、キューを作って、このキューの操作中はクリティカルセクションで防護するのも手。

大きなBitmapを印刷する
Printerの解像度は、Displayよりもかなり高い。
その所為か、Win95でA4いっぱいにBitmapを印刷しようとすると、うまく印刷できないことが多い。
GDIに問題があるのか、Displayドライバなのか、Printerドライバなのか、その組み合わせなのか...

で、Printerの解像度でA4いっぱいのBitmapをメモリ上に作り出し、これをDIB化。
SetDIBitsToDeviceを使ってこのDIB印刷すればうまく印刷できた。
WinNT3.51では、逆にそのまま印刷したほうが良い結果が得られた。

Delphi2.0のDdeClientConvコンポーネントのWaitFlg
WinNT3.51用のインストーラもどきを作成しようとして、DdeClientConvコンポーネントを使った。
ExecuteMacroを使用して、さてアイテムを登録しようという段になって動作しない。
あれ?と思って調べてみると、ExecuteMacroで指定しているWaitFlgが思うように動作していない。
どうもしかたないので、VCLソースを修正して事無きを得た。

VCLソースを持ってる人は、これを使う前に確認してみたほうがいいよ。

Delphi2.0でstringをPCharでキャストする
ある構造体のPCharを設定する時の失敗。

stringの変数で文字列を作って、これをPCharキャストして構造体のメンバに設定した。
この後、この構造体を使ったWinAPIを呼び出す前に、もとのstring変数を変更すると、変更後の文字列が元より長い場合は動作したが、短い場合動作しなかった。
よく考えてみれば、そのstringの最初の文字をPCharでキャストした構造体メンバがさしているのだから変更してはいけない。

Delphi1.0でWin95のプリンタドライバを作製できる
DDKは必要だが、Win95とWin3.1のプリンタドライバはただの16bitDLL。
これならDelphi1.0で作成できる。実際あるPC FAX用プリンタドライバはDelphi1.0で書いてある。

DLLの「printfデバッグ」
DLLを作成すると、ちょっとデバッグしにくい。特にprintfデバッグを頼りにしている私には。
で、わりとうまくいった方法を紹介。

まず、メインウィンドウのクラス名に変った名前を付けた「デバッグウィンドウアプリケーション」を作る。
DelphiならメインフォームのNameプロパティに変った名前をつけたアプリケーション。
このアプリケーションにTMemo,TListbox等の複数行表示可能なコンポーネントを張り付ける。
DLL側では、FindWindow APIを使ってこれを探し出す。見つからなかったら表示をあきらめる。
次に表示したい文字列をグローバルアトムに登録。
(デバッグ表示用の文字列だから255文字の制限は実際上問題になら無いだろう。)
さっき探したWindowにSendMessage APIを使って文字列を送り付ける。
忘れずにグローバルアトムを消しておく。
「デバッグウィンドゥアプリケーション」では、受け取ったグローバルアトムを文字列に戻す。
それをすぐに表示用コンポーネントで表示する。
もし、デバッグウィンドウアプリケーションが立ち上がってなかったら、DLLでFindWindowに失敗するので文字列は表示されない。
デバッグウィンドゥアプリケーションが立ち上がっている時だけ有効な「printfデバッグ」ができる。

グローバルアトム
Win32では、別プロセスは「異なるメモリ空間」なので、プロセス間「文字列」通信は問題だ。
しかし、「グローバルアトム」というのを使えば何とかなる。
グローバルアトムってのはWindowシステムが用意した文字列表の様なもの。
ここに自分で指定した文字列を登録できる。登録結果は整数値で表される。
これならSendMessage APIで送信できるというわけ。
GlobalAddAtom でグローバルアトムに登録。
GlobalGetAtomNameでグローバルアトムに登録された文字列を取得。
GlobalDeleteAtomで登録削除。
注意点は

[表紙] [Program Files] [オブジェクト指向異聞] [プログラム未整理知識] [Winsockを使ってみようぜ] [だべり] [What's New] [書いた奴] [リンク] 内緒!