I've converted a Microsoft DDE server sample application from C++ to Delphi.
My Delphi DDE server works well in Windows9x connecting to Excel 97. But it
does not work with Windows XP connecting to Excel 97. Then I programmed a
DDE client in Delphi and surprisingly, it works well with my Delphi DDE
Server. But when i tried to connect the Delphi DDE client to the C++ DDE
server, it doesn't work.
I guess Excel was written in C/C++ and that's why it doesn't work with my
Delphi DDE server. I also noticed that Excel can get the data in it's first
request with XTYP_REQUEST and then it can't get subsequent data updates with
XTYP_ADVREQ. Something wrong somewhere in XTYP_ADVREQ or DdePostAdvise?
I've included the related source code below. Please take a look...
//------------------------
----------
MS DDE Server Example
#include <windows.h>
#include <stdio.h>
#include <ddeml.h>
// Globals...
HSZ g_hszAppName;
HSZ g_hszTopicName;
HSZ g_hszItemName;
int g_count = 0;
DWORD g_idInst = 0;
// Declarations:
HDDEDATA EXPENTRY DdeCallback(UINT type, UINT fmt, HCONV hConv, HSZ hsz1,
HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2);
// WinMain()..
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow) {
// Initialize DDEML...
if(DdeInitialize(&g_idInst
, DdeCallback, APPCLASS_STANDARD, 0)) {
MessageBox(NULL, "DdeInitialize() failed", "Error", MB_SETFOREGROUND);
return -1;
}
// Create string handles...
g_hszAppName = DdeCreateStringHandle(g_id
Inst, "DdemlSvr", NULL);
g_hszTopicName = DdeCreateStringHandle(g_id
Inst, "MyTopic", NULL);
g_hszItemName = DdeCreateStringHandle(g_id
Inst, "MyItem", NULL);
if( (g_hszAppName == 0) || (g_hszTopicName == 0) || (g_hszItemName ==
0) ) {
MessageBox(NULL, "DdeCreateStringHandle() failed", "Error",
MB_SETFOREGROUND);
return -2;
}
// Register DDE server
if(!DdeNameService(g_idIns
t, g_hszAppName, NULL, DNS_REGISTER)) {
MessageBox(NULL, "DdeNameService() failed!", "Error",
MB_SETFOREGROUND);
return -3;
}
// Create a timer to simulate changing data...
SetTimer(0,0,1,0);
// Message loop:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
// On WM_TIMER, change our counter, and update clients...
if(msg.message == WM_TIMER) {
g_count++;
DdePostAdvise(g_idInst, g_hszTopicName, g_hszItemName);
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
// Our DDE Callback function...
HDDEDATA EXPENTRY DdeCallback(UINT wType, UINT fmt, HCONV hConv, HSZ hsz1,
HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2) {
switch (wType) {
// --------------------------
----------
----------
----------
--------
case XTYP_CONNECT:
// Client is trying to connect. Respond TRUE if we have what they
want...
if ((!DdeCmpStringHandles(hsz
1, g_hszTopicName)) &&
(!DdeCmpStringHandles(hsz2
, g_hszAppName)))
return (HDDEDATA)TRUE; // SERVER supports Topic|Service
else
return FALSE; // SERVER does not support Topic|Service
// --------------------------
----------
----------
----------
--------
case XTYP_ADVSTART:
// Client starting advisory loop.
// Say "ok" if we have what they are asking for...
if((!DdeCmpStringHandles(h
sz1, g_hszTopicName)) &&
(!DdeCmpStringHandles(hsz2
, g_hszItemName)))
return (HDDEDATA)TRUE; // SERVER supports Topic|Service
else
return FALSE; // SERVER does not support Topic|Service
// --------------------------
----------
----------
----------
--------
case XTYP_ADVREQ:
// Client wants our data. Since this is specific to Excel, we'll
// go ahead and assume they want XlTable-formatted data. For a
// generic DDE server, you might want to handle various formats
// specified by the passed in fmt parameter.
if(!DdeCmpStringHandles(hs
z1, g_hszTopicName) &&
!DdeCmpStringHandles(hsz2,
g_hszItemName)) {
short xltableData[100];
// tdtTable record...
xltableData[0] = 0x0010; // tdtTable
xltableData[1] = 4; // 2 short ints following
xltableData[2] = 1; // # rows
xltableData[3] = 1; // # cols
// tdtInt record...
xltableData[4] = 0x0006;
xltableData[5] = 2;
xltableData[6] = (short)g_count;
return DdeCreateDataHandle(g_idIn
st, (UCHAR*)xltableData, 2*7, 0,
g_hszItemName, fmt, 0);
}
// --------------------------
----------
----------
----------
--------
default:
return (HDDEDATA)NULL;
}
}
//------------------------
----------
MS DDE Server Example
//------------------------
----------
Delphi DDE Server
unit uMain;
{$R-,T-,H+,X+}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
WinTypes, DDEML, ExtCtrls, StdCtrls;
type
TMainForm = class(TForm)
lbCount: TLabel;
Timer1: TTimer;
lbCallType: TLabel;
lbResult: TLabel;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
g_hszAppName: hsz;
g_hszTopicName: hsz;
g_hszItemName: hsz;
g_count: Integer;
g_idInst: LongInt;
SentData: array [0..254] of Char;
function GetDdeError():String;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
function DDECallback(CallType, Fmt: UINT; Conv: HConv; hsz1, hsz2: HSZ;
Data: HDDEData; Data1, Data2: DWORD): HDDEData; stdcall; export;
implementation
{$R *.DFM}
procedure TMainForm.FormCreate(Sende
r: TObject);
var
Result: Word;
TempStr: String;
begin
g_count := 0;
g_idInst := 0;
g_hszAppName := 0;
g_hszTopicName := 0;
g_hszItemName := 0;
Result := DdeInitialize(g_idInst, DdeCallback, APPCLASS_STANDARD, 0);
if Result <> DMLERR_NO_ERROR then
begin
TempStr := 'Error: DdeInitialize - ' + GetDdeError;
ShowMessage(TempStr);
Exit;
end;
// Create string handles...
g_hszAppName := DdeCreateStringHandle(g_id
Inst, 'DDEServer', CP_WINANSI);
g_hszTopicName := DdeCreateStringHandle(g_id
Inst, 'MyTopic', CP_WINANSI);
g_hszItemName := DdeCreateStringHandle(g_id
Inst, 'MyItem', CP_WINANSI);
if (g_hszAppName = 0) or (g_hszTopicName = 0) or (g_hszItemName = 0) then
begin
TempStr := 'Error: DdeCreateStringHandle - ' + GetDdeError;
ShowMessage(TempStr);
Exit;
end;
// Register DDE server
if DdeNameService(g_idInst, g_hszAppName, 0, DNS_REGISTER) = 0 then
begin
TempStr := 'Error: DdeCreateStringHandle - ' + GetDdeError;
ShowMessage(TempStr);
Exit;
end;
end;
function TMainForm.GetDdeError():St
ring;
var
DDEError: Word;
begin
DDEError := DdeGetLastError(g_idInst);
case DDEError of
DMLERR_ADVACKTIMEOUT: Result := 'Timeout on sync advise request';
DMLERR_BUSY: Result := 'Server is busy';
DMLERR_DATAACKTIMEOUT: Result := 'Timeout on sync data request';
DMLERR_DLL_NOT_INITIALIZED
: Result := 'DDEML not initialised';
DMLERR_DLL_USAGE: Result := 'Invalid request';
DMLERR_EXECACKTIMEOUT: Result := 'Timeout on sync exec request';
DMLERR_INVALIDPARAMETER: Result := 'Invalid parameter in request';
DMLERR_LOW_MEMORY: Result := 'Server ran out of buffer memory';
DMLERR_MEMORY_ERROR: Result := 'Memory allocation error';
DMLERR_NO_CONV_ESTABLISHED
: Result := 'No conversation established';
DMLERR_NOTPROCESSED: Result := 'Request not processed by server';
DMLERR_POKEACKTIMEOUT: Result := 'Timeout on sync poke request';
DMLERR_POSTMSG_FAILED: Result := 'PostMessage failed';
DMLERR_REENTRANCY: Result := 'Sync request already in progress';
DMLERR_SERVER_DIED: Result := 'Server died';
DMLERR_SYS_ERROR: Result := 'DDEML Internal error';
DMLERR_UNADVACKTIMEOUT: Result := 'Timeout on unadvise request';
DMLERR_UNFOUND_QUEUE_ID: Result := 'Invalid transaction ID';
end;
end;
function DDECallback(CallType, Fmt: UINT; Conv: HConv; hsz1, hsz2: HSZ;
Data: HDDEData; Data1, Data2: DWORD): HDDEData; stdcall;
var
DataSize: Word;
xltableData: array [0..99] of Shortint;
begin
case CallType of
XTYP_CONNECT:
begin
MainForm.lbCallType.Captio
n := 'XTYP_CONNECT';
if (DdeCmpStringHandles(hsz1,
MainForm.g_hszTopicName) = 0) and
(DdeCmpStringHandles(hsz2,
MainForm.g_hszAppName)= 0) then
Result := HDDEDATA(TRUE) // SERVER supports Topic|Service
else
Result := 0; // SERVER does not support Topic|Service
end;
XTYP_ADVSTART:
begin
MainForm.lbCallType.Captio
n := 'XTYP_ADVSTART';
if (DdeCmpStringHandles(hsz1,
MainForm.g_hszTopicName) = 0) and
(DdeCmpStringHandles(hsz2,
MainForm.g_hszItemName)= 0) then
Result := HDDEDATA(TRUE)
else
Result := 0;
end;
XTYP_ADVREQ, XTYP_REQUEST:
begin
MainForm.lbCallType.Captio
n := 'XTYP_ADVREQ';
if (DdeCmpStringHandles(hsz1,
MainForm.g_hszTopicName) = 0) and
(DdeCmpStringHandles(hsz2,
MainForm.g_hszItemName)= 0) then
begin
{// tdtTable record...
xltableData[0] := $0010; // tdtTable
xltableData[1] := $0004; // 2 short ints following
xltableData[2] := $0001; // # rows
xltableData[3] := $0001; // # cols
// tdtInt record...
xltableData[4] := $0006;
xltableData[5] := $0002;
xltableData[6] := Shortint(MainForm.g_count)
;
DataSize := SizeOf(xltableData);
Result := DdeCreateDataHandle(MainFo
rm.g_idIns
t, @xltableData,
DataSize, 0,
MainForm.g_hszItemName, fmt, 0);}
StrCopy(MainForm.SentData,
PChar(IntToStr(MainForm.g_
count)));
Result := DdeCreateDataHandle(MainFo
rm.g_idIns
t, @MainForm.SentData,
StrLen(MainForm.SentData) + 1
, 0, MainForm.g_hszItemName, CF_TEXT, 0);
end;
end
else
Result := HDDEDATA(nil);
end;
MainForm.lbResult.Caption := IntToStr(Result);
end;
procedure TMainForm.Timer1Timer(Send
er: TObject);
begin
Inc(g_count);
lbCount.Caption := IntToStr(g_count);
DdePostAdvise(g_idInst, g_hszTopicName, g_hszItemName);
end;
end.
//------------------------
----------
Delphi DDE Server