メイン

プログラミング アーカイブ

2007年4月 2日

Turbo C++

Borlandのホームページをみていたら、Turbo C++という文字があったので、ええ、今更?と怪訝になっていたら、どうも、Borland Developer's Studioから、単言語で切り出したものらしい。確かに、Borland Developer's Studioは、C++、C#、Delphiなどの統合開発環境をもっているらしかったけど、私のような一アマチュア・プログラマには、値段も機能も、過剰であった。
 で、確認したら、私がいまだに使っている、Borland C++ Builder 3.0とも上位互換性があるらしいので、賞与が出たら、Turbo C++の購入を検討してみよう。ホームページにも書いたが、Borland C++ Builder は、GUIアプリにほとんど挫折しかかっていた私を救ってくれた命の恩人である(別に、命の危険にさらされていない、って!)。でも、もっているBorland C++ Builder 3.0は、Standard Editionだったので、インターネットやSOAなどの通信がらみのアプリは書けなかった。Turbo C++のProfessional Editionは、そこまでカバーしてくれているらしいので、また勉強のネタが増える(と、ブログには書いておこう、ということになるだけかもしれないが)。

2007年11月 6日

PROLOGの帰趨

 今から10年以上も前だろうか、PROLOGというコンピュータ言語が流行ったことがあった。当時は、人口知能の第5世代コンピュータ開発ということで、日本の鼻息が荒く、また、日本は未曾有のバブル好景気で、そのこともあって、アメリカは、日本に決定的に人口知能の分野で差を付けられてしまうのではないか、と焦った。
 PROLOGとは、論理型プログラミングの一種で、要するに、手順と目標を論理的に記述しておけば、プログラムの方で勝手に解をみつけて止まってくれる、というものである。
 例えば、有名なハノイの塔の問題だと、
hanoi(1,X,Y,Z):- move(1,X,Y).
hanoi(N,X,Y,Z):- M is N-1, hanoi(M,X,Z,Y),
move(N,X,Y),
hanoi(M,Z,Y,X).
move(N,X,Y):- nl, write([disc,N, from, X, to, Y]);
 と定義しておいて、
hanoi(3,left,middle,right) としてrunさせればよい。
 このように、PROLOGによると、抽象的にアルゴリズムを記述ておけば、勝手に解を探してくれるので、プログラミングは簡単であり、予想されるプログラマ不足の解決に福音である、ともてはやされた。
 しかし、C++, JavaのようにPROLOGが普及することなく、ひっそりと消えていった。同類の人口知能言語のLISPはまだ、使われているはずだ。
 結局普及しなかった理由は、「論理的な、あまりに論理的な・・」ではなかろうか。すなわち、プログラミングといっても、あまりきれいに論理的に割り切れるものは少なく、そういう泥臭いアプリケーションとなると、どうしても、C++, Javaのような手続型言語が有利なのだろう。また、C++, Javaは、オブジェクト指向の波に乗ったことも大きい。というのは、GUIの環境がメインになってくると、属性やメソッドの記述を統一できるオブジェクト指向の概念が必須と言ってよいが、その意味でも、PROLOGは乗り遅れた。
 そうやって、PROLOGが廃れた理由を述べたが、実は、事物は放っておけば廃れるので、廃れる理由を一々説明する必要はない。寧ろ、なぜC++, Javaが流行ったかさえ述べればよい、というのが、芸能の世界からの教訓である。

2008年5月 9日

印刷プログラミング

 プログラマでもないのに、とても卑近な業務用アプリを書き続けてもう20年以上にもなる。それで、業務用アプリともなると、どうしても帳票など、印刷する機能が必須である。
 そこで、最早化石のような、昔書いたアプリのソースコードを一部をここに引用してみる。returnでなく、exit()を使っているあたりにオールド・ファッションが漂っている。

