読者です 読者をやめる 読者になる 読者になる

NetworkInterface

.NET Framework

NetworkInterfaceは、ネットワークインターフェイスに関する情報を持つクラス・・・。

NetworkInterface クラス (System.Net.NetworkInformation)

ネットワークインターフェイスの名前一覧を出力する・・・。

f:id:BG1:20170321153432p:plain

空の場合は、

f:id:BG1:20170321153453p:plain

Systemを参照し、

usingではSystem.Net.NetworkInformationを指定する・・・。
(クラス名はNetworkInterface、参照はSystem、ややこしい・・・。)
NetworkInterface.GetAllNetworkInterfacesでNetworkInterfaceの配列を取得し、niに格納・・・。
niの中からforeachでインターフェイスnを取り出し、n.Nameを出力・・・。これを繰り返す・・・。

n.Name = ローカル エリア接続
n.Name = Loopback Pseudo-Interface 1
n.Name = isatap.{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
続行するには何かキーを押してください . . .

伏せてるけど、こんな感じで3つ出た・・・。

Sample/dotnet/NetworkInterface/NetworkInterface/src/NetworkInterface_ at master · bg1bgst333/Sample · GitHub

RasEntryDlg

WindowsAPI

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

WindowsAPI

今回は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

WindowsAPI

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

CreatePipe

WindowsAPI

Windowsで名前なしパイプを作成するには、CreatePipeを使う・・・。

CreatePipe 関数

WindowsAPIにはforkが無いし、パイプの扱い方もUNIXとは違うので、パイプによるプロセス間通信の実装は結構難しい・・・。

C言語CreatePipeメモ(Hishidama's Windows C "CreatePipe" Memo)

上記を参考に、UNIXシステムコールのpipeでやったようなプログラムをWindowsAPIでもやってみる・・・。

f:id:BG1:20170316230844p:plain

コンソールの、

f:id:BG1:20170316230937p:plain

空プロジェクトで、CreatePipe.cppを追加したら、

親が書き込んで子が読むパイプ配列hPipe1、子が書き込んで親が読むパイプ配列hPipe2、子が親からの読み込みに使うパイプhChildRead、子が親への書き込みに使うパイプhChildWrite、の6つのハンドルを用意・・・。

CreatePipeでhPipe1[0]に読み込みパイプ、hPipe1[1]に書き込みパイプが格納される・・・。
しかし、実際にはhPipe1[0]は親側では使わない・・・。
そして、第3引数がNULLだと、子プロセスに渡して使わせることができない・・・。
しかし、ここではあえてNULLにしている・・・。

いったんどちらのパイプも継承不可能にしつつ、DuplicateHandleでhPipe1[0]を複製してhChildReadに格納・・・。
ここで第6引数がTRUEだと、こちらのhChildReadは子プロセスに渡して読み込みに使うことができる・・・。

使わないhPipe1[0]はCloseHandleで閉じる・・・。

ここまでで、親プロセスの書き込みはhPipe1[1]、子プロセスの読み込みはhChildReadとなる・・・。

hPipe2はさっきとは逆で、実質hPipe2[1]を使わない・・・。

hPipe2[1]を複製してhChildWriteとする・・・。

hPipe2[0]は親プロセスの読み込み、子プロセスからの書き込みはhChildWriteとなる・・・。

使わないhPipe2[1]を閉じる・・・。

CreateProcessで"ChildProcess.exe"を起動する・・・。
si.hStdInputに子が読み込む用のハンドルhChildReadを渡して、子プロセスの標準入力が親プロセスからの読み込みになるようにしている・・・。
si.hStdOutputに子が書き込む用のハンドルhChildWriteを渡して、子プロセスの標準出力が親プロセスへの書き込みになるようにしている・・・。

3秒待って、

"I am Parent!"を子プロセスに向けて書き込む・・・。

さらにその9秒後に子プロセスからのメッセージを読み込んで出力・・・。
今回、子プロセスの標準入力をhChildRead、標準出力をhChildWriteに換えているので、標準エラー出力に出力する_tperrorを使って、文字列出力をする・・・。

ChildProcess.cppは、

6秒後にfgetsで親プロセスから子プロセスに送られてきたメッセージを読み込んで"Child: "を付けて出力・・・。
そのあと、3秒待って、子プロセスから親プロセスにfputsで"I am Child!"を送る・・・。

2つのプログラムのうち、CreatePipeを実行すると、

Parent: I am Parent!: No error
Child: I am Parent!: No error
Child: I am Child!: No error
Parent: I am Child!: No error

3秒ごとにParent, Child, Child, Parentとなっていて、Childが"I am Parent!"を読み込んでたり、ParentでI am Child!を読み込んでたりするのがわかる・・・。

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

mkfifo

UNIXシステムコール

mkfifoは、名前付きパイプを作成する・・・。

Man page of MKFIFO

親プロセスと子プロセスの関係なら、forkでpipeを共有できるが、全く関係ない2つ以上のプロセス間で通信する場合は、名前付きパイプを使う・・・。
名前付きパイプはファイルのようなもので、lsコマンドで存在を確認できる・・・。

mkfifo1.cで、mkfifo1からmkfifo2への名前付きパイプ"TEST1"、mkfifo2からmkfifo1への名前付きパイプ"TEST2"を作る・・・。

3秒待って、TEST1にmsgを書き込む・・・。

3秒後(mkfifo2から見たら6秒後)にTEST1からbufに読み込む・・・。
で出力・・・。

その3秒後、TEST2に書き込む・・・。

mkfifo1からみて12秒後、TEST2からbufに読み込んで出力・・・。

mkfifo1は、3秒後に、

$ ./mkfifo1
mkfifo1 fd[1]: I am mkfifo1!

となり、その3秒後に

$ ./mkfifo2
mkfifo2 fd[0]: I am mkfifo1!

さらに3秒後、

$ ./mkfifo2
mkfifo2 fd[0]: I am mkfifo1!
mkfifo2 fd[1]: I am mkfifo2!
$

さらに3秒後、

$ ./mkfifo1
mkfifo1 fd[1]: I am mkfifo1!
mkfifo1 fd[0]: I am mkfifo2!
$

となる・・・。

Sample/unixsyscall/mkfifo/mkfifo/src/mkfifo at master · bg1bgst333/Sample · GitHub

ReportEvent

WindowsAPI

ReportEventは、イベントログにメッセージを書き込む・・・。

ReportEvent 関数

イベントログの出力はちゃんとやろうとすると結構面倒なので、今回は割と単純にログ出力するだけ・・・。

まず、TCHAR *型の文字列バッファポインタptszTextを宣言し、newでメモリを確保・・・。
_tcscpyで表示する文字列"Test EventLog Message"をセット・・・。

RegisterEventSourceでイベントソース(そのイベントが起きたアプリとでも思っておけばいい・・・。)を登録・・・。
第1引数は、サーバ名を指定するらしいが、PCローカルのログなのでNULL・・・。
第2引数は、イベントソース・・・。"ReportEvent"にしておいた・・・。
成功すると、ハンドルhEventLogが返るので、これをReportEventに渡す・・・。

EVENTLOG_INFORMATION_TYPEは情報イベントであることを示す・・・。
カテゴリは適当に100・・・。
イベントIDも適当に1001・・・。
ユーザ識別子はNULLでいい・・・。
出力する文字列の数は1・・・。
バイナリデータつけないので0・・・。
メッセージ文字列の配列のアドレスを指定するので、ptszTextのさらにアドレスを(LPCTSTR *)にキャストして渡す・・・。
バイナリデータはNULL・・・。

最後に、イベントログのハンドルを閉じて、ptszTextを解放・・・。

実行すると、

続行するには何かキーを押してください . . .

成功なら、アプリからは何も出力されないが、

f:id:BG1:20170315140209p:plain

こんな感じでログが出力されている・・・。
ただし、イベントIDの対応付けなどちゃんと動くために必要なものがないので、エラーみたいな文章が出ている・・・。

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