﻿/*
さて、このようなDDEサーバーを作るにはどうしたらよいのでしょうか。


１．DdeInitialize関数でDDEを初期化する。
２．DdeNameService関数でサービス名を登録する。
３．DDEコールバック関数でXTYP_ADVSTARTトランザクションを待つ
　　これが来たらアドバイズループ開始。
４．特定のトピック、項目のデータが変化したときDdePostAdvise関数を
　　呼んでサーバーのDDEコールバック関数にXTYP_ADVREQトランザクションを
　　送る。
５．DDEコールバック関数でXTYP_ADVREQが来たらデータのハンドルを
　　DdeCreateDataHandle 関数で作成して返す。
     (これでクライアントにデータが送られた)
     (４，５の繰り返し)
６．クライアントがアドバイズループの終了を要求したら
　　XTYP_ADVSTOPトランザクションが来るので適当な処理を行う。

ここでは、クライアントからアドバイズループ要求が来たら 刻々と現在時刻をクライアントに送りつづけるプログラムを作ります。
アドバイズループとはサーバーから次々とデータを送りだし クライアントがこれを処理する一連のループのことです。
*/

//        hotsv01.cpp

#ifndef STRICT
#define STRICT
#endif

#define SERVICE "hotsv01"
#define TOPIC "NEKO_DEMO_WAKARU"
#define ITEM "HOT_ITEM"

#define ID_MYTIMER 1

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HDDEDATA CALLBACK MyDdeProc(UINT, UINT, HCONV, HSZ, HSZ,
	HDDEDATA, DWORD, DWORD);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
int MyHotDDEStart(void);
int MyEndHotDDE(void);

char szClassName[] = "hotsv01"; //ウィンドウクラス

HWND hParent; //メインウィンドウのハンドル
HSZ hszService, hszTopic, hszItem;
DWORD ddeInst;
HCONV hConv;

// ここでは、サービス名を「hotsv01」トピック名を「NEKO_DEMO_WAKARU」 項目名を「HOT_ITEM」とします。
// いつもとちょっと違うのは、InitApp関数の戻り値がATOMになっている点です。 これは、特に意味はありませんが後で少し解説します。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
	LPSTR lpsCmdLine, int nCmdShow)
{
	MSG msg;
	
	if (!InitApp(hCurInst))
		return FALSE;
	if (!InitInstance(hCurInst, nCmdShow))
		return FALSE;
	while (GetMessage(&msg, NULL, 0, 0)){
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

//ウィンドウ・クラスの登録

ATOM InitApp(HINSTANCE hInst)
{
	WNDCLASSEX wc;
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WndProc; //プロシージャ名
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInst; //インスタンス
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = NULL; //メニュー名
	wc.lpszClassName = (LPCSTR)szClassName;
	wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	return(RegisterClassEx(&wc));
}
// さて、この関数は今まではBOOL型でしたが今回はATOMになっています。 RegisterCalss関数にせよ、RegisterClassEx関数にせよこの戻り値は ATOMです。
// したがってInitApp関数もATOMが自然ですね。 ATOMそのものについては後の章で解説する予定です。 （プログラムの本質とはあまり関係ありません）
//ウィンドウの生成

BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
	HWND hWnd;
	
	hWnd = CreateWindow(szClassName,
		"猫でもわかるDDEサーバー(hot)", //タイトルバーにこの名前が表示されます
		WS_OVERLAPPEDWINDOW, //ウィンドウの種類
		CW_USEDEFAULT, //Ｘ座標
		CW_USEDEFAULT, //Ｙ座標
		270, //幅
		70, //高さ
		NULL, //親ウィンドウのハンドル、親を作るときはNULL
		NULL, //メニューハンドル、クラスメニューを使うときはNULL
		hInst, //インスタンスハンドル
		NULL);
	if (!hWnd)
		return FALSE;
	hParent = hWnd; //グローバル変数にコピー
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	return TRUE;
}

// いつもとほとんど同じです。親ウィンドウのハンドルがDDEコールバック関数で 必要になってくるのでグローバル変数にコピーしています。 
// また、サーバーのクライアント領域には特に何も表示しないので ウィンドウを小さめに作っています。

//ウィンドウプロシージャ

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	int id;
	
	switch(msg){
		case WM_CREATE:
			MyHotDDEStart();
			break;
		case WM_TIMER:
			DdePostAdvise(ddeInst, hszTopic, hszItem);
			break;
		case WM_CLOSE:
			id = MessageBox(hWnd,
				"終了してもよいですか",
				"終了確認",
				MB_YESNO | MB_ICONQUESTION);
			if (id == IDYES){
				MyEndHotDDE();
				DestroyWindow(hWnd);
			}
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		default:
			return(DefWindowProc(hWnd, msg, wp, lp));
	}
	return 0;
}

// 親ウィンドウのプロシージャですが、WM_TIMERメッセージが来るたびに DdePostAdvise関数を呼んでDDEコールバック関数にXTYP_ADVREQトランザクションを 送っています。
//（SetTimer関数はDDEコールバック関数の中で呼んでいます。）

int MyHotDDEStart()
{
	UINT uResult;
	HDDEDATA hData;
	
	uResult = DdeInitialize(&ddeInst, MyDdeProc, APPCLASS_STANDARD, 0);
	if (uResult == DMLERR_NO_ERROR)
		MessageBox(hParent, "DdeInitialize成功", "OK", MB_OK);
	else {
		MessageBox(hParent, "DdeInitialize失敗", "Error", MB_OK);
		return -1;
	}
	hszService = DdeCreateStringHandle(ddeInst, SERVICE, CP_WINANSI);
	hszTopic = DdeCreateStringHandle(ddeInst, TOPIC, CP_WINANSI);
	hszItem = DdeCreateStringHandle(ddeInst, ITEM, CP_WINANSI);
	hData = DdeNameService(ddeInst, hszService, 0, DNS_REGISTER);
	if (hData == 0){
		MessageBox(hParent, "DdeNameService失敗", "Error", MB_OK);
		return -2;
	}
	
	return 0;
}

// DDEを初期化してサービス名を登録しています。

int MyEndHotDDE()
{
	HDDEDATA hData;
	BOOL bResult1, bResult2, bResult3;
	
	hData = DdeNameService(ddeInst, hszService, 0, DNS_UNREGISTER);
	if (hData != 0)
		MessageBox(hParent, "ネームサービス解除成功", "OK", MB_OK);
	else {
		MessageBox(hParent, "ネームサービス解除失敗", "Error", MB_OK);
		return -1;
	}
	bResult1 = DdeFreeStringHandle(ddeInst, hszService);
	bResult2 = DdeFreeStringHandle(ddeInst, hszTopic);
	bResult3 = DdeFreeStringHandle(ddeInst, hszItem);
	if (bResult1 * bResult2 * bResult3)
		MessageBox(hParent, "ストリングハンドル開放成功", "OK", MB_OK);
	else {
		MessageBox(hParent, "ストリングハンドル開放失敗", "Error", MB_OK);
		return -2;
	}
	if (DdeUninitialize(ddeInst)){
		MessageBox(hParent, "DdeUninitialize成功", "OK", MB_OK);
		return 0;
	} else {
		MessageBox(hParent, "DdeUninitialize失敗", "Error", MB_OK);
		return -3;
	}
}

// DDE終了時の関数です。

HDDEDATA CALLBACK MyDdeProc(UINT uType, UINT uFmt, HCONV hcconv, HSZ hszTpc1, HSZ hszTpc2,
	HDDEDATA hdata, DWORD dwData1, DWORD dwData2)
{
	char szBuffer[256];
	HDDEDATA hData;
	SYSTEMTIME st;
	
	switch(uType){
		case XTYP_CONNECT:
			//hszTpc2:サービス名 hszTpc1:トピック名
			DdeQueryString(ddeInst, hszTpc2, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, SERVICE)!= 0)
				return(HDDEDATA)FALSE;
			DdeQueryString(ddeInst, hszTpc1, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, TOPIC)!= 0)
				return(HDDEDATA)FALSE;
			MessageBox(hParent, "サーバに接続しました", szClassName, MB_OK);
			return(HDDEDATA)TRUE;
		case XTYP_ADVSTART:
			//hszTpc2:項目名 hszTpc1:トピック名
			DdeQueryString(ddeInst, hszTpc2, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, ITEM)!= 0)
				return(HDDEDATA)FALSE;
			DdeQueryString(ddeInst, hszTpc1, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, TOPIC)!= 0)
				return(HDDEDATA)FALSE;
			SetTimer(hParent, ID_MYTIMER, 500, NULL);
			MessageBox(hParent, "アドバイズループが開始されました。", szClassName, MB_OK);
			return(HDDEDATA)TRUE;
		case XTYP_ADVREQ:
			//hszTpc2:項目名 hszTpc1:トピック名
			DdeQueryString(ddeInst, hszTpc2, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, ITEM)!= 0)
				return(HDDEDATA)FALSE;
			DdeQueryString(ddeInst, hszTpc1, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, TOPIC)!= 0)
				return(HDDEDATA)FALSE;
			GetLocalTime(&st);
			wsprintf(szBuffer, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);
			hData = DdeCreateDataHandle(ddeInst, (LPBYTE)szBuffer, strlen(szBuffer)+ 1, 0,
				hszItem, CF_TEXT, 0);
			return hData;
		case XTYP_ADVSTOP:
			//hsz2:項目名 hsz1:トピック名
			DdeQueryString(ddeInst, hszTpc2, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, ITEM)!= 0)
				return(HDDEDATA)FALSE;
			DdeQueryString(ddeInst, hszTpc1, szBuffer, sizeof(szBuffer), CP_WINANSI);
			if (strcmp(szBuffer, TOPIC)!= 0)
				return(HDDEDATA)FALSE;
			MessageBox(hParent, "アドバイズループ終了", szClassName, MB_OK);
			KillTimer(hParent, ID_MYTIMER);
			return(HDDEDATA)TRUE;
	}
	return(HDDEDATA)FALSE;
}
/*
DDEコールバック関数です。
XTYP_ADVSTARTが来たらSetTimer関数を呼んで、0.5秒ごとに親のプロシージャに WM_TIMER メッセージを送ります。
親のプロシージャではWM_TIMERメッセージが 来るたびに DdePostAdvise 関数を実行して、DDEコールバック関数にXTYP_ADVREQ が来るようにします。 
DDEコールバック関数でXTYP_ADVREQが来たら現在時刻を 取得してこれを DdeCreateDataHandle 関数でデータハンドルにします。 そして，これをリターンしてクライアントに送ります。

クライアントからアドバイズループ終了要求が来たらタイマーを殺します。 ところで、この XTYP_ADVSTOP が来る前にサーバーを終了しても KillTimer関数は実行されません。
しかし、３２ビット版ではアプリケーションが 終了するとタイマはすべて開放されるのでKillTimer関数を呼ばなくでも大丈夫です。

さて、このサーバーにアドバイズループの開始を要求して、次々と送られてくる データを表示するクライアントを作ってみてください。
*/