if ((pr1=fopen("prn","wt"))==NULL) exit(1); /* file open */
/* printer status set */
fprintf(pr1,"\x1b\x7e\x01%c%c",0,0); /* printer initialize */
fprintf(pr1,"\x1b\x7e\x02%c%c%c",0,1,0x3c); /* char pitch = 6 */
fprintf(pr1,"\x1b\x7e\x03%c%c%c",0,1,0x4b); /* line pitch = 7.5 */
fprintf(pr1,"\x1b\x7e\x0e%c\x01\x17",0); /* bold on */
#if defined(P4118)
fprintf(pr1,"\x1b\x7e\x37%c\x01\x05",0); /* set gothic 32 for 4421 */
#endif

 このように、昔の印刷プログラミングでは、先ず、fopen()で、プリンタ・デバイスをオープンし、それから、0x1bで始まる特殊な文字列を、プリンタに対して送る必要があった。0x1bは、キーボードのエスケープキーのスキャン・コードに対応するので、このようなプリンタ制御用の文字列は、エスケープ・シーケンスと呼ばれた。
 このエスケープ・シーケンスというのが、下記の点でとても面倒であった。
(1) まず、デバイス依存性が高いこと。上記のエスケープ・シーケンスは、IBMのプリンタ用であるが、これをNECのプリンタ用に書き直そうとして、一苦労したことがあった。機種に適合しないエスケープ・シーケンスを流すと、紙が排出されまくったりして、多くの場合、悲惨なことになる。
(2) 通常のガイドに必ずしも書かれていないこと。普通のエンドユーザは、エスケープ・シーケンスを流してプリンタを制御することなんてしないので、普通のマニュアルには載っていないことが多い。よって、別途高い金で、専用のテクニカル・リファレンスを買う必要があった。
(3) とても非自明であること。例えば、
fprintf(pr1,"\x1b\x7e\x02%c%c%c",0,1,0x3c); /* char pitch = 6 */
 を見ると分かるように、ほとんどマシン語と変わらない、単なる記号列である。
(4) ストリーム・シーケンスであること。
 すなわち、ページという概念がなく、ひたすら、一文字一文字、印刷文字列や制御文字列を流すしかない。よって、ページの末尾にフッタを印刷しようとすると、数を数えて改行コードを送出する必要があったりした。
 このような苦労は、Win32 APIの下では、解消された(それはいかなる意味でも、Win32 APIが扱いやすいと言ってない)。
 Win32 APIの下では、プリンタは、ページという概念をもつ。そして、ページ・バッファの任意の座標に、ビットマップや文字列を書きこんで、ページ単位で、出力デバイスのプリンタに書き込む、ということである。ディスプレイに書き込むのとプログラムの文脈では、何も変わらない。こういう概念だと、原則的に、デバイス依存性はない。もちろん、キャノンであったり、エプソンであったり、HPであったりと、プリンタのドライバはそれぞれ異なるが、そういう差異はドライバ側が吸収してカプセル化している。
 こうして、Win32 API側では、プリンタ・ドライバを初期化するDocumentProperties()や、プリンタ・ドライバの能力を取得するDeviceCapabilities()などのAPI関数を使って、印刷を制御する。
 私がプログラミングに使っているBorland C++ Builderでは、さらにカプセル化されていて、印刷指示に、Win32 API関数を直接呼び出す必要はない。例えば、下記のとおりである。これは、基本的に、デバイス依存性なく動く。ただ、解像度の点は考慮が必要で、それを考えないと、マージンがずれたりする。よって、事前にプリンタの解像度を取得して、パラメータを予め調整しておく必要はある。

Printer()->Canvas->Font->Style.Clear();
fn = Printer()->Canvas->Font->Style;
Printer()->Canvas->Font->Name = "MS 明朝";

for ( b = 0; b < N; b++ ) {
rewind(fi);
Page = P;
Printer()->BeginDoc();
Printer()->Canvas->Font->Style.Clear();
vpit = Printer()->PageHeight / 30 - 10;
Printer()->Canvas->Font->Size = 12;
lpit = 65; // 全角ピッチ
lfpit = Printer()->Canvas->TextWidth("W"); // 半角ピッチ
.....
Printer()->Canvas->TextOut(xpos,ypos + Ybase,XChar); // 文字列の印刷

}

2008年7月15日

TSRのプログラムの例

参考までに、というほどの意味もないが、昔、TSRの本と首っ引きで書いた、vfly.c といプログラムのソースコードを参考までに示す。タイムスタンプをみると、1992年8月で、なんと16年も前である。当時は、MS-DOS3.0の時代だったと思う。

当時もそうであったが、今見ると、一層、ソースコードの意味が分からない。なんでも、MS-DOSの割り込みをフックするルーチンを書き換えるので、随分トリッキーだと思った記憶がある。

あ、これは、画面上を、蠅のように、'*'というキャラクタが飛び回るプログラムである。実際上の意味は何もない。画面が見にくくなる以外は、悪さもしない。ただ、CPUリソースを浪費するだけである。


/* vfly.c */
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <dos.h>

#define vram_adr(x) \
((unsigned char far *)MK_FP(0xe000, 2 * (x)))
#define vram_atr(x) \
((unsigned char far *)MK_FP(0xe000, 2 * (x) + 1 ))

static unsigned int pos,count,old_pos,xxx;
static unsigned char old_atr;
static unsigned char old_char; /* 1 char buffer */
static unsigned char old_atr1;
static unsigned char old_char1; /* 1 char buffer */
void interrupt (*old_func)();

void interrupt cnt_c()
{
disable();
count++;
if (count==xxx) {
if (pos>1400) pos=0;
*vram_atr(old_pos)=old_atr;
*vram_adr(old_pos)=old_char;
*vram_atr(old_pos+1)=old_atr1;
*vram_adr(old_pos+1)=old_char1;
old_char=*vram_adr(pos);
old_atr=*vram_atr(pos);
old_char1=*vram_adr(pos+1);
old_atr1=*vram_atr(pos+1);
*vram_adr(pos)= 107;
*vram_atr(pos)=1;
*vram_adr(pos+1)= 8;
*vram_atr(pos+1)=3;
old_pos = pos;
pos += 2;
count=0;
}
else {
enable();
old_func();
return;
}
enable();
old_func();
}

void main( void )
{
pos=0;
count=0;
xxx = 5;
old_atr=*vram_atr(0);
old_char=*vram_adr(0);
old_atr1=*vram_atr(1);
old_char1=*vram_adr(1);
old_func=getvect(0x1c);
setvect(0x1c,cnt_c); /* rewrite the cntr_C trap address */
/* terminate and stay resident */
enable();
keep(0,(((unsigned)sbrk(0)+15)>>4)+_DS-_psp);
}

2008年12月28日

RADについて

私が普段、簡単なツールを作成するために使っているのは、Borland C++ Builder(以下、BCBと呼ぶ)というRAD(rapid application development)ツールである。BCBを起動すると、ブランクのフォームが立ち上がる。そこに、ボタン、テキスト領域などのオブジェクトを、メニューバー(ウイジットなどとも呼ばれる)からドラッグ&ドロップして、次にオブジェクトを右クリックするとか、ダブルクリックするなどの操作を行う。すると、コードを書く領域が現れて、そこに所定の処理を行うコードを書く。こうしておくと、残りのコードは、BCBが自動生成してくれる。下記のコード1、コード2及びコード3がそうである。

コード1は、一種のランチャーであり、自動生成され、普通は眺めたり編集したりする必要はない。コード3は、実際の処理を行う部分である。コード2は、コード3に関連して自動生成されるヘッダファイルである。このとき、人手で書いたのは、コード3の青太字の部分だけである。これだけだとフォームと処理の間の関連付けはどこに記述されているのか、と思われるが、実はMain.dfmというバイナリの記述ファイルがあって、ここに、フォームの属性やイベント関係の情報が含まれている。

一方、Windowsにおける標準的なというか伝統的な記法は、コード4のようなものである。これは、フォーム(ウインドウ)をマウスの右クリックするのと左クリックするのとで異なるメッセージが表れるようにするだけのプログラムであるが、RADでないと、このようなコードは全て手で書かなくてはならない。これだけ見ていても結構うんざりするが、これはいわゆる"Hello world"のような最も簡単なプログラムなので、この程度で怖気づいていては、正統なWindowsプログラミングは諦めた方がいい。

しかし、BCBやMicrosoft Visual StudioなどのRADがあるのに、このような伝統的な記法は廃れていない。その理由は、RADでは痒いところに手が届きにくいからである。例えば、コード4で、WM_RBUTTONDOWNというマウスボタン右クリックのイベントを拾って処理を行っているが、少なくとも私が使っているBCB Ver.3では、フォームには、マウス左クリックやダブルクリックなどのイベントは関連付け可能であっても、そのままでは右クリックのイベントを拾うことはできない。もし拾おうとすると、別途API関数を記述しなくてはならなくなって、下手したら正統なWindowsプログラミングよりも錯綜しかねない。

このようにRADを使うと便利であるが、カプセル化されているため、とても融通が利かないことがある。よって、私の知っている範囲では、BCBは、プロのプログラマにはあんまり人気がない。BCBを愛用しているのは、ほとんど私のようなアマチュアであると思われる。

ただ、定型のプログラムの生産性の差は圧倒的なので、RADはその制御記述可能性の粒度を上げているのではないだろうか。なにしろ、私が使っているBCBは、10年以上も前、Windows95が隆盛していた頃リリースされたものなのである。

一方で私の信念があって、それは少々年代がすすんでも、開発ツールはこれ以上使いやすいものにはなりにくい、ということである。開発速度と、制御記述可能性の粒度というトレードオフがあるからである。よって、機械語そのものみたいなアセンブラの出番がなくなることはない。また、カーニハン・リッチの本にあるようなテクニックが不要になることもない。プログラミングは従って、コード書きオタク(ナードとか、ギークなどとも呼ばれる)の聖域であり続けるのである。少なくとも私のような初心者には、彼らのやっていることは遥か彼方にあって、その全貌すら眺めることはできない。



コード1
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
USERES("sample.res");
USEFORM("Main.cpp", Form1);
//---------------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
//---------------------------------------------------------------------------


コード2
//---------------------------------------------------------------------------
#ifndef MainH
#define MainH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE 管理のコンポーネント
void __fastcall FormClick(TObject *Sender);
private: // ユーザー宣言
public: // ユーザー宣言
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif


コード3
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
ShowMessage("クリックされました。");>
}
//---------------------------------------------------------------------------


// コード4 #include <windows.h>

// ウィンドウ・クラス名
#define MYWNDCLSNAME "MyWindowClass"

LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch( uMsg){
case WM_RBUTTONDOWN:
MessageBox(hWnd, "元気ですか?", "Message",
MB_OK | MB_ICONINFORMATION );
return 0;
case WM_LBUTTONDOWN:
MessageBox(hWnd, "もう嫌、こんな生活。", "Message",
MB_OK | MB_ICONINFORMATION );
return 0;
case WM_DESTROY:
PostQuitMessage( 0);
return 0;
}

// 処理しないメッセージはシステムに任せる
return DefWindowProc( hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wndcls;
HWND hWnd;
MSG msg;

// ウィンドウ・クラスの登録
ZeroMemory( &wndcls, sizeof(wndcls) );
wndcls.lpfnWndProc = WndProc;
wndcls.hInstance = hInst;
wndcls.hIcon = LoadIcon( 0, IDI_APPLICATION);
wndcls.hCursor = LoadCursor( 0, IDC_ARROW);
wndcls.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
// wndcls.hbrBackground = 0x0131;
wndcls.lpszClassName = MYWNDCLSNAME;
if( RegisterClass( &wndcls) == 0) return -1;

// メイン・ウィンドウの作成
hWnd = CreateWindow( MYWNDCLSNAME, "My Window",
WS_OVERLAPPEDWINDOW | WS_VSCROLL |
WS_HSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
0,
0, hInst, NULL);
if( hWnd == 0) return -2;

// 引数にしたがって,ウィンドウの初期表示状態を指定する
ShowWindow( hWnd, nCmdShow);
UpdateWindow( hWnd);

// メッセージ・ループ
while( GetMessage( &msg, 0, 0, 0) ){
DispatchMessage( &msg);
}

// WM_QUITメッセージのwParamをプログラムの終了コードにする
return msg.wParam;
}

About プログラミング

ブログ「Commutative Weblog」のカテゴリ「プログラミング」に投稿されたすべてのエントリーのアーカイブのページです。過去のものから新しいものへ順番に並んでいます。

前のカテゴリはスポーツです。

次のカテゴリはペット・動物です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。