[ C++で開発 ]
クロスプラットフォームなC++プログラミングを実現するフレームワークがACEです。
最初のプログラムとして、例によってHello Worldを作成します。ACEはGUIについては対象外なので、コンソールプログラムで作成します。
ACEを用いる場合、Cの標準関数(<stdio.h>など)ですらラッピングされているので、printfのラッピングであるACE_OS::printfを呼びます。
// hello_ace.cpp
#include <ace/OS.h>
int main(int argc, char* argv[])
{
ACE_OS::printf("Hello, ACE World!\n");
return 0;
}
ACEでは、main関数を裏で置き換えてライブラリの初期化等を行っています。そこで、mainを書くときにいくつか制約があります。次の3つのどれかの書式で書かなくてはなりません。引数の省略や型の違い(第2引数をchar**で受ける等)はNGです。
int main(int argc, char *argv[])
int wmain(int argc, wchar_t *argv[])
int ACE_TMAIN(int argc, ACE_TCHAR *argv[])
2.の書式は今のところWin32のみ提供されています。したがって3.の書式の方が移植性が高いと云えます。
// hello_ace.cpp
#include <ace/OS.h>
int ACE_MAIN(int argc, ACE_TCHAR* argv[])
{
ACE_OS::printf("Hello, ACE World!\n");
return 0;
}
ACEが以下のディレクトリにインストールされている場合
/usr/local/ace
+--- ace
| +--- *.h *.inl
+--- bin
+--- lib
+--- lib*.so
インクルードパスとライブラリパスを適切に指定すればOKです。
work$ g++ -I/usr/local/ace -L/usr/local/ace/lib -lACE hello_ace.cpp work$ ./a.out Hello, ACE World! work$
ACEが以下のディレクトリにインストールされている場合
D:\lib\ace
+--- ace
| +--- *.h *.inl
+--- bin
| +--- *.dll
+--- lib
+--- *.lib
インクルードパスとライブラリパスを適切に指定すればOKです。
C:\work>cl /DWIN32 /Id:\lib\ace /GX /MD hello_ace.cpp
/link /LIBPATH:d:\lib\ace\lib ace.lib
:
C:\work>hello_ace
Hello, ACE World!
C:\work>
C:\work>cl /DWIN32 DACE_AS_STATIC_LIBS /Id:\lib\ace /GX /MD hello_ace.cpp
/link /LIBPATH:d:\lib\ace\lib advapi32.lib aces.lib
:
C:\work>hello_ace
Hello, ACE World!
C:\work>
スタティック版でビルドする際は、aces.libの中から呼び出しているシステム・ライブラリをリンク時に指定しなくてはならないようです。また、aces.libをビルドする時にデフォルトでランタイムライブラリはDLLを指定しているので、/MDを指定する必要があります。
複数のソースファイルからプログラムを構成するときは、makeやVisual Studio等の統合開発環境といったビルドツールを使用し、Makefileや各統合開発環境用のプロジェクト設定を記述します。
いくつものACE+TAOアプリケーション・プログラムを作っていく際、それぞれ毎に1からプロジェクト設定を記述するのはとても面倒です。特にVisual Studioの場合、プロジェクト1個1個にリンクするライブラリ名やプリプロセッサ定義をGUI上で入力する必要があり、かなりの手間となります。
そこで、ACE+TAOでは、MPC(Makefile, Project, and Workspace Creator)と呼ばれるプロジェクト設定作成ツールを使うと便利です。なお、MPCの詳細は合わせて「MPCの使い方」ページを参照ください。
MPCの設定では、ACEのインクルードファイル、ライブラリファイルの存在するディレクトリを、環境変数ACE_ROOTで示す必要があります。
| 環境変数名 | 設定内容(UNIX) | 設定内容(Windows) |
|---|---|---|
| ACE_ROOT |
/usr/local/ace |
D:\lib\ace |
まず、以下のMPCのプロジェクト設定ファイルを記述します。
以下は、ACEライブラリをリンクして1つの実行ファイルを構築するプロジェクト設定ファイルです。
project(hello_ace) : aceexe {
exename = hello_ace
Source_Files {
hello_ace.cpp
}
}
|
次に、ビルドツール固有のプロジェクト設定ファイルをMPCツールを使って生成します。
UNIX系
work$ mpc.pl -type make hello_ace.mpc
:
work$
Makefile.hello_aceが生成されます。
VC++ 7.1
work$ mpc.pl -type vc71 hello_ace.mpc
:
work$
VC++ 8.0
work$ mpc.pl -type vc8 hello_ace.mpc
:
work$
hello_ace.vcprojが生成されます。
以下は、ACEライブラリをリンクして1つの共有ライブラリ・ファイルを構築するプロジェクト設定ファイルです。
project(hello_ace_lib) : acelib {
sharedname = hello_ace
libout = .
Source_Files {
hello_ace_lib.cpp
}
Header_Files {
hello_ace_lib.h
}
}
|
liboutを指定しないと、デフォルトの場所$(ACE_ROOT)/libに出力されてしまいます。
ACEでは、ロギングのためのフレームワークが用意されています。
| #include | ace/Log_Msg.h |
ace/Log_Msg.hをインクルードすると、以下のロギング用マクロが利用できます。ログ出力は標準エラー出力に行われます。
| ロギング用マクロ定義 | 内容 | op_status | コンパイル制御 |
ACE_ASSERT(test) |
assert()とほぼ同様。testが偽ならば、メッセージとファイル名・行番号を出力しアボートする。 | ー |
ACE_NDEBUG |
ACE_HEX_DUMP( (level, buffer, size [,text])) |
bufferの内容を16進文字列で出力する。textが指定されていたらダンプの直前に表示する。 | 0 |
ACE_NOLOGGING |
ACE_RETURN(value) |
関数からリターンする。 | value | |
ACE_ERROR_RETURN( (level, string, ...), value) |
stringをlevelで指定したログレベルで記録し、関数からリターンする。 | value | |
ACE_ERROR( (level, string, ...)) |
stringをlevelで指定したログレベルで記録する。 | -1 | |
ACE_DEBUG( (level, string, ...)) |
stringをlevelで指定したログレベルで記録する。 | 0 | |
ACE_ERROR_INIT(value, flags) |
ログ機構のオプションフラグをflagsに設定する | value | |
ACE_ERROR_BREAK( (level, string, ...)) |
breakに続いてACE_ERRORを呼び出す。 使用例)for/while文の中でエラーメッセージを表示し抜ける時 |
||
ACE_TRACE(string) |
ファイル名、行番号、stringを表示する。また、このACE_TRACEを含むブロックから出る時にLeaving 'string'と表示する。 |
ACE_NTRACE |
通常、ACE_NTRACEマクロは1(TRACE出力抑制)になっているので、ACE_TRACEを有効にするには、コンパイル時にACE_NTRACE=0を定義する必要があります。
ACE_DEBUG()マクロを使用してデバッグプリントを行うことができます。
ACE_DEBUG((LM_DEBUG, "recvBuffer[n-1]=%x\n", recvBuffer[n-1]));
マクロの第一引数はデバッグレベルを示す定数です。以下のように規定されています。
| シンボル | 値 | 内容 |
|---|---|---|
| LM_SHUTDOWN | 1 | |
| LM_TRACE | 2 | 関数呼び出しのトレースに使用 |
| LM_DEBUG | 4 | デバッグ時に使用する場合 |
| LM_INFO | 8 | |
| LM_NOTICE | 16 | エラーではないが特別な扱いが必要 |
| LM_WARNING | 32 | 警告メッセージ |
| LM_STARTUP | 64 | ロギングの初期化 |
| LM_ERROR | 128 | エラーメッセージ |
| LM_CRITICAL | 256 | 致命的な状況(ハード障害など) |
| LM_ALERT | 512 | 至急対処が必要な状況 |
| LM_EMERGENCY | 1024 | パニック状況。 |
| LM_MAX | 1024 |
マクロの第二引数は、printf書式指定と同様の文字列を指定します。書式指定はロギングに便利な機能がいろいろ揃えられています。
| 書式 | 型 | 内容 | 書式 | 型 | 内容 |
| a | - | プリント後プログラムをアボート | R | int | 10進数 |
| c | char | 単一文字 | S | int | シグナルの名前 |
| C | char* | 文字列 | s | ACE_TCHAR* | 文字列、ワイド文字列を含む |
| i, d | int | 数値 | T | - | 現在時刻 hh:mm:ss.uuuuuu |
| I | - | インデント | D | - | 現在日時 mm/dd/yy hh:mm:ss.uuuuuuuu |
| e,E,f,F,g,G | double | 倍精度浮動小数 | t | - | スレッドID |
| l | - | 行番号 | u | int | 符号無し10進数 |
| M | - | メッセージのデバッグレベル | w | wchar_t | 単一ワイド文字 |
| m | - | errnoに対応するメッセージ | W | wchar_t* | ワイド文字列 |
| N | - | マクロが書かれたファイル名 | x, X | int | 16進数 |
| n | - | プログラム名(初期化時に指定要) | @ | void* | 16進でポインタ値 |
| o | int | 8進数 | % | N/A | '%' |
| P | - | プロセスID | |||
| p | ACE_TCHAR* | perrorのようにerrnoに対応した文字列 | |||
| Q | ACE_UINT64 | 10進数 | |||
| r | void (*)() |
コンパイル時に、ACE_NLOGGINGを定義すると、デバッグプリントはすべて抑制されます。
デバッグプリントのレベルを制御するには、ACE_Log_Msgクラスのpriorityメンバ関数を使用します。
u_long priority_mask = ACE_LOG_MSG->priority_mask(ACE_Log_Msg::PROCESS); ACE_CLR_BITS(priority_mask, LM_DEBUG | LM_INFO); ACE_LOG_MSG->priority_mask(priority_mask, ACE_Log_Msg::PROCESS);
とすれば、以降LM_DEBUGとLM_INFOを指定したACE_DEBUG等の出力が抑制されます。
int main(int argc, char* argv[]) {
ACE_LOG_MSG->open(argv[0], ACE_Log_Msg::STDERR);
初期化としてプログラム名とロギングの出力先を設定しています。初期化を呼ばなくてもデフォルトでロギングの出力先が標準エラー出力になりますが、プログラム名は設定されません。
デフォルトのロギングではメッセージにタイムスタンプが付きません。以下の設定で、各ログにタイムスタンプを付与することができます。
ACE_LOG_MSG->set_flags(ACE_Log_Msg::VERBOSE_LITE);
VERBOSE_LITEの場合、タイムスタンプとログ優先度が付与されます。以下に例を示します。
Jan 13 22:00:51.682 2008@LM_DEBUG@Using TCP Server port 9999
VERBOSEにすると、VERBOSE_LITEに加えてさらにホスト名、プロセスIDが追加されます。
デフォルトのロギングは標準エラー出力に吐き出されるので、標準エラー出力をリダイレクトすればファイルに落とすことと同じです。
しかし、画面とファイル、またさらにsyslogなどに出力したいときなどの設定ができます。
ToDo:
ACE_Get_Optクラスを使うと、コマンドラインオプションの解析が簡潔に記述できます。
| クラス名 | ACE_Get_Opt |
| #include | ace/Get_Opt.h |
簡単な例を以下に述べます。
書式:sample.exe [-c count] [-n name] [-x]
#include <ace/Get_Opt.h>
int main(int argc, char *argv[])
{
ACE_Get_Opt get_opts(argc, argv, ACE_TEXT("c:n:x"));
int opt;
int count;
char *name;
bool isXMode = false;
while ( (opt=get_opts()) != -1 ) {
switch (opt) {
case 'c':
count = ACE_OS::atoi(get_opts.opt_arg());
break;
case 'n':
name = get_opts.optarg;
break;
case 'x':
isXMode = true;
break;
default:
print_usage();
return -1;
}
}
:
UDPマルチキャストによるネットワーク通信のプログラム・サンプルを作成します。
multicast
+--- multicast.mwc
+--- mcast_send
| +--- mcast_send.mpc
| +--- mcast_send.cpp
+--- mcast_recv
+--- mcast_recv.mpc
+--- mcast_recv.cpp
workspace {
mcast_recv
mcast_send
}
今回は、2つのプロジェクト(mcast_send, mcast_recv)の2つを束ねるだけのワークスペースなので、上記のような記述になります。
project : aceexe {
}
ACEライブラリを使用する実行ファイルを生成するプロジェクトなので、aceexeプロジェクト設定を継承する記述をしています。それ以外はデフォルトの設定となります。
project : aceexe {
}
ACEライブラリを使用する実行ファイルを生成するプロジェクトなので、aceexeプロジェクト設定を継承する記述をしています。それ以外はデフォルトの設定となります。
D:\work\multicast> mwc.pl -type vc71 multicast.mwc Using .../lib/tao/bin/MakeProjectCreator/config/MPC.cfg Generating 'vc71' output using default input Generation Time: 0s D:\work\multicast>
この結果、以下のVisual C++ 7.1用ソリューション・プロジェクトファイルが生成されます。
multicast
+--- multicast.sln
+--- mcast_send
| +--- mcast_send.vcproj
+--- mcast_recv
+--- mcast_recv.vcproj
注)ACE_TEXTの使用によるワイド文字列対応は使用していません。
mcast_recv.cpp
// -*- C++ -*-
#include <ace/OS.h>
#include <ace/Log_Msg.h>
#include <ace/INET_Addr.h>
#include <ace/SOCK_Dgram_Mcast.h>
int main(int argc, char* argv[])
{
if (argc < 3) {
ACE_ERROR_RETURN((LM_ERROR, "引数が足りません\n"
"\t書式:%s <マルチキャストアドレス>"
" <ポート番号>\n"
, argv[0]), -1);
}
char* mcastAddrText = argv[1];
u_short mcastPort = ACE_OS::atoi(argv[2]);
ACE_DEBUG((LM_DEBUG, "[%s]:%d\n", mcastAddrText, mcastPort));
ACE_INET_Addr mcastAddr(mcastPort, mcastAddrText);
ACE_SOCK_Dgram_Mcast mcast;
if (-1 == mcast.join(mcastAddr)) {
ACE_ERROR_RETURN((LM_ERROR, "%p\n", "join"), -1);
}
char buff[64];
while (true) {
ACE_OS::memset(buff, 0, sizeof(buff));
ssize_t recvLen = mcast.recv(buff, sizeof(buff), mcastAddr);
if (recvLen > 0) {
ACE_DEBUG((LM_DEBUG, "Mcast data received (%d bytes)\n", recvLen));
} else if (recvLen == -1) {
ACE_ERROR((LM_ERROR, "ACE_SOCK_Dgram %p)\n", "recv error"));
}
ACE_OS::sleep(1); // [sec]
}
ACE_RETURN(0);
}
ACE_INTE_Addrクラスのコンストラクタには、いくつかのバリエーションがあります。今回は、以下のポート番号(u_short型)とホスト名(char*)を指定したコンストラクタでACE_INTET_Addrクラスのインスタンスを生成します。
ACE_INET_Addr (u_short port_number,
const char host_name[],
int address_family = AF_UNSPEC);
アドレス・ファミリをAF_UNSPECにしているので、ホスト名の文字列に応じてIPv4またはIPv6のアドレス情報が生成されます。
コネクションレスのマルチキャスト用ソケット(ACE_SOCK_Dgram_Mcast)クラスを生成し、次に受信するマルチキャストのアドレス・ポートを設定(join)します。
引数なしでマルチキャスト用ソケットを生成します。
ACE_SOCK_Dgram_Mcast mcast;
先ほど生成したアドレス情報を引数にjoinメンバ関数を呼び出します。内部では、setsockoptコールでマルチキャスト受信の登録を行っています。
if (-1 == mcast.join(mcastAddr)) {
ACE_ERROR_RETURN((LM_ERROR, "%p\n", "join"), -1);
}
マルチキャスト・ソケットからデータ受信を行います。
ssize_t recvLen = mcast.recv(buff, sizeof(buff), mcastAddr);
Windows版では、受信したデータグラムの大きさが上記のrecv引数に渡すバッファの大きさより大きいときは、エラーとなります。
TCPによるネットワーク通信のプログラム・サンプルを作成します。
TCPクライアントからの接続を待ち、接続が完了するとデータを受信するサンプルを以下に示します。サーバの挙動には、同時に1つのクライアントからの接続を処理するサーバ、複数の接続を並行して処理するサーバがあります。複数接続の処理には、マルチスレッド、非同期(select)などの手法があります。
まず簡単にACEのソケットプログラミングを見るために、同時に単一のクライアントからの接続だけを処理するシングルスレッドのTCPサーバを作成します。
TCPサーバではポート番号を指定して待ちうけるので、ACE_INET_Addrオブジェクトをポート番号(またはTCPサービス名文字列)を指定して生成します。コンストラクタに引数でポート番号を渡さず、setメンバ関数でポート番号を指定しているのは、戻り値でエラーを取るためです。エラー処理は、後ほど示すTCPサーバ全体コードに記述しています。
ACE_INET_Addr portToListen;
portToListen.set(8181);
TCPクライアントからのコネクション要求を待ち受けるために、ACE_SOCK_Acceptorオブジェクトを待ちうけアドレス(上述)およびポート再利用有無を指定して生成します。
ACE_SOCK_Acceptor acceptor;
acceptor.open(portToListen, 1);
実際にTCPクライアントからのコネクション要求があるまでブロックして待ち続けるacceptメンバ関数を呼びます。コネクションが確立後、リード・ライト操作をする対象となるストリームは、ACE_SOCK_Streamオブジェクトとなります。acceptメンバ関数の引数に指定します。
ACE_SOCK_Stream peer;
acceptor.accept(peer);
コネクション確立後、ACE_SOCK_Streamオブジェクトのrecvメンバ関数を呼んで、TCPクライアントからのデータを受信します。ここでは記述していませんが、TCPクライアントへ送信するときは、同じACE_SOCK_Streamオブジェクトのsendメンバ関数を呼びます。
char buffer[4096];
ssize_t bytesReceived = peer.recv(buffer, sizeof(buffer));
以下に、TCPサーバ(単一クライアント版)のコード全体を示します。
#include <ace/INET_Addr.h>
#include <ace/SOCK_Acceptor.h>
#include <ace/Log_Msg.h>
#include <string>
#include <iostream>
void dieWithError(const std::string& message);
int main(int argc, char* argv[])
{
ACE_INET_Addr portToListen;
if (portToListen.set(8181) == -1) {
dieWithError("ACE_INET_Addr::set failed.");
}
ACE_SOCK_Acceptor acceptor;
if (acceptor.open(portToListen, 1) == -1) {
dieWithError("ACE_SOCK_Acceptor::open failed.");
}
for (;;) {
ACE_SOCK_Stream peer;
ACE_DEBUG((LM_DEBUG, ACE_TEXT("waiting for remote connection...\n")));
if (acceptor.accept(peer) == -1) {
dieWithError("ACE_SOCK_Acceptor::accept failed.");
}
ACE_DEBUG((LM_DEBUG, ACE_TEXT("accepted.\n")));
char buffer[4096];
ssize_t bytesReceived;
while ((bytesReceived = peer.recv(buffer, sizeof(buffer))) != -1) {
if (bytesReceived == 0) { // peer reset connection
break;
}
ACE_HEX_DUMP((LM_DEBUG, buffer, bytesReceived, ACE_TEXT("TCP received")));
}
peer.close();
}
}
void dieWithError(const std::string& message) {
ACE_ERROR((LM_ERROR, ACE_TEXT("[Error]%p : "), ACE_TEXT(message)));
ACE_OS::exit(1);
}
ブログの記事参照
ToDo: いずれ記載予定