Source file: /~heha/messtech/rs485dde.zip/SRC/RS485DDE.PAS

program rs485dde;
{$D Burster/Gernsbach RS485<->DDE-Schnittstelle h#s 02/03 -- 04/04}
{auch fr Gantner ISK100, auch fr Windows NT (wegen Zeitmessung)?}
{Ein beliebiger Kommandozeilenschalter deaktiviert den direkten Portzugriff
 unter Windows 3.x/9x/Me und l„sst RS485DDE genauso wie unter NT arbeiten
* šberarbeitung 04/04 fr detaillierte Fehler-Ereignis-Liste; Wegfall
  des About-Fensters, neue Programmstruktur mit permanentem Dialog
SYMBOL LEUCHTET BEI FEHLER ZU KURZ, ein T3 (maximale "Bedenkzeit") muss rein}

{$F-,A+,G+,K+,W-}
{$I-,Q-,R-,S-}
{$V+,B-,X+,T+,P+}
{$M $3000,$1000}
{$N-}
{$R rs485dde}

uses WinProcs,WinTypes,Win31,Ddeml,ToolHelp, WUtils,{Parser,}Tray16;

const
 AppName='RS485DDE';		{Fensterklassenname}
 HelpFileName='RS485DDE.HLP';
 FLASH=200;			{ms Aufblitzen bei Transaktion}

{************* Hauptprogramm *************************}
var
 DdeHelpString: array[0..1024] of Char;		{zum Zusammensetzen}
const
 MAXCOM=3;		{Anzahl untersttzter Schnittstellen -1}
 QUEUESIZE=80;		{Schlangengr”áe bei OpenComm, = max. Stringl„nge}
 DdeLastSystem=4;	{Anzahl der System-Items}
 DefT=20;	{Standard-TimeOut fr T1 und T2 [ms], COM-Port ist blockiert}
 WM_IconChange=WM_User+101;

 DdeSystemS: array[0..DdeLastSystem] of PChar=(
   'System','Topics','Formats','Help','SysItems');
	{System-Item-Stringkonstanten}

 DdeStrings:array[0..DdeLastSystem] of PChar=(nil,
  '<COMx parameters timeouts>','CF_TEXT',
  DdeHelpString,
  'Topics Formats Help SysItems');
	{Stringkonstanten als Antwort auf Anfragen auf System-Items}
 DdeService='RS485';

var
 Inst: LongInt;				{Instanz-Variable ("Handle"), GLOBAL}
 ServiceHsz: Hsz;			{Stringhandle fr Service (RS485)}
{ DataHsz: array[0..DdeNumData] of Hsz;	{Stringhandles fr normale Items}
 SysHsz: array[0..DdeLastSystem]of Hsz;	{System-Stringhandles}
 SysConnections: Integer;	{ist mit Null initialisiert}
 DdeConnections: array[0..MAXCOM] of Integer;
	{Anzahl aktiver Verbindungen pro COM-Port, Index=Cid von OpenComm}
 Talking: array[0..MAXCOM] of Byte;	{TRUE solange Daten nicht gelesen!}
 ComTimers: array[0..MAXCOM] of Word;
{Zeitmarken der letzten Kommunikation, wichtig zur Einhaltung von Pausen}
{Rechenzeit-Hergabe und Reentranz-Aufl”sung ist eingebaut (via CBR_Block)}
 PortAccess: Boolean;			{=false}

{$IFDEF HaveWindow}
var
 MainWnd: HWnd;
 Icons: array[0..3] of HIcon;	{4 Icons fr ruhend, verbunden, Fehler, OK}
 CurIcon: Integer;
 Statistik: array[0..3] of Word;	{Zugriffsz„hler, Indizes:
	0=DDE-Verbindungen inklusive SYSTEM, 1=DDEPokes+DDERequests (alle),
	2=RS485-Fehler (Format, Prfsumme),  3=RS485-Okay}
 Listbox: HWnd;

{************* Unterprogramme *************************}
procedure IncStatistik(index,add:Integer);
 begin
  if add<>0 then Inc(Statistik[index],add) else Statistik[index]:=0;
  SetDlgItemInt(MainWnd,101+index,Statistik[index],false);
 end;

procedure AddError(nr: Integer; src:PChar);
 var
  s: TS255;
  i: Integer;
 begin
  i:=LoadString(Seg(HInstance),nr,s,sizeof(s));
  if src<>nil then begin
   lstrcpy(s+i,' (');
   Inc(i,2);
   Inc(i,EscapeStr(src,lstrlen(src),s+i,sizeof(s)-i-1));
   lstrcpy(s+i,')');
  end;
  i:=SendMessageP(Listbox,LB_AddString,0,@s);
  SendMessage(Listbox,LB_SetCurSel,i,0);
  if i>1000
  then SendMessage(Listbox,LB_DeleteString,0,0); {„ltesten Fehler l”schen}
 end;

procedure ChangeDdeIcon(NewIcon:Integer);
 var
  Msg:TMsg;
 begin
  if NewIcon>=2 then begin
   IncStatistik(NewIcon,1);
   SetTimer(MainWnd,102,FLASH,nil);	{Timer neu starten}
  end;
  if CurIcon=NewIcon then exit;
  CurIcon:=NewIcon;
  PeekMessage(Msg,MainWnd,WM_IconChange,WM_IconChange,PM_Remove);
  PostMessage(MainWnd,WM_IconChange,NewIcon,0);
 end;

function GetDdeConnections:Integer; {liefert nicht die SYSTEM-Verbindungen!}
 var
  Cid,i: Integer;
 begin
  i:=SysConnections;
  for Cid:=0 to MAXCOM do Inc(i,DdeConnections[Cid]);
  GetDdeConnections:=i;
 end;

procedure ChangeDdeDisplay;
 var
  Cid: Integer;
  vsrec:record
   s: PChar;
   i: Integer;
  end;
  s: TS31;
 begin
  vsrec.s:=WUtils.StdMBoxTitle;
  vsrec.i:=GetDdeConnections;
  wvsprintf(s,'%s [%d]',vsrec);
  SetWindowText(MainWnd,s);
  ChangeDdeIcon(Integer(vsrec.i<>0));
 end;
{$ELSE}
procedure AddError(nr:Integer; src:PChar); begin; end;
{$ENDIF}

procedure DdeError(Code:Integer);
 begin
  vMBox({$IFDEF HaveWindow}MainWnd{$ELSE}0{$ENDIF},102,0,Code);
 end;

function CreateStringHandle(Str:PChar):Hsz;
 var Stringhandle: Hsz;
 begin
  Stringhandle:=DdeCreateStringHandle(Inst,Str,CP_WinAnsi);
  CreateStringHandle:=StringHandle;
  if StringHandle=0 then DdeError(2);
 end;

procedure FreeStringHandle(Stringhandle:Hsz);
 begin
  if Stringhandle<>0 then
   DdeFreeStringHandle(Inst,StringHandle);
 end;

function GetSystemNumber(Hsz2: Hsz; Fmt:Word):Integer;
 var
  I: Integer;
 begin
  GetSystemNumber:=0;	{Kein Datum!}
  if Fmt<>CF_Text then exit;
  for I:=1 to DdeLastSystem do
   if DdeCmpStringHandles(Hsz2,SysHsz[I])=0 then begin
    GetSystemNumber:=I;
    exit;
   end;
 end;

function GetPrecisTick:Word;
{wie GetTickCount, jedoch wirklich ms-genau;
 WORD reicht fr Zeitdifferenzen von 1 Minute,
 ein Kandidat fr WUtils?}
 var
  ti:TTimerInfo;
 begin
  InitStruct(ti,sizeof(ti));
  TimerCount(@ti);
  GetPrecisTick:=LongRec(ti.dwmsSinceStart).lo;
 end;

procedure BeginCriticalSection; assembler;
 asm    mov     ax,1681h
	int     2Fh
 end;

procedure EndCriticalSection; assembler;
 asm    mov     ax,1682h
	int     2Fh
 end;

function Make_Summe(s:PChar; slen:Integer):Byte; assembler;
{Prfsumme ber die Zeichen des Strings berechnen,
 Attach_Summe verl„sst sich auf ES und SI bei Rckgabe!}
 asm    les	si,[s]
	mov	cx,slen
	mov	ah,0
	jcxz	@@e
	cld
@@l:	seges	lodsb
	add	ah,al		{AH = Summenz„hler}
	loop	@@l
@@e:	xchg    ah,al
 end;

procedure Attach_Summe(s:PChar; slen:Integer); assembler;
{Prfsumme in Hex (2 Stellen) an String anh„ngen}
 const
  format: array[0..4] of Char='%02X';
 asm    les	si,[s]
	push	es
	push	si
	push	[slen]
	call	Make_Summe
	mov	ah,0
	push	ax
	push	ds
	push	offset format
	push	es
	push	si
	call	wsprintf
	add	sp,5*2		{eigentlich unn”tigt, erledigt Returncode}
 end;

function GetUD(Conv:HConv):LongInt;
{holt zugeortnetes COM-Handle und Umschaltzeit T2 aus Conv heraus}
 var
  ci: TConvInfo;
 begin
  DdeQueryConvInfo(Conv,QID_Sync,@ci);
  GetUD:=ci.hUser;
 end;

function SendRecv(Cid:Integer; T1: Word; s:PChar; slen:Integer;
  T2: Word; buf:PChar; bufsize:Integer; TermBelow: Char):Integer;
{Sendet und empf„ngt String zum und vom RS485-Ger„t.
 T1 ist die Zeit, die vor dem Senden bzgl. vorheriger šbertragung
 zu warten ist.
 Der Sendestring sollte mit einem Zeichencode <20h abgeschlossen sein
 (=ASCII-Protokoll), slen=Stringl„nge, -1 fr nullterminiert;
 der Empfang des Strings bricht ab bei Zeitberschreitung,
 Puffer voll oder bei Empfang eines Zeichencodes < TermBelow.
 T2 ist die maximale Wartezeit zwischen Senden und Empfang in ms.
 Rckgabe: Anzahl empfangener Zeichen (auch bei TimeOut),
 -1 bei Sendefehler, -2 bei Reentranzfehler.
 Der gelesene String ist nicht nullterminiert!

 Der Burster/Gernsbach RS485-Umsetzer Typ 5401-Z001 schaltet die
 Datenrichtung mit Hilfe der RTS-Leitung um.
 Deshalb muss dieses Programm unmittelbar nach der Ausgabe des letzten Bytes
 RTS l”schen und nicht verz”gern, sonst gibt es Buskollisionen und verlorene
 Datenbytes.
 Wegen des dazu notwendigen Portzugriffs l„uft diese Warteschleife nicht
 unter Windows NT; stattdessen arbeitet eine berechnete Warteschleife,
 die den Umschaltzeitpunkt mit einer Zugabe von 1..2 ms sch„tzt.
 Auch die Portabfrage selbst w„re nicht sicher vor Taskumschaltungen;
 deshalb erfolgt eine Einrahmung in einen (hoffentlich funktionierenden)
 "kritischen Abschnitt".

 Der Gantner RS485-Umsetzer Typ ISK100 detektiert die Richtungsumschaltung
 anhand des Sendesignals TxD (kompliziertere Schaltung).
 Dieser Umsetzer l„uft daher problemlos auch unter Windows NT.}

 label exi;
 var
  ret,bytesread: Integer;
  bitper1000byte:Integer;
  tic: Word;
  DCB: TDCB;
  ComStat: WinTypes.TComStat absolute DCB;	{krzer als DCB}
  ls_reg: Word absolute bytesread;
 begin
  Ret:=-2;
  Inc(Talking[Cid]);			{Hoffentlich atomar!}
  if Talking[Cid]>1 then goto exi;	{Keine verschachtelten Zugriffe!}
  if slen=-1 then slen:=lstrlen(s);
  ret:=-1;
  GetCommError(Cid,ComStat);		{"auftauen" bei Fehler}
  ls_reg:=0;				{Portzugriffe sind m”glich?}
  if PortAccess then ls_reg:=Word(EscapeCommFunction(Cid,GetBaseIrq))+5;
  if ComStat.cbInQue<>0			{"Verirrte" Zeichen gekommen?}
  then ComTimers[Cid]:=GetPrecisTick;	{Wartezeit neu starten}
  FlushComm(Cid,1);			{Empfangswarteschlange leeren}
  GetCommState(Cid,DCB);		{Baudrate fr TimeOut-Bestimmung}
  bitper1000byte:=(((2+DCB.bytesize
    +Integer(DCB.parity<>0))shl 1)+DCB.stopbits)*500;
  while GetPrecisTick-ComTimers[Cid]<T1 do ShortYield;	{T1 warten}
  EscapeCommFunction(Cid,SetRTS);	{RTS aktiv (nach -12V) setzen}
  BeginCriticalSection;			{Keine Taskumschaltung zulassen!}
  WriteComm(Cid,s,slen);		{Ab in die Warteschlange!}
  tic:=GetPrecisTick;			{Jetzt geht das Rennen los!}
  if ls_reg>=$100 then repeat		{Ja, hier wird Windows blockiert!}
   if GetPrecisTick-tic > MulDiv(slen,bitper1000byte,DCB.baudrate)+1
   then begin				{wenn's mal wieder l„nger dauert}
    EndCriticalSection;
    EscapeCommFunction(Cid,ClrRTS);
    FlushComm(Cid,0);			{Sendewarteschlange leeren}
    goto exi;
   end;
  until Port[ls_reg] and $60 = $60	{Hardware-Zugriff! Sender leer?}
  else repeat
  until GetPrecisTick-tic > MulDiv(slen,bitper1000byte,DCB.baudrate)+1;
  EscapeCommFunction(Cid,ClrRTS);	{RTS inaktiv (nach +12V) setzen}
  EndCriticalSection;			{Ab jetzt haben wir Zeit...}
  ret:=0;
  repeat			{T2 und den Empfang der Bytes abwarten}
   bytesread:=ReadComm(Cid,buf,bufsize);
   Inc(ret,bytesread);
   Inc(buf,bytesread);
   Dec(bufsize,bytesread);
   ShortYield;		{Gut gemeint, aber ein Anlass zur Verschachtelung}
   if bytesread<>0 then begin
    ComTimers[Cid]:=GetPrecisTick;	{nur wenn Zeichen kommen, akt.}
    if Ord((buf-1)^)<Ord(TermBelow) then break;	{eine Endekennung}
   end;
  until (bufsize=0)			{Puffer voll}
  or (GetPrecisTick-tic > MulDiv(slen+ret+1,bitper1000byte,DCB.baudrate)+T2);
exi:
  Dec(Talking[Cid]);
  SendRecv:=ret;
 end;

type
 PCid=^TCid;
 TCid=record	{Daten-Ensemble zur Speicherung an der DDE-Transaktion}
  id: Integer;	{Comm-ID 0..3}
  t1,T2: Byte;	{T1- und T2-TimeOut in ms}
 end;

function DdeCallback(CallType,Fmt:Word; Conv:hConv; HSz1,HSz2:HSz;
  Data:hDdeData; Data1,Data2: LongInt): hDdeData; export;
 label set_ico;
 const		{zum Hinberretten nach XTYP_Connect_Confirm}
  ConnectingC:TCid=(id:-1);	{Zeitvorgaben unn”tig}
 var
  SendSum, EmpfSum, SRNaked: Boolean;
  EmpfChar: Char;
  Sum: Byte;
  sp,sp2,sp3: PChar;
  i,ec,ico: Integer;
  C: TCid;
  dcb,dcb2: TDcb;
  S,buf: array[0..QUEUESIZE] of Char;	{1 Byte extra fr Null lassen}
 begin
  DdeCallback:=0;
  ico:=2;				{von Fehlschlag ausgehen}
  case CallType of
   XTYP_Connect: begin
    if DdeCmpStringHandles(Hsz1,SysHsz[0])=0 then begin	{1. "system"}
    end else begin
     i:=DdeQueryString(Inst,Hsz1,s,sizeof(s),CP_WinAnsi); {2. "COMx"}
     sp:=s;				{Weil Kommata bei Netzwerk-DDE}
     while i<>0 do begin		{ nicht erlaubt sind...}
      if sp^='+' then sp^:=',';		{alle Plus zu Komma wandeln}
      Inc(sp); Dec(i);
     end;
     C.t1:=DefT;
     C.t2:=DefT;
     sp:=lstrchr(s,' ');
     if sp=nil then begin		{ohne Angabe der Baudrate usw.}
      Val(s+3,C.id,ec);
      if ec<>0 then begin AddError(1,s); exit; end;
      Dec(C.id);
     end else begin
      sp2:=lstrchr(sp+1,' ');		{TimeOut-Parameter}
      if sp2<>nil then begin
       sp2^:=#0;
       sp3:=lstrchr(sp2+1,',');
       if sp3<>nil then begin
	sp3^:=#0;
	Val(sp3+1,C.t2,ec);		{getrennte Zeiten}
	if ec<>0 then begin AddError(2,sp3+1); exit; end;
       end;
       Val(sp2+1,C.t1,ec);
       if ec<>0 then begin
	AddError(2,sp2+1);		{falsche TimeOut-Zahl}
	exit;
       end;
       if sp3=nil then C.t2:=C.t1;	{gleiche Zeiten}
       if C.t2=0 then begin AddError(2,nil); exit; end;
      end;
      if BuildCommDCB(s,dcb)<>0 then begin
       AddError(3,s);
       exit;
      end;{mit Angabe der Baudrate}
      C.id:=dcb.id;
     end;
     if Word(C.id)>MAXCOM then begin AddError(1,nil); exit; end;
     if DdeConnections[C.id]<>0 then begin
      if sp<>nil then begin
       GetCommState(C.id,dcb2);
       if memcmp(PChar(@dcb),PChar(@dcb2),sizeof(dcb))<>0 then begin
	AddError(4,nil);		{Mssen gleiche Parameter sein!}
	exit;
       end;
      end;
     end else begin
      if sp<>nil then sp^:=#0;		{Parameter abhacken}
      C.id:=OpenComm(s,QUEUESIZE,QUEUESIZE);
      if Word(C.id)>MAXCOM then begin
       i:=5;
       case C.id of
	IE_Open: i:=6;
	IE_NOpen: i:=7;
	IE_Hardware: i:=8;
       end;
       AddError(i,s);
       exit;	{Bereits (woanders) ge”ffnet!}
      end;
      if (sp<>nil) and (SetCommState(dcb)<>0) then begin
       CloseComm(C.id);
       AddError(9,sp);
       exit;
      end;
     end;
     ConnectingC:=C;
    end;
    DdeCallback:=1;		{Verbindung okay!}
   end;

   XTYP_Connect_Confirm: begin
    C:=ConnectingC;
    ConnectingC.id:=-1;
    DdeSetUserHandle(Conv,QID_Sync,LongInt(C));	{nachtr„gliche Zuordnung!}
    if C.id>=0 then Inc(DdeConnections[C.id])
    else Inc(SysConnections);
{$IFDEF HaveWindow}
    ChangeDdeDisplay;
    IncStatistik(0,1);
{$ENDIF}
   end;

   XTYP_Disconnect: begin
    LongInt(C):=GetUD(Conv);		{Deskriptor holen}
    if C.id>=0 then begin
     Dec(DdeConnections[C.id]);
     if DdeConnections[C.id]=0 then CloseComm(C.id);
    end else Dec(SysConnections);
{$IFDEF HaveWindow}
    IncStatistik(0,-1);
    ChangeDdeDisplay;
{$ENDIF}
   end;

   XTYP_Request: begin
(*Aufbau Request-Item:
 Sende-String{:|;}Empfangs-Maske{|:|;}  oder  Sende-String*
 : = normales Trennzeichen
 ; = Prfsumme anh„ngen (Sende-String) bzw. prfen (Empfangs-String)
 Empfangs-Maske ist hier das erste erwartete Zeichen des Empfangs-Strings
 (wird abgeschnitten, genauso wie die Prfsumme)
 Das Request-Item wird von hinten geparst; der Sende-String darf deshalb
 ':' und ';' enthalten, auch die Empfangs-Maske darf ';' oder ':' sein
 Die šbergabe von Sende-String* erm”glicht ungefilterte (Bin„r-)šbertragung
 (z.B. Profibus, ModBus), der Stern wird abgeschnitten,
 ggf. "\r" nicht vergessen! *)
{$IFDEF HaveWindow}
    IncStatistik(1,1);
{$ENDIF}
    if Fmt<>CF_Text
    then begin AddError(10,nil); exit; end;	{Kann nicht anders!}
    LongInt(C):=GetUD(Conv);		{Deskriptor holen}
    if C.id>=0 then begin
     SendSum:=false;
     EmpfSum:=false;
     SRNaked:=false;
     i:=DdeQueryString(Inst,Hsz2,s,sizeof(s),CP_WinAnsi);
     if i<2 then begin AddError(11,s); exit; end;
     Dec(i);{auch ungefiltert mindestens 1 Zeichen}
     EmpfChar:=s[i];
     if EmpfChar='*' then SRNaked:=true
     else begin
      if i<3 then begin
       AddError(11,s);
       exit;		{mindestens 4 Zeichen mssen's hier sein}
      end;
      if EmpfChar=';' then EmpfSum:=true;
      if EmpfChar in [':',';'] then begin
       Dec(i);
       EmpfChar:=s[i];
      end;
      Dec(i);
      case s[i] of
       ';': SendSum:=true;
       ':': ;
       else AddError(12,s); exit;	{Fehler im DDE-Request}
      end;
      if SendSum then begin
       Attach_Summe(s,i);
       Inc(i,2);
      end;
      s[i]:=#13; Inc(i);
     end;
     i:=SendRecv(C.id,C.t1,s,i,C.t2,buf,sizeof(buf),
       Char(IfThenElse(SRNaked,$00,$20)));
     if i=-2 then begin DdeCallback:=CBR_Block; exit; end;
     if i<0 then begin AddError(13,nil); goto set_ico; end;
     if SRNaked then sp:=buf	{ungefiltert bergeben (auch Leerstring)}
     else begin
      if i<2 then begin
       AddError(14,buf);	{Mindestens 2 Zeichen mssen kommen!}
       goto set_ico;
      end;
      if buf[0]<>EmpfChar then begin
       AddError(15,buf);
       goto set_ico;	{Falsches Startzeichen}
      end;
      Dec(i);
      if buf[i]<>#13 then begin
       AddError(16,buf);
       goto set_ico;	{Falsches Endezeichen}
      end;
      buf[i]:=#0;
      if EmpfSum then begin
       if i<3 then begin
	AddError(14,buf);
	goto set_ico;	{Mindestens 4 Zeichen mssen kommen!}
       end;
       Dec(i,2);		{2 Zeichen hinten weniger}
       s[0]:='$';
       lstrcpy(s+1,buf+i);
       buf[i]:=#0;
       Val(s,Sum,ec);
       if ec<>0 then begin
	AddError(17,buf);
	goto set_ico;	{Falsche Zeichen in Prfsumme o.„.}
       end;
       if Make_Summe(buf,i)<>Sum then begin
	AddError(18,buf);
	goto set_ico;	{Falsche Prfsumme}
       end;
      end;
      sp:=buf+1; Dec(i);
     end;
     sp[i]:=#0;			{fr DDE terminieren}
     DdeCallback:=DdeCreateDataHandle(Inst,sp,i+1,0,hsz2,CF_Text,0);
     Inc(ico);			{Erfolg}
     goto set_ico;
    end;			{SYSTEM-Thema}
    i:=GetSystemNumber(hsz2,fmt);
    if i<=0 then begin
     AddError(19,nil);
     exit;
    end;
    sp:=DdeStrings[i];
    DdeCallback:=DdeCreateDataHandle(Inst,sp,lstrlen(sp)+1,0,hsz2,CF_Text,0);
   end;

   XTYP_Poke: begin
(*Aufbau Poke-Item:
 Sende-String-Kopf{|:|;}
 : = normales Trennzeichen (wirkungslos, auáer um : zu erm”glichen)
 ; = Prfsumme anh„ngen (Sende-String)
 Sende-String-Kopf wird einfach um die Poke-Daten erg„nzt,
 ggf. mit Prfsumme erg„nzt, und die ACK-Best„tigung vom Ger„t eingeholt.
 Bei fehlender oder NACK-Best„tigung gibt's ein DDE_fNotProcessed. *)
{$IFDEF HaveWindow}
    IncStatistik(1,1);
{$ENDIF}
    if Fmt<>CF_Text
    then begin AddError(10,nil); exit; end;	{Kann nicht anders!}
    LongInt(C):=GetUD(Conv);			{Deskriptor holen}
    if C.id<0 then begin
     AddError(20,nil);
     exit;
    end;
    i:=DdeQueryString(Inst,Hsz2,s,sizeof(s),CP_WinAnsi);
    if i=0 then begin
     AddError(11,nil);
     exit;
    end;
    Dec(i);
    SendSum:=false;
    if s[i]=';' then SendSum:=true;
    if not (s[i] in [':',';']) then Inc(i);
    if Data<>0 then begin
     lstrcpyn(s+i,DdeAccessData(Data,nil),sizeof(s)-3-i);
	{Die Daten von DdeAccessData k”nnen keine Nullen enthalten,
	 weil die DDE-L„ngenangabe in 32er Stckelung kommt (getestet)}
     DdeUnaccessData(Data);
     Inc(i,lstrlen(s+i));		{neu durchz„hlen}
    end;
    if SendSum then begin
     Attach_Summe(s,i);
     Inc(i,2);
    end;
    s[i]:=#13; Inc(i);
    i:=SendRecv(C.id,C.t1,s,i,C.t2,buf,sizeof(buf),' ');
    if i=-2 then begin DdeCallback:=CBR_Block; exit; end;	{Reentranz}
    if i<0 then begin AddError(13,nil); goto set_ico; end;
    if i=1 then case buf[0] of
     #$06: begin
      DdeCallback:=DDE_fAck;
      Inc(ico);			{Erfolg}
      goto set_ico;
     end;
     #$15: begin
      AddError(22,buf);		{NAK}
      goto set_ico;
     end;
    end else AddError(21,buf);	{irgendetwas}
set_ico:
{$IFDEF HaveWindow}
    ChangeDdeIcon(ico);		{Erfolg oder Misserfolg anzeigen}
{$ENDIF}
    DdeEnableCallback(Inst,0,EC_EnableAll);	{blockierte neu starten}
   end;

  end;
 end;

procedure DDEInit;
 var
  W: Word;
 begin
  if DdeInitialize(Inst,DdeCallBack,CBF_Skip_Registrations
    or CBF_Skip_Unregistrations
    or CBF_Fail_Executes
    or CBF_Fail_Advises,0)<>DMLErr_No_Error
  then DdeError(DMLErr_Sys_Error);
  W:=LoadString(Seg(HInstance),106,DdeHelpString,256);
  Inc(W,LoadString(Seg(HInstance),107,DdeHelpString+W,256));
  LoadString(Seg(HInstance),108,DdeHelpString+W,256);
  ServiceHsz:=CreateStringHandle(DdeService);
  for W:=0 to DdeLastSystem do SysHsz[W]:=CreateStringHandle(DdeSystemS[W]);
  if DdeNameService(Inst,ServiceHsz,0,DNS_Register)=0
  then DdeError(3);
 end;

procedure DdeDone;
 var i:Integer;
 begin
  DdeNameService(Inst,ServiceHsz,0,DNS_Unregister);
  for I:=DdeLastSystem downto 0 do
   FreeStringHandle(SysHsz[i]);
  FreeStringHandle(ServiceHsz);
  DdeUninitialize(Inst);
 end;

{$IFDEF HaveWindow}

var
 traydata: TNotifyIconData;
 MinSize: TPoint;	{AusgangsgrӇe fr Dialogfenster}
 ListPos: TPoint;

procedure ShellNotify;
 begin
  if not Shell_NotifyIcon(NIM_Modify,traydata) then begin
   LongRec(traydata.uFlags).lo:=NIF_Icon or NIF_Tip or NIF_Message;
   Shell_NotifyIcon(NIM_Add,traydata);
  end;
 end;

procedure GetMinSize;
 var
  wp:TWindowPlacement;
 begin
  wp.length:=sizeof(wp);
  GetWindowPlacement(MainWnd,@wp);
  MinSize.x:=wp.rcNormalPosition.right-wp.rcNormalPosition.left;
  MinSize.y:=wp.rcNormalPosition.bottom-wp.rcNormalPosition.top;
  GetWindowRect(Listbox,wp.rcNormalPosition);
  ListPos:=PPoint(@wp.rcNormalPosition)^;
  ScreenToClient(MainWnd,ListPos);
 end;

function MainDlgProc(Wnd:HWnd; Msg,wParam:Word; lParam:LongInt):Bool; export;
 var
  lPar: LongRec absolute lParam;
  P: TPoint;
  m,sysm: HMenu;
  s: TS31;
 begin
  MainDlgProc:=false;
  case Msg of
   WM_InitDialog: begin
    MainWnd:=Wnd;
    Listbox:=GetDlgItem(Wnd,105);
    sysm:=GetSystemMenu(Wnd,false);
    DeleteMenu(sysm,SC_Maximize,0);
    LoadString(Seg(HInstance),111,s,sizeof(s));	{Hilfe}
    InsertMenu(sysm,1,MF_ByPosition or MF_String,$1D0,s);
    if Swap(Word(GetVersion))>=3*256+95 then begin
     LoadString(Seg(HInstance),110,s,sizeof(s));	{In den Tray!}
     InsertMenu(sysm,2,MF_ByPosition or MF_String,$1E0,s);
     if CmdShow in [SW_Minimize,SW_ShowMinimized,SW_ShowMinNoActive]
     then PostMessage(Wnd,WM_SysCommand,$1E0,0);
    end;
    GetMinSize;	{Fokus NICHT setzen! Sonst wirkt ShowMinNoActive nicht}
   end;
   WM_GetMinMaxInfo: begin
    PMinMaxInfo(lParam)^.ptMinTrackSize:=MinSize;
   end;
   WM_Size: if wParam=SIZE_Minimized then begin
    if LongRec(traydata.cbSize).lo<>0 then ShowWindow(Wnd,SW_Hide);
   end else begin
    SetWindowPos(Listbox,0,0,0,
      lPar.lo-8-ListPos.x,lPar.hi-8-ListPos.y,SWP_NoZOrder or SWP_NoMove);
   end;
   WM_Timer: begin
    KillTimer(Wnd,102);
    if CurIcon<>0 then ChangeDdeIcon(1);
   end;
   WM_IconChange: begin
    lPar.lo:=Icons[wParam];
    SetClassWord(Wnd,GCW_HIcon,lPar.lo);
    InvalidateRect(Wnd,nil,true);
    if LongRec(traydata.cbSize).lo<>0 then begin
     LongRec(traydata.hIcon).lo:=lPar.lo;
     LongRec(traydata.uFlags).lo:=NIF_Icon;		{nur Icon „ndern}
     ShellNotify;
    end;
    SendDlgItemMessage(Wnd,100,STM_SETICON,lPar.lo,0);
   end;
   WM_SetText: begin
    if LongRec(traydata.cbSize).lo<>0 then begin
     lstrcpy(traydata.szTip,PChar(lParam));
     LongRec(traydata.uFlags).lo:=NIF_Tip;		{nur Text „ndern}
     ShellNotify;
    end;
   end;
   WM_SysCommand: case wParam and $FFF0 of
    $1E0: begin				{In den Tray!}
     LongRec(traydata.cbSize).lo:=sizeof(traydata);
     LongRec(traydata.hwnd).lo:=Wnd;
     LongRec(traydata.uID).lo:=110;
     LongRec(traydata.uFlags).lo:=NIF_Icon or NIF_Tip or NIF_Message;
     LongRec(traydata.uCallbackMessage).lo:=WM_User+100;
     LongRec(traydata.hIcon).lo:=GetClassWord(Wnd,GCW_HIcon);
     GetWindowText(Wnd,traydata.szTip,sizeof(traydata.szTip));
     if Shell_NotifyIcon(NIM_Add,traydata)
     and IsIconic(Wnd)
     then ShowWindow(Wnd,SW_Hide);
    end;
    $1D0: WinHelp(Wnd,HelpFileName,HELP_Index,0);
   end;
   WM_User+100: case lParam of
    WM_LButtonDown: begin
     ShowWindow(Wnd,SW_Restore);
    end;
    WM_RButtonDown: begin
     m:=LoadMenu(Seg(HInstance),MakeIntResource(100));
     sysm:=GetSubMenu(m,0);
     GetCursorPos(P);
     TrackPopupMenu(sysm,TPM_RightAlign or TPM_RightButton,
      P.x,P.y,0,Wnd,nil);
     DestroyMenu(m);
    end;
   end;
   WM_Command: case wParam of		{kommen nur vom System-Tray!}
    2: ShowWindow(Wnd,SW_Minimize);	{besser als gar nichts}
    100: begin				{normales Fenster!}
     if not IsWindowVisible(Wnd) then ShowWindow(Wnd,SW_Minimize);
     Shell_NotifyIcon(NIM_Delete,traydata);
     LongRec(traydata.cbSize).lo:=0;	{auch knftig deaktivieren}
    end;
    106: begin		{Rcksetzen+L”schen}
     SendMessage(Listbox,LB_ResetContent,0,0);
     for lPar.lo:=1 to 3 do IncStatistik(lPar.lo,0);
    end;
    900: WinHelp(Wnd,HelpFileName,HELP_Index,0);
    999: ShowWindow(Wnd,SW_Restore);
   end;
   WM_Close: begin
    if (GetDdeConnections>0)
    and (MBox1(Wnd,109{WarnClose},MB_IconQuestion or MB_YesNo or MB_Sound,
      PChar(GetDdeConnections))<>IDYes)
    then exit;
    if LongRec(traydata.cbSize).lo<>0
    then Shell_NotifyIcon(NIM_Delete,traydata);
    WinHelp(Wnd,HelpFileName,HELP_Quit,0);
    DestroyWindow(Wnd);
   end;
   WM_Destroy: PostQuitMessage(0);
  end;
 end;

const
 wc: TWndClass=(
  style: {CS_VRedraw or CS_HRedraw}CS_DblClks;
  lpfnWndProc: @DefDlgProc{@MainWndProc};
  cbClsExtra: 0;
  cbWndExtra: DlgWindowExtra;
  hInstance: Seg(HInstance);
  hIcon: 0;
  hCursor: 0;
  hbrBackground: COLOR_Background+1;
  lpszMenuName: nil;
  lpszClassName: AppName);
var
 Msg:TMsg;
 i: Integer;
 AppTitle: TS31;

begin
 if HPrevInst<>0 then begin
  SetActiveWindow(MemW[HPrevInst:Ofs(MainWnd)]);
  exit;
 end;
 for i:=0 to HIGH(Icons) do
   Icons[i]:=LoadIcon(Seg(HInstance),MakeIntResource(100+i));
 wc.hIcon:=Icons[0];
 wc.hCursor:=LoadCursor(0,IDC_Arrow);
 RegisterClass(wc);
 CreateDialog(Seg(HInstance),MakeIntResource(100),0,@MainDlgProc);
 ShowWindow(MainWnd,SW_ShowMinNoActive);
 GetWindowText(MainWnd,AppTitle,sizeof(AppTitle));
{$ELSE}
var
 Msg:TMsg;
const
 AppTitle='RS485<->DDE';
begin
 if HPrevInst<>0 then exit;
{$ENDIF}
 WUtils.StdMBoxTitle:=AppTitle;
 if  (lstrlen(CmdLine) =0)		{Portzugriff erlaubt?}
 and (GetWinFlags and WF_WinNT =0)	{Portzugriff m”glich?}
 then PortAccess:=true;
 DdeInit;
 while GetMessage(Msg,0,0,0) do begin
{$IFDEF HaveWindow}
  if IsDialogMessage(MainWnd,Msg) then continue;
{$ENDIF}
  TranslateMessage(Msg);
  DispatchMessage(Msg);
 end;
 DdeDone;
{$IFDEF HaveWindow}
 for i:=0 to HIGH(Icons) do DestroyIcon(Icons[i]);
{$ENDIF}
end.
Detected encoding: UTF-80