とあるクラスを実装しているときに、ちょっと共通部分があったので、privateメンバ関数(非virtual)にして纏めてみた。
・・・そしたらそのクラスのhファイルをincludeしている全てのcppファイルがコンパイルされた・・・。
うーむ。まあ、なんでそうなるのかは分かる。VC++も頑張っているのかスキップだのなんだの言って高速に処理してくれる。
のだけれど、hファイルを、外部に公開するためのヘッダファイルと考えると、
公開してないprivate関数を追加した場合は、ヘッダファイルを変更したくないのが本音。
けれどC++の仕様では、private関数を追加するにはクラスの定義を変更しなければならない。
そこで代替策が出てくる。ずばり、staticグローバル関数を使ってしまう。
これならば新しく追加しようと引数の数を変えようとヘッダファイルの変更の必要は無い。
第1引数にクラスオブジェクトへのポインタ(つまり、this)を渡してあげれば、
メンバ変数にも問題無くアクセスできる・・・いやできない!privateやprotectedな変数にはアクセスできないのだ。
じゃあfriend宣言でもするか・・・って結局クラス定義を書き換えなくてはならなくなってしまう。
そこでさらなる代替策。もとのクラスをAとすると、A_friendという新しいクラスを作る。
でもってクラスAの定義の中にfriend A_friend;
と入れておく。それでもって、A_friendの定義は、
全てcppの中に書く。Aにprivate関数を加えたくなったら、A_friendにstaticなメンバ関数を追加し、
それの第1引数はA*(つまりthis)とする。うむ完璧だ。いくらA_friendクラスにstaticメンバ関数を加えても、
hファイルは何も変わらない。A_friendクラスからはAのprivate及びprotectedなメンバにアクセスできるので、
Aのprivate関数と同等の処理が出来る。よし、これからはこれで行くか。・・・。・・・。
だあああああ。めんどくせええええええ。これだったらprivate関数追加してコンパイル時間を待った方が楽だ。
private関数をcppに隠蔽するという目的は達したが、楽にコードを書くという目的は達成できなかった。
本末転倒というかなんとゆーか。
その1〜その4ままで作成してきたクラス群の使用例を示します。
まず、最初にサーバー側が受け入れ準備をします。
ポート番号は、他のアプリケーションが使わないような値を指定して下さい。
まずは、適切なクラスにメンバ変数を追加します。
SDI/MDIならば、ドキュメントクラス、ダイアログベースならばダイアログクラスかアプリケーションクラスが
お勧めですが、より適切なクラスがあるならばそちらに置いて下さい。
CAcceptSocket m_Accept; |
m_Accept.Create(ポート番号); m_Accept.Listen(); |
CClientSocket
オブジェクトのメンバ変数m_lpOutput
に対して出力し、入力はOnReceive内で
m_lpInput
から読みこみましょう。
お次はクライアントからの接続方法です。
やはり適切なクラスにメンバ変数を配置します。
CConnectSocket m_Connect; |
connect.Create(); connect.Connect(接続先IP,ポート番号); connect.Init(); |
connect.m_lpOutput
に対してデーターを出力しましょう。
入力はOnReceive
内でm_lpInput
より読みこみます。
CClientSocket
と使い方は同じですね。
m_lpOutputに対してデーターの出力を終えたら、m_lpOutput->Flush()を呼び出さないと実際の送信が終わりませんので注意しましょう。
また、TCPの特徴として、送ったデーターが繋がって届いたり、ばらばらになって届いたりする可能性があります。
ばらばらになって届いた場合は、m_lpInputから入力するときにブロックされるのでさほど問題は無いのですが、
繋がって届いた時にしばしば問題が発生します。送った方は2つのデーターを送ったつもりでも、
受け取る方はそれがくっついた一つのデーターを受け取ります。この場合、OnReceiveは1回しか呼ばれません。
そのため、OnReceiveでは、受け取ったデーターが無くなるまで処理をする、
というコードを記述しなければなりません。そのために便利なのが、m_lpInput->IsBufferEmpty()です。
受け取りバッファが空になるとTRUEになりますので、これがTRUEになるまでm_lpInputに対する読みこみ処理を繰り返し行うようにしましょう。
(2000/1/14) もうちょっと分かりやすくなるよう推敲。
初心者向けFAQ
Q、VC++でボタンの色を変えたのですが、どうやれば良いのでしょうか?
A、諦めましょう。
Q、VC++で、リストボックスの各行毎に色を変えたいのですが、どうやれば良いのでしょうか?
A、諦めましょう。
Q、VC++で、読みこんだBitmapを保存したいのですが、どうやれば良いのでしょうか?
A、諦めましょう。
Q&Aじゃないですねこれ。もちろん実際には実行する方法はあります。 ですがどれも複雑で初心者には手が出ないのが本当の所でしょう。一言二言のアドバイスで出来るものでもありません。 このように、結構誰もがやりたいと思うもの、つまり需要が高いものでも、実行するのは結構困難である、 というものが結構あります。初心者のうちにそういったものに出会ったらどうすれば良いのでしょうか? 答えは「諦めましょう」です。ええそうなんです。諦めて、別のものを作りましょう。 そうしていって実力を上げたら、過去につまずいたものに挑戦しましょう。 ですが現実の質問では諦めましょう、とは言い難いんですよね〜。相手の実力も分かりませんし。 そこでオーナードローを使いましょう、と答えてしまうんですけどね。 これだけの情報ではまず出来ないでしょう。「何事も諦めが肝心」
お次はクライアントアプリケーションのソケットの記述です。
CConnectSocketに、次のメンバ変数を追加します。
CSocketFile* m_lpFile; CArchive* m_lpInput; CArchive* m_lpOutput; |
お次はメンバ関数のコードの記述です。以下の関数を加えましょう。
CConnectSocket::CConnectSocket() { m_lpFile = NULL; m_lpInput = NULL; m_lpOutput = NULL; } | |
CConnectSocket::~CConnectSocket() { Release(); } | |
CConnectSocket::Init() { Release(); m_lpFile = new CSocketFile(this, TRUE); m_lpInput = new CArchive(m_lpFile, CArchive::load); m_lpOutput = new CArchive(m_lpFile, CArchive::store); } | |
CConnectSocket::Release() { if (m_lpInput != NULL)delete m_lpInput; if (m_lpOutput != NULL)delete m_lpOutput; if (m_lpFile != NULL)delete m_lpFile; m_lpInput = NULL; m_lpOutput = NULL; m_lpFile = NULL; } |
(2000/1/14) 省略部分も全部書いて推敲。