OutputDebugString

Win32プロジェクトでデバッグログを出したい場合、C/C++の標準出力系の関数などは使えない。
APIの標準出力も面倒だし、ファイル出力にしても若干面倒。
VisualStudioなどIDEに出すなら、OutputDebugStringを使うのがいい。

OutputDebugString function (Windows)

Win32プロジェクト
Win32プロジェクト

Win32プロジェクトで、

Windows アプリケーション
Windows アプリケーション

Windows アプリケーションで空のプロジェクト作成。

OutputDebugString.cpp
OutputDebugString.cpp

OutputDebugString.cppを追加。

_tWinMainの中で、OutputDebugStringで、"ABCDE"と改行を出力する。

出力
出力

出力ウィンドウを出す。

デバッグなしで開始
デバッグなしで開始

デバッグなしで開始だと、出力元の表示が[ビルド]になってしまって、何も出力されない。

デバッグ開始
デバッグ開始

デバッグ開始にすると、

デバッグ文字列
デバッグ文字列

出力元の表示が[デバッグ]になって、"ABCDE"が出力されている。

Sample/winapi/OutputDebugString/OutputDebugString/src/OutputDebugString at master · bg1bgst333/Sample · GitHub

DestroyWindow

DestroyWindowは、指定されたウィンドウハンドルのウィンドウを破棄する。

DestroyWindow function | Microsoft Docs

空のプロジェクトから、メニューリソースを追加して、

DestroyWindow.rcはこのようにアイテム1つだけのメニューとする。

resource.hは、

メニューとアイテムに加えてボタン用リソースIDも用意。

DestroyWindow.cppで、

ID_ITEM_1_1を選択した時も、ID_BUTTON1を押した時も、DestroyWindowでhwndが指すウィンドウを破棄する。

実行時
実行時

実行するとこうだが、

Item1-1
Item1-1

Item1-1を選択すると、ウィンドウは閉じる(実際は破棄されている)し、

Button1
Button1

Button1を押しても、ウィンドウは閉じる。(実際は破棄されている。)

Sample/winapi/DestroyWindow/DestroyWindow/src/DestroyWindow at master · bg1bgst333/Sample · GitHub

InsertMenuItem

InsertMenuItemは、メニューにアイテムを挿入する。

InsertMenuItemA function (winuser.h) - Win32 apps | Microsoft Docs

メニューリソースを使わず、コード上でメニューアイテムを挿入する。
WM_CREATEの時に、

MENUITEMINFOにアイテム情報をセットして、InsertMenuItemで指定のメニューと位置に挿入する。

起動時
起動時

起動時。

Item1に3つ入ってる
Item1に3つ入ってる

Item1に3つ入ってる。

Item2にも3つ入ってる
Item2にも3つ入ってる

Item2にも3つ入ってる。

Sample/winapi/InsertMenuItem/InsertMenuItem/src/InsertMenuItem at master · bg1bgst333/Sample · GitHub

AdjustWindowRect

AdjustWindowRectは、クライアント領域が指定のサイズになるようなウィンドウサイズを計算する。

AdjustWindowRect function (winuser.h) - Win32 apps | Microsoft Docs

resource.hで、

メインメニュー本体とVGA(640x480)にするアイテム。

AdjustWindowRect.rcで、

"SizeVGA(640x480)(&V)"を選択すると、VGAになる。

AdjustWindowRect.cppは、

WM_SIZEでウィンドウサイズが変化するたびに、ウィンドウサイズとクライアントサイズを計算し保持しておく。

WM_PAINTでウィンドウサイズとクライアントサイズを描画。

"SizeVGA(640x480)"が選択されたら、(0, 0, 640, 480)でAdjustWindowRectに渡す。
そうすると、そのクライアントサイズに対応するウィンドウサイズに更新される。
現在位置を取得し、そこを起点にウィンドウサイズになるウィンドウにMoveWindowでリサイズする。

最初はこう。
最初はこう。

最初はこう。

SizeVGAを選択
SizeVGAを選択

SizeVGAを選択。

クライアントサイズが640x480になってる
クライアントサイズが640x480になってる

クライアントサイズが640x480になってる。
それに合わせたウィンドウサイズになっているということ。

Sample/winapi/AdjustWindowRect/AdjustWindowRect/src/AdjustWindowRect at master · bg1bgst333/Sample · GitHub

RasEntryDlg

RasEntryDlgは、VPNダイアルアップ接続エントリーの作成・編集ダイアログを表示する・・・。

RasEntryDlg 関数

今回は、VPN接続のためにRasEntryDlgで電話帳エントリーの作成を行う・・・。

f:id:BG1:20170320120851p:plain

今回はWin32プロジェクト・・・。

f:id:BG1:20170320120941p:plain

RasEntryDlg.cppを追加したら、

f:id:BG1:20170320121059p:plain

rasdlg.libをリンク・・・。

RASENTRYDLG型rasDlgを0で初期化しておいて、rasDlg.dwSizeにサイズ、rasDlg.dwFlagsにRASEDFLAG_NewEntry(新しいエントリーの追加)を指定する・・・。
あとは、RasEntryDlgにエントリー名とrasDlgを渡すだけで表示される・・・。
最終的に作成されればTRUEでこの時はメッセージを出す・・・。
エラーやキャンセルならFALSE・・・。

f:id:BG1:20170320121844p:plain

実行すると、これが出てくる・・・。
VPNで、

f:id:BG1:20170320121929p:plain

xで伏せてるけど、ここにサーバアドレス(URLでもIPでも)と接続名を入れる・・・。

f:id:BG1:20170320122032p:plain

ユーザ名、パスワードなどを入れる・・・。

f:id:BG1:20170320122121p:plain

作成成功すると、こっちで用意したメッセージボックスが出る・・・。

f:id:BG1:20170320122227p:plain

そしてエントリーが作成されてる・・・。

Sample/winapi/RasEntryDlg/RasEntryDlg/src/RasEntryDlg at master · bg1bgst333/Sample · GitHub

ServiceMain

今回はWindowsのシステムサービスを作る・・・。

ServiceMainは、このサービスのメイン処理を担う部分・・・。

ServiceMain 関数

まず、

f:id:BG1:20170318144512p:plain

コンソールアプリケーションで作成・・・。
(Win32プロジェクトでもできるが、コンソールアプリだと疑似サービスを作成して独自に追加した処理をデバッグするような方法ができるので、こちらのほうがいい・・・。)

ServiceMain.cppで、

必要な関数、グローバル変数の宣言・・・。

エントリポイントはServiceMain・・・ではなく、

_tmainである・・・。(または_tWinMainでも可・・・。)
この部分は、[コントロールパネル]の[管理ツール]の[サービス]でServiceMainサービスの開始をした時に実行される・・・。
SERVICE_TABLE_ENTRY型変数steでサービス名とServiceMain関数のアドレスを指定したら、StartServiceCtrlDispatcherで登録・・・。
以降、制御がStartServiceCtrlDispatcherの中に移り、サービスが終了するまで出てこない・・・。
ServiceMainが別スレッドで実行されて、以降はServiceMainの処理に移る・・・。
あと、一応動作確認用のイベントログの準備をしている・・・。

ServiceMainでは、

RegisterServiceCtrlHandlerでさまざまなサービス操作に対するハンドラHandlerを登録する・・・。

SetServiceStatusでサービス状態をSERVICE_START_PENDING(開始中)にセット・・・。
イベントログにも"SERVICE_START_PENDING"・・・。
でそのあとにサービス開始時の独自の処理とかを書くが、今回は何もしない・・・。

独自の処理が終ったら、SetServiceStatusでサービス状態をSERVICE_RUNNING(実行中)にセット・・・。
イベントログにも"SERVICE_RUNNING"・・・。
bRunをTRUEにして、サービスメインループが続くようにする・・・。

今回は、1秒ごとに"ServiceMainLoop!"をイベントログに出力するだけ・・・。

さて、Handlerでは、

SERVICE_CONTROL_STOP(サービスの停止)が要求された時、SetServiceStatusでサービス状態をSERVICE_STOP_PENDING(停止中)にする・・・。
(停止にはしない・・・。あくまでも、これから停止するという状態・・・。)
"SERVICE_STOP_PENDING"のイベントログを出力したら、停止処理・・・といっても、bRunをFALSEにするだけ・・・。
これで、ServiceMainのループから抜ける・・・。
また、

SERVICE_CONTROL_INTERROGATEでは、システムからサービス状態の更新要求が来た時に、SetServiceStatusで更新する・・・。
ただ、今回は1度も来なかった・・・。

ServiceMainループから抜けたら、SetServiceStatusでSERVICE_STOPPEDをセット・・・。これで完全に停止したと言えるので、イベントログで"SERVICE_STOPPED"と出力して終わる・・・。

f:id:BG1:20170318152904p:plain

管理者権限でcmd.exeを実行・・・。
exeのある場所に移動したら、

sc create <サービス名> binPath= <exeのフルパス>

でサービスを登録する・・・。

C:\Project\Cloud\github.com\Sample\winapi\ServiceMain\ServiceMain\src\ServiceMai
n\debug>sc create ServiceMain binPath= "C:\Project\Cloud\github.com\Sample\winap
i\ServiceMain\ServiceMain\src\ServiceMain\debug\ServiceMain.exe"
[SC] CreateService SUCCESS

C:\Project\Cloud\github.com\Sample\winapi\ServiceMain\ServiceMain\src\ServiceMai
n\debug>

こんな感じ・・・。

f:id:BG1:20170318153917p:plain

[コントロールパネル]から[サービス]を選択・・・。

f:id:BG1:20170318154031p:plain

ServiceMainが追加されてる・・・。
開始はしていない・・・。

f:id:BG1:20170318154352p:plain

手動で開始・・・。

f:id:BG1:20170318154514p:plain

[コントロールパネル]から[イベントビューアー]を見ると、

f:id:BG1:20170318154731p:plain

ServiceMainでSERVICE_START_PENDINGが出力されているのが確認できる・・・。

f:id:BG1:20170318155008p:plain

その次にはSERVICE_RUNNINGも出ているし、

f:id:BG1:20170318155158p:plain

以降はServiceMainLoop!が出ている・・・。

f:id:BG1:20170318155506p:plain

停止すると、

f:id:BG1:20170318155557p:plain

こうなり、

f:id:BG1:20170318155658p:plain

SERVICE_STOP_PENDINGが出て、

f:id:BG1:20170318155905p:plain

最終的には、SERVICE_STOPPEDとなる・・・。
(SERVICE_CONTROL_INTERROGATE)はなかった・・・。

ちなみに、サービスの削除は、

C:\Project\Cloud\github.com\Sample\winapi\ServiceMain\ServiceMain\src\ServiceMai
n\debug>sc delete ServiceMain
[SC] DeleteService SUCCESS

で、

f:id:BG1:20170318160244p:plain

確かにServiceMainが消えてる・・・。

Sample/winapi/ServiceMain/ServiceMain/src/ServiceMain at master · bg1bgst333/Sample · GitHub

CreateNamedPipe

Windowsで名前付きパイプを作成するには、CreateNamedPipeを使う・・・。

CreateNamedPipe 関数

名前なしパイプと比べると、あそこまで大変ではない・・・。

CreateNamedPipeプロジェクトにCreateNamedPipe.cppを追加し、

CreateNamedPipeで"\\.\pipe\TEST"という名前付きパイプを作成し、hNamedPipeに格納・・・。
PIPE_ACCESS_DUPLEXだと1つで読み書き両方できる模様・・・。
("\\.\pipe\"を付けるのが決まり・・・。)

作成したら、ConnectNamedPipeで接続する・・・。

3秒待って、WriteFileでhNamedPipeに"I am Parent!"を書き込む・・・。

9秒待って、ReadFileでhNamedPipeから読み込んでszBuf2に格納して変換して出力・・・。

FlushFileBuffersでバッファをフラッシュ・・・。
DisconnectNamedPipeで切断・・・。
CloseHandleでhNamedPipeを閉じる・・・。

ChildProcessプロジェクトを作成し、ChildProcess.cppを追加し、

こちら側はCreateFileで"\\.\pipe\TEST"という名前付きパイプをOPEN_EXISTINGで開き、ハンドルはhNamedPipeに格納・・・。
(失敗したらエラーで終了・・・。)

6秒後にReadFileでhPipedNameから読み込んだ内容を出力・・・。

その3秒後にhNamedPipeに"I am Child!"と書き込む・・・。

CloseHandleでhNamedPipeを閉じる・・・。
(フラッシュとか切断とかしなくていいのか・・・。)

CreateNamedPipe.exeとChildProcess.exeを同時に実行すると、
CreateNamedPipe.exe側は3秒後に、

Parent: I am Parent!

で、その3秒後にChildProcess.exeで、

Child: I am Parent!

でその3秒後に、

Child: I am Parent!
Child: I am Child!

となり、その3秒後にCreateNamedPipe.exeは、

Parent: I am Parent!
Parent: I am Child!

となる・・・。

Sample/winapi/CreateNamedPipe/CreateNamedPipe/src/CreateNamedPipe at master · bg1bgst333/Sample · GitHub