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

program rs485dde;
{$D Burster/Gernsbach RS485<->DDE-Schnittstelle h#s 02/03 -- 04/04}
{auch für Gantner ISK100, auch für 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 für 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 unterstützter Schnittstellen -1}
 QUEUESIZE=80;		{Schlangengröße bei OpenComm, = max. Stringlänge}
 DdeLastSystem=4;	{Anzahl der System-Items}
 DefT=20;	{Standard-TimeOut für 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 für Service (RS485)}
{ DataHsz: array[0..DdeNumData] of Hsz;	{Stringhandles für 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 für 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, Prüfsumme),  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 für Zeitdifferenzen von 1 Minute,
 ein Kandidat für 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;
{Prüfsumme über die Zeichen des Strings berechnen,
 Attach_Summe verlässt sich auf ES und SI bei Rückgabe!}
 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;
{Prüfsumme 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 für nullterminiert;
 der Empfang des Strings bricht ab bei Zeitüberschreitung,
 Puffer voll oder bei Empfang eines Zeichencodes < TermBelow.
 T2 ist die maximale Wartezeit zwischen Senden und Empfang in ms.
 Rückgabe: 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;	{kürzer 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 für 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 Hinüberretten 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 für 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);		{Müssen 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
 ; = Prüfsumme anhängen (Sende-String) bzw. prüfen (Empfangs-String)
 Empfangs-Maske ist hier das erste erwartete Zeichen des Empfangs-Strings
 (wird abgeschnitten, genauso wie die Prüfsumme)
 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 müssen'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 müssen 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 müssen 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 Prüfsumme o.ä.}
       end;
       if Make_Summe(buf,i)<>Sum then begin
	AddError(18,buf);
	goto set_ico;	{Falsche Prüfsumme}
       end;
      end;
      sp:=buf+1; Dec(i);
     end;
     sp[i]:=#0;			{für 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)
 ; = Prüfsumme anhängen (Sende-String)
 Sende-String-Kopf wird einfach um die Poke-Daten ergänzt,
 ggf. mit Prüfsumme 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 Stückelung 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 für 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 künftig deaktivieren}
    end;
    106: begin		{Rücksetzen+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: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded