﻿/*
１．DdeInitialize関数でDDEを初期化する。
２．DdeConnect関数でサーバーに接続する。
３．DdeClientTransaction関数でサーバーにXTYP_ADVSTARTを送る。
４．DDEコールバック関数でXTYP_ADVDATAを待つ。
５．XTYP_ADVDATAが来たらDdeGetData関数でデータを取得する。
６．アドバイズループが不要になったらサーバーにXTYP_ADVSTOPを送る。
７．DdeDisconnect関数で接続を切る（通信ハンドルを無効にする）。
８．DdeUninitialize関数で通信を終了する。

今までと，ほとんど変わりはありません。アドバイズループを開始するのに XTYP_ADVSTARTを送ります。あとは、DDEコールバック関数に XTYP_ADVDATAが来たら、データを取得してやりたい処理をします。 
アドバイズループを終了したいときはXTYP_ADVSTOPを送ってやります。
メニューの「DDE」「DDE初期化」を選択した後、「アドバイズループ開始要求」 を選択します。すると、サーバーから次々と時刻データが送られてくるので これを表示します。
サーバーを終了させてしまうと、当然時刻は止まってしまいます。

では、プログラムを見てみましょう。

*/

//        hotcl01.cpp

#ifndef STRICT
    #define STRICT
#endif

#define SERVICE "hotsv01"
#define TOPIC "NEKO_DEMO_WAKARU"
#define ITEM "HOT_ITEM"

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HDDEDATA CALLBACK DdeMyProc(UINT, UINT, HCONV,
                HSZ, HSZ, HDDEDATA, DWORD, DWORD);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
int MyDdeInit(HWND);
int MyDdeEnd(HWND);

char szClassName[] = "hotcl01";        //ウィンドウクラス
char szBuffer[256];

HWND hParent; //親ウィンドウのハンドル

HSZ hszService;
HSZ hszTopic;
HSZ hszItem;
DWORD ddeInst;
HCONV hConv;

// 接続するサービスはhotsv01（前章で作ったサーバー）、トピックはNEKO_DEMO_WAKARU、 項目名はHOT_ITEMとします。

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 = "MYMENU";    //メニュー名
    wc.lpszClassName = (LPCSTR)szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    return (RegisterClassEx(&wc));
}

// ウィンドウの生成

BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindow(szClassName,
            "猫でもわかるDDE-hotクライアント",//タイトルバーにこの名前が表示されます
            WS_OVERLAPPEDWINDOW,    //ウィンドウの種類
            CW_USEDEFAULT,    //Ｘ座標
            CW_USEDEFAULT,    //Ｙ座標
            240,//幅
            75,    //高さ
            NULL,    //親ウィンドウのハンドル、親を作るときはNULL
            NULL,    //メニューハンドル、クラスメニューを使うときはNULL
            hInst,    //インスタンスハンドル
            NULL);
    if (!hWnd)
        return FALSE;
    hParent = hWnd;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

// このへんはいつもと同じです。メインウィンドウの大きさは 時刻表示ができる程度に小さくしてあります。
// また、メインウィンドウの ハンドルは後で必要になるのでグローバル変数にコピーしておきました。

//ウィンドウプロシージャ

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    int id;
    HDC hdc;
    PAINTSTRUCT ps;

    switch (msg) {
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            TextOut(hdc, 10, 10, szBuffer, strlen(szBuffer));
            EndPaint(hWnd, &ps);
            break;
        case WM_COMMAND:
            switch (LOWORD(wp)) {
                case IDM_END:
                    SendMessage(hWnd, WM_CLOSE, 0, 0);
                    break;
                case IDM_INIT:
                    MyDdeInit(hWnd);
                    break;
                case IDM_HOTSTART:
                    DdeClientTransaction(NULL, 0, hConv, hszItem, CF_TEXT,
                        XTYP_ADVSTART | XTYPF_ACKREQ, 3000, NULL);
                    break;
                case IDM_DDEEND:
                    DdeClientTransaction(NULL, 0, hConv, hszItem, CF_TEXT, XTYP_ADVSTOP,
                        3000, NULL);
                    MyDdeEnd(hWnd);
                    break;
            }
            break;
        case WM_CLOSE:
            id = MessageBox(hWnd,
                "終了してもよいですか",
                "終了確認",
                MB_YESNO | MB_ICONQUESTION);
            if (id == IDYES) {
                if(DdeUninitialize(ddeInst) != 0)
                    MessageBox(hWnd, "DdeUninitialize成功", szClassName, MB_OK);
                else
                    MessageBox(hWnd, "DdeUninitialize失敗", szClassName, MB_OK);
                DestroyWindow(hWnd);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}

// 親ウィンドウのプロシージャです。
// XTYP_ADVSTARTとXTYPF_ACKREQを組み合わせるとデータをクライアント が確認するまでサーバーは次のデータを送らないようにします。
// XTYPF_NODATAと組み合わせると実際のデータは送られてきません。 （データの変更のみを知ることができる）

int MyDdeInit(HWND hWnd)
{
    UINT uResult;

    uResult = DdeInitialize(&ddeInst, DdeMyProc, APPCMD_CLIENTONLY, 0);
    if (uResult == DMLERR_NO_ERROR)
        MessageBox(hWnd, "DdeInitialize成功", "OK", MB_OK);
    else {
        MessageBox(hWnd, "DdeInitialize失敗", "Error", MB_OK);
        return -1;
    }
    hszService = DdeCreateStringHandle(ddeInst, SERVICE, CP_WINANSI);
    hszTopic = DdeCreateStringHandle(ddeInst, TOPIC, CP_WINANSI);
    hszItem = DdeCreateStringHandle(ddeInst, ITEM, CP_WINANSI);
    hConv = DdeConnect(ddeInst, hszService, hszTopic, NULL);
    if (hConv == 0) {
        MessageBox(hWnd, "DdeConnect失敗です", szClassName, MB_OK);
        if(DdeUninitialize(ddeInst) != 0)
            MessageBox(hWnd, "DdeUninitialize実行しました", szClassName, MB_OK);
        return -3;
    }
    return 0;
}

// DDEを初期化してサーバーに接続する関数です。これも，特に説明は不要ですね。

int MyDdeEnd(HWND hWnd)
{
    BOOL bResult, bService, bTopic, bItem;

    bResult = DdeDisconnect(hConv);
    if (bResult)
        MessageBox(hWnd, "DdeDisconnect成功", szClassName, MB_OK);
    bService = DdeFreeStringHandle(ddeInst, hszService);
    bTopic = DdeFreeStringHandle(ddeInst, hszTopic);
    bItem = DdeFreeStringHandle(ddeInst, hszItem);
    if (bService * bTopic * bItem) {
        MessageBox(hWnd, "DdeFreeStringHandle成功", szClassName, MB_OK);
        return 0;
    } else {
        MessageBox(hWnd, "DdeFreeStringHandle失敗", szClassName, MB_OK);
        return -1;
    }
}

// 通信を終了する関数です。この中ではDdeUninitialize関数は呼んでいません。
// アプリケーション終了時に呼んでいます（親のプロシージャを見て下さい）。 
// なぜかというとDdeUninitialize関数を実行するとこのアプリケーションでは 再度サーバーに接続できなくなってしまうからです。

HDDEDATA CALLBACK DdeMyProc(UINT uType, UINT uFmt, HCONV hconv,
                HSZ hszTpc1, HSZ hszTpc2, HDDEDATA hData, DWORD dwData1, DWORD dwData2)
{

    switch (uType) {
        case XTYP_ADVDATA:
            // 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;
            DdeGetData(hData, (LPBYTE)szBuffer, sizeof(szBuffer), 0);
            InvalidateRect(hParent, NULL, TRUE);
            return (HDDEDATA)TRUE;
    }
    return (HDDEDATA)FALSE;
}
// DDEコールバック関数です。サーバーがデーターを送ってきたら XTYP_ADVDATAが来るので、ここでやりたい処理をします。 
// ここでは、hDataをszBufferにコピーして、クライアント領域を再描画させています。
// サーバーとクライアントを起動して、いろいろ実験してみて下さい。 
// 特にサーバーを終了してからクライアントを終了させたり，その逆をやってみて 失敗する関数を確認してみて下さい。
