Skip to content

Instantly share code, notes, and snippets.

@seraphy
Created November 12, 2014 09:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seraphy/28917741f60bf4b1496b to your computer and use it in GitHub Desktop.
Save seraphy/28917741f60bf4b1496b to your computer and use it in GitHub Desktop.
DDEサーバーの実装例(VC6コンパイル可能バージョン)、メイン画面は、スクロールバーつきログ表示機能あり。
// DDEServerExampleVC6.cpp
//
#include "stdafx.h"
// stdafx.h内に、#pragma warning(disable:4786) をいれてmap展開の長さ警告を抑制する
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include <ddeml.h>
#include <memory>
#include <vector>
#include <list>
#include <map>
/**
* ウインドクラス名
*/
static TCHAR szWindowClass[] = _T("DDESRVWndClass");
/**
* サービス名
*/
static LPCTSTR SERVICE_NAME = _T("DDESRV");
/**
* アプリケーションタイトル
*/
static LPCTSTR TITLE_NAME = _T("DDESRV Sample Application");
/**
* インスタンスハンドル
*/
HINSTANCE hInst;
/**
* DDEMLのハンドル
*/
DWORD m_idInst;
/**
* DDEのサービス名
*/
HSZ m_hszServiceName;
/**
* アドバイズループ中のトピックとアイテムを示す
*/
struct Item
{
HSZ hszTopic;
HSZ hszItem;
};
/**
* アドバイズループ中のトピックとアイテムのリストを会話ごとに保持するマップ
*/
std::map<HCONV, std::list<Item> > itemsMap;
/**
* ログメッセージデータ
*/
struct LogData
{
/**
* 連番
*/
int number;
/**
* 時刻
*/
SYSTEMTIME systime;
/**
* メッセージ
*/
std::vector<TCHAR> message;
};
/**
* ログの通し番号
*/
int logSerialNo = 0;
/**
* ログの上限
*/
const int maxLogs = 1000;
/**
* ログを保持するリスト.
* 上限を超えると古いものから削除されてゆく。
*/
std::list<LogData> logs;
/**
* ログを登録する
*/
void AppendLog(std::vector<TCHAR> message)
{
LogData logData;
logData.number = ++logSerialNo;
GetLocalTime(&logData.systime);
logData.message = message;
logs.push_back(logData);
while (logs.size() > maxLogs) {
logs.pop_front();
}
}
/**
* ログを登録する(書式付)
*/
void AppendLog(LPCTSTR message, ...)
{
va_list args;
va_start(args, message);
int len = lstrlen(message);
std::vector<TCHAR> buf(len + 512);
wvsprintf(&buf[0], message, args);
va_end(args);
AppendLog(buf);
}
/**
* DDEの文字列ハンドルから文字列を取得する.
*/
std::vector<TCHAR> handleToString(HSZ handle)
{
std::vector<TCHAR> buf(1);
DWORD reqLen = DdeQueryString(m_idInst, handle, NULL, 0, CP_WINNEUTRAL);
if (reqLen > 0) {
buf.resize(reqLen + 1, 0);
LPTSTR pBuf = &buf[0];
DdeQueryString(m_idInst, handle, pBuf, reqLen + 1, CP_WINNEUTRAL);
}
return buf;
}
/**
* DDEのデータハンドルからバイト列を取得する.
*/
std::vector<BYTE> dataHandleToBytes(HDDEDATA hdata)
{
//ハンドルからデータのアドレスを得る
DWORD reqLen = DdeGetData(hdata, NULL, 0, 0);
std::vector<BYTE> buf(reqLen);
DdeGetData(hdata, (LPBYTE) &buf[0], reqLen, 0);
return buf;
}
/**
* 文字列からバイトデータを作成する.
* フォーマットがCF_TEXTの場合はMBCS、CF_UNICODETEXTの場合はUNICODEでバイト列を作成する.
*/
HDDEDATA stringToData(LPCTSTR uniBuf, HSZ hszItem, UINT uFmt)
{
HDDEDATA data;
if (uFmt == CF_TEXT) {
#ifdef _UNICODE
int len = lstrlen(uniBuf);
int bufsiz = len * 2 + 1;
std::vector<CHAR> buf(bufsiz);
WideCharToMultiByte(CP_ACP, 0, uniBuf, len, &buf[0], bufsiz, NULL, NULL);
LPCSTR p = (LPCSTR) &buf[0];
#else
LPCSTR p = (LPCSTR) uniBuf;
#endif
data = DdeCreateDataHandle(m_idInst,
(LPBYTE)p, lstrlenA(p) + 1, 0, hszItem, CF_TEXT, 0);
} else {
#ifdef _UNICODE
LPCWSTR p = (LPCWSTR) uniBuf;
#else
int len = lstrlen(uniBuf);
std::vector<WCHAR> buf(len + 1);
MultiByteToWideChar(CP_ACP, 0, uniBuf, len, &buf[0], len + 1);
LPCWSTR p = (LPCWSTR) &buf[0];
#endif
data = DdeCreateDataHandle(m_idInst,
(LPBYTE)p, (lstrlenW(p) + 1) * sizeof(WCHAR), 0, hszItem, CF_UNICODETEXT, 0);
}
return data;
}
/**
* 応答用テキストを作成する.
* 単一文字列と、タブ区切り改行による複数行文字列の、いずれかをメニューより切り替え可能とする.
* (ExcelのDDERequestは、タブ区切りで列、改行(CR)で行と認識する配列として戻り値を認識する.)
* (単一の値の場合でも、一要素の一次元配列となる.)
*/
std::vector<TCHAR> GetResponseText(LPCTSTR topic, LPCTSTR item)
{
int len1 = lstrlen(topic);
int len2 = lstrlen(item);
int total = 64 + len1 + len2;
std::vector<TCHAR> buf(total);
wsprintf(&buf[0], _T("%ld:%s#%s"), GetTickCount(), topic, item);
return buf;
}
/**
* DDEMLのコールバック
*/
HDDEDATA CALLBACK DdeCallback(
UINT uType,
UINT uFmt,
HCONV hconv,
HSZ hszTopic,
HSZ hszItem,
HDDEDATA hdata,
DWORD dwData1,
DWORD dwData2)
{
// uTypeにメッセージの種類が入っている
switch(uType)
{
/**
* DDEサービスが登録された場合にコールバックされる
*/
case XTYP_REGISTER:
{
std::vector<TCHAR> serviceName = handleToString(hszTopic);
std::vector<TCHAR> instanceName = handleToString(hszItem);
AppendLog(_T("XTYP_REGISTER serviceName=%s instance-serviceName=%s"),
&serviceName[0], &instanceName[0]);
return (HDDEDATA)TRUE;
}
/**
* DDEサービスが登録解除された場合にコールバックされる
*/
case XTYP_UNREGISTER:
{
std::vector<TCHAR> serviceName = handleToString(hszTopic);
std::vector<TCHAR> instanceName = handleToString(hszItem);
AppendLog(_T("XTYP_UNREGISTER serviceName=%s instance-serviceName=%s"),
&serviceName[0], &instanceName[0]);
return (HDDEDATA)TRUE;
}
/**
* クライアントと接続された場合にコールバックされる
*/
case XTYP_CONNECT:
{
std::vector<TCHAR> topicName = handleToString(hszTopic);
AppendLog(_T("XTYP_CONNECT topicName=%s"), &topicName[0]);
// トピック名をチェックし接続可能であればTRUE、そうでなければFALSEを返す.
// この時点では接続していないため、hConvは設定されていない
return (HDDEDATA)TRUE;
}
/**
* 接続確認の通知
*/
case XTYP_CONNECT_CONFIRM:
{
AppendLog(_T("XTYP_CONNECT_CONFIRM conv=%lx"), hconv);
return (HDDEDATA)TRUE;
}
/**
* クライアントと接続解除された場合にコールバックされる
*/
case XTYP_DISCONNECT:
{
AppendLog(_T("XTYP_DISCONNECT conv=%lx"), hconv);
// 同一conv内のアドバイズをすべてクリアする
itemsMap.erase(hconv);
return (HDDEDATA)TRUE;
}
/**
* 直接、指定されたトピックとアイテムのデータを取得する.
*/
case XTYP_REQUEST:
{
if (uFmt != CF_TEXT && uFmt != CF_UNICODETEXT) {
AppendLog(_T("XTYP_REQUEST reject uFmt=%d"), uFmt);
return (HDDEDATA)0;
}
// hsz2には項目名が入っている。この項目名から要求されているデータを識別する
std::vector<TCHAR> topicName = handleToString(hszTopic);
std::vector<TCHAR> itemName = handleToString(hszItem);
// 応答文字列の作成
std::vector<TCHAR> uniBuf = GetResponseText(&topicName[0], &itemName[0]);
AppendLog(_T("XTYP_REQUEST conv=%lx topicName=%s itemName=%s uFmt=%ld result=%s"),
hconv, &topicName[0], &itemName[0], uFmt, &uniBuf[0]);
// バイトデータとして返却する
return stringToData(&uniBuf[0], hszItem, uFmt);
}
case XTYP_ADVSTART:
{
if (uFmt != CF_TEXT && uFmt != CF_UNICODETEXT) {
// アドバイズループを確立できるフォーマットでなければリジェクトする必要がある。
// (適合するフォーマットがでるまで、クライアント側より繰り返し呼び出される。)
AppendLog(_T("XTYP_ADVSTART conv=%lx reject uFmt=%d"), hconv, uFmt);
return (HDDEDATA)0;
}
// アドバイズループの開始
std::vector<TCHAR> topicName = handleToString(hszTopic);
std::vector<TCHAR> itemName = handleToString(hszItem);
AppendLog(_T("XTYP_ADVSTART conv=%lx topicName=%s itemName=%s uFmt=%ld"),
hconv, &topicName[0], &itemName[0], uFmt);
if (lstrcmp(&itemName[0], _T("StdDocumentName")) == 0) {
return (HDDEDATA) FALSE;
}
// アドバイズ対象の記録
std::map<HCONV, std::list<Item> >::iterator ite = itemsMap.find(hconv);
if (ite == itemsMap.end()) {
ite = itemsMap.insert(std::map<HCONV, std::list<Item> >::value_type(hconv, std::list<Item>())).first;
}
std::list<Item> &items = ite->second;
Item item = {hszTopic, hszItem};
items.push_back(item);
return (HDDEDATA) TRUE;
}
case XTYP_ADVSTOP:
{
// アドバイズループの終了
std::vector<TCHAR> topicName = handleToString(hszTopic);
std::vector<TCHAR> itemName = handleToString(hszItem);
AppendLog(_T("XTYP_ADVSTOP conv=%lx topicName=%s itemName=%s"),
hconv, &topicName[0], &itemName[0]);
// 登録済みアイテムから除去する
std::map<HCONV, std::list<Item> >::iterator ite = itemsMap.find(hconv);
if (ite != itemsMap.end()) {
std::list<Item> &items = ite->second;
for (std::list<Item>::iterator ite = items.begin();
ite != items.end(); ++ite)
{
const Item& item = *ite;
if (item.hszItem == hszItem && item.hszTopic == hszTopic) {
items.erase(ite);
AppendLog(_T("Item Removed"));
break;
}
}
}
return (HDDEDATA) TRUE;
}
case XTYP_ADVREQ:
{
AppendLog(_T("XTYP_ADVREQ"));
if (uFmt != CF_TEXT && uFmt != CF_UNICODETEXT) {
AppendLog(_T("XTYP_ADVREQ conv=%lx reject uFmt=%d"), hconv, uFmt);
return (HDDEDATA)0;
}
// このメッセージはサーバーがDdePostAdvise関数を呼んだときに送られてくる。
// このメッセージを受けたときにサーバーはクライアントにデーターを送る。
std::vector<TCHAR> topicName = handleToString(hszTopic);
std::vector<TCHAR> itemName = handleToString(hszItem);
// 応答文字列の作成
std::vector<TCHAR> uniBuf = GetResponseText(&topicName[0], &itemName[0]);
AppendLog(_T("XTYP_ADVREQ conv=%lx topicName=%s itemName=%s flags=%ld result=%s"),
hconv, &topicName[0], &itemName[0], dwData1, &uniBuf[0]);
// バイトデータとして返却する
return stringToData(&uniBuf[0], hszItem, uFmt);
}
/*
* VBAのDDEExecuteなどから呼び出される.
* (引数はUnicodeでわたってくる)
*/
case XTYP_EXECUTE:
{
// コマンドの受信
// hszTopicにトピック名、hdataにコマンドが格納されている。
std::vector<TCHAR> topicName = handleToString(hszTopic);
// ハンドルからデータのアドレスを得る
std::vector<BYTE> data = dataHandleToBytes(hdata);
DdeFreeDataHandle(hdata);
AppendLog(_T("XTYP_EXECUTE: conv=%lx topicName=%s uFmt=%ld command=%s"),
hconv, &topicName[0], uFmt, &data[0]);
//return DDE_FNOTPROCESSED; // 処理しない場合
return (HDDEDATA)DDE_FACK;
}
case XTYP_POKE:
{
if (uFmt != CF_TEXT && uFmt != CF_UNICODETEXT) {
AppendLog(_T("XTYP_POKE conv=%lx reject uFmt=%d"), hconv, uFmt);
// ExcelのDDEPokeは値としてRange等のCellオブジェクトを指定しないと
// データが全く送信されない.
// Range等を指定した場合は、送信形式を繰り返し試行してくるため
// 受け入れ可能な形式に対してのみ応答すること.
return (HDDEDATA)0;
}
// send unsolicited data to the server
std::vector<TCHAR> topicName = handleToString(hszTopic);
std::vector<TCHAR> itemName = handleToString(hszItem);
// ハンドルからデータのアドレスを得る
std::vector<BYTE> data = dataHandleToBytes(hdata);
//std::vector<TCHAR> strCmd(reinterpret_cast<LPCSTR>(&data[0]));
DdeFreeDataHandle(hdata);
AppendLog(_T("POKE: conv=%lx topicName=%s itemName=%s data=%s"),
hconv, &topicName[0], &itemName[0], &data[0]);
return (HDDEDATA)DDE_FACK;
}
default:
return (HDDEDATA) FALSE;
}
}
/**
* 画面サイズとログの行数から、スクロールバーの大きさを算定、設定する
*/
void CalcScrollBar(HWND hWnd)
{
RECT rct;
GetClientRect(hWnd, &rct);
int height = rct.bottom;
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TEXTMETRIC metric = {0};
GetTextMetrics(hdc, &metric);
int numOfDisplayLines = height / metric.tmHeight;
int count = logs.size();
SCROLLINFO scr = {0};
scr.cbSize = sizeof(SCROLLINFO);
scr.fMask = SIF_PAGE | SIF_RANGE;
scr.nMin = 0;
scr.nMax = count - 1;
scr.nPage = numOfDisplayLines;
#ifdef _DEBUG
std::vector<TCHAR> buf(512);
wsprintf(&buf[0], _T("nPage=%d count=%d\n"), scr.nPage, scr.nMax);
OutputDebugString(&buf[0]);
#endif
SetScrollInfo(hWnd , SB_VERT , &scr , TRUE);
}
/**
* ウィンドウが作成されるときに呼び出される
*/
void OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
// DDEサービスの初期化
m_idInst = 0;
if (DdeInitialize(&m_idInst, DdeCallback, APPCLASS_STANDARD, 0L) != DMLERR_NO_ERROR) {
return;
}
// サービス名を文字列ハンドルにする (いらなくなったらハンドルを解放すること)
m_hszServiceName = DdeCreateStringHandle(m_idInst, SERVICE_NAME, CP_WINNEUTRAL);
// DDEサービスを登録する
HDDEDATA ret = DdeNameService(m_idInst, m_hszServiceName, 0, DNS_REGISTER);
// コールバックをすべて有効とする
DdeEnableCallback(m_idInst, 0, EC_ENABLEALL);
// スクロールバーの設定
CalcScrollBar(hWnd);
}
/**
* DDEのアドバイズループで定期的にデータ変更を通知するためのタイマー
*/
void OnTimer(HWND hWnd, WPARAM, LPARAM)
{
for (std::map<HCONV, std::list<Item> >::iterator iteMap = itemsMap.begin();
iteMap != itemsMap.end(); ++iteMap) {
std::list<Item> &items = iteMap->second;
for (std::list<Item>::iterator ite = items.begin();
ite != items.end(); ++ite)
{
const Item& item = *ite;
std::vector<TCHAR> topicName = handleToString(item.hszTopic);
std::vector<TCHAR> itemName = handleToString(item.hszItem);
AppendLog(_T("[OnTimer]--> DdePostAdvise topic=%s item=%s"), &topicName[0], &itemName[0]);
DdePostAdvise(m_idInst, item.hszTopic, item.hszItem);
}
}
}
/**
* ログを描画する
*/
void OnPaint(HWND hWnd, WPARAM, LPARAM)
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TEXTMETRIC metric = {0};
GetTextMetrics(hdc, &metric);
SCROLLINFO scr = {0};
scr.cbSize = sizeof(SCROLLINFO);
scr.fMask = SIF_POS | SIF_TRACKPOS | SIF_RANGE | SIF_PAGE;
GetScrollInfo(hWnd, SB_VERT, &scr);
int pos = scr.nPos;
int page = scr.nPage;
int cnt = 0;
int idx = 0;
for (std::list<LogData>::const_iterator ite = logs.begin();
ite != logs.end(); ++ite) {
if (idx >= pos) {
int y = cnt * metric.tmHeight;
const LogData& logData = *ite;
LPCTSTR message = &logData.message[0];
int total = lstrlen(message) + 32;
std::vector<TCHAR> buf(total + 1);
wsprintf(&buf[0], _T("[%05d] %02d:%02d.%03d:%02d %s"),
logData.number,
logData.systime.wHour,
logData.systime.wMinute,
logData.systime.wSecond,
logData.systime.wMilliseconds,
message);
TextOut(hdc, 5, y, &buf[0], _tcslen(&buf[0]));
cnt++;
}
if (cnt > page) {
break;
}
idx++;
}
EndPaint(hWnd, &ps);
}
/**
* メインウィンドウを破棄する.
*/
void OnDestroy(HWND hWnd)
{
if (m_idInst) {
// DDEMLコールバックを停止
DdeEnableCallback(m_idInst, 0, EC_DISABLE);
// DDEサービスを登録解除
DdeNameService(m_idInst, 0, 0, DNS_UNREGISTER);
// 使用済み文字列ハンドルを解放する
DdeFreeStringHandle(m_idInst, m_hszServiceName);
// DDEMLを初期化解除する
DdeUninitialize(m_idInst);
}
PostQuitMessage(0);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
OnCreate(hWnd, wParam, lParam);
break;
case WM_TIMER:
OnTimer(hWnd, wParam, lParam);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_PAINT:
OnPaint(hWnd, wParam, lParam);
break;
case WM_VSCROLL:
{
SCROLLINFO scr = {0};
scr.cbSize = sizeof(SCROLLINFO);
scr.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
GetScrollInfo(hWnd, SB_VERT, &scr);
switch(LOWORD(wParam)) {
case SB_TOP:
scr.nPos = scr.nMin;
break;
case SB_BOTTOM:
scr.nPos = scr.nMax;
break;
case SB_LINEUP:
if (scr.nPos) {
scr.nPos--;
}
break;
case SB_LINEDOWN:
if (scr.nPos < scr.nMax) {
scr.nPos++;
}
break;
case SB_PAGEUP:
scr.nPos -= scr.nPage;
break;
case SB_PAGEDOWN:
scr.nPos += scr.nPage;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
scr.nPos = HIWORD(wParam);
break;
}
SetScrollInfo(hWnd, SB_VERT, &scr, TRUE);
}
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_SIZE:
CalcScrollBar(hWnd);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY:
OnDestroy(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
if (!RegisterClassEx(&wcex)) {
MessageBox(NULL,
_T("Call to RegisterClassEx failed!"),
TITLE_NAME,
MB_ICONERROR | MB_OK);
return 1;
}
hInst = hInstance;
HWND hWnd = CreateWindow(
szWindowClass,
TITLE_NAME,
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
250, 100,
NULL,
NULL,
hInstance,
NULL
);
if (!hWnd) {
MessageBox(NULL,
_T("Call to CreateWindow failed!"),
TITLE_NAME,
MB_ICONERROR | MB_OK);
return 1;
}
AppendLog(TITLE_NAME);
SetTimer(hWnd, 1, 5000, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
Sub test_dde()
Dim ch
ch = DDEInitiate("DDESRV", "AAA")
DDEExecute ch, "ls -lsR"
DDEPoke ch, "BBB", ActiveSheet.Range("A1")
DDETerminate ch
End Sub
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment