I'm trying to make to Delphi applications communicate with each other via WM_COPYDATA. The problem I'm having though is is that the sender app is written in Delphi 7 and the receiver is written in Delphi 10.2 . I copied my Delphi 7 program's code into Delphi 10 and the communication worked perfectly. Using the exact same code in Delphi 7 however caused my string being passed to the receiver app to get corrupted. The codes I use are as follows:
One the sending side I have:
procedure TSenderApp.SendString(ToSend: string);
var
copyDataStruct : TCopyDataStruct;
receiverHandle : THandle;
res : integer;
begin
copyDataStruct.dwData := 140500; //use it to identify the message contents
copyDataStruct.cbData := (1+ Length(ToSend))* SizeOf(Char) ;
copyDataStruct.lpData := pchar(ToSend) ;
receiverHandle := FindWindow(PChar('TRecieverApp'),PChar('RecieverApp')) ;
if receiverHandle = 0 then
begin
ShowMessage('CopyData Receiver NOT found!') ;
Exit;
end;
res := SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle),
LPARAM(#copyDataStruct)) ;
end;
And on the receiving side I have:
procedure TRecieverApp.WMCopyData(var Message: TMessage);
var
p : PCopyDataStruct;
l : Integer;
s : string;
begin
p := PCopyDataStruct( Message.lParam );
if (p <> nil) then
begin
ShowMessage('New Message Recieved!');
l := p^.cbData;
SetLength( s, (l+1) );
StrLCopy( PChar(s), PChar(p^.lpData), l );
Edit1.Text := s;
end
else
Edit1.Text := 'ERROR';
end;
What am I doing wrong? Or why is the message string being corrupted when sent from the Delphi 7 written SenderApp and not from the Delphi 10 written SenderApp?
You are sending and processing data using the native encoding of Char, which is AnsiChar in Delphi 7 but is WideChar in Delphi 10.2 Tokyo (Delphi switched everything to Unicode in D2009). When Delphi 7 sends the data as ANSI, Delphi 10.2 misinterprets it as UTF-16. And vice versa. So you end up with corruption either way.
You need to convert the data to an agreed-upon character encoding before sending it, and convert it from that encoding after receiving it.
Try something more like this:
{$IF CompilerVersion >= 24} // XE3+
{$LEGACYIFEND ON}
{$IFEND}
var
MyDataID: UINT = 0;
procedure TSenderApp.SendString(const ToSend: string);
var
copyDataStruct : TCopyDataStruct;
receiverHandle : HWND;
res : LRESULT;
s : UTF8String;
begin
if MyDataID = 0 then
begin
ShowMessage('CopyData ID NOT registered!');
Exit;
end;
receiverHandle := FindWindow('TRecieverApp', 'RecieverApp');
if receiverHandle = 0 then
begin
ShowMessage('CopyData Receiver NOT found!');
Exit;
end;
{$IF CompilerVersion >= 20} // D2009+
s := UTF8String(ToSend);
{$ELSE}
s := UTF8Encode(ToSend);
{$IFEND}
copyDataStruct.dwData := MyDataID; //use it to identify the message contents
copyDataStruct.cbData := Length(s) * SizeOf(AnsiChar);
copyDataStruct.lpData := PAnsiChar(s);
res := SendMessage(receiverHandle, WM_COPYDATA, WPARAM(Handle), LPARAM(#copyDataStruct));
end;
initialization
MyDataID := RegisterWindowMessage('MyDataID');
{$IF CompilerVersion >= 24} // XE3+
{$LEGACYIFEND ON}
{$IFEND}
var
MyDataID: UINT = 0;
procedure TRecieverApp.WMCopyData(var Message: TMessage);
var
p : PCopyDataStruct;
s : UTF8String;
begin
p := PCopyDataStruct(Message.lParam);
if (p <> nil) and (MyDataID <> 0) and (p^.dwData = MyDataID) then
begin
SetString(s, PAnsiChar(p^.lpData), p^.cbData);
{$IF CompilerVersion >= 20} // D2009+
Edit1.Text := String(s);
{$ELSE}
Edit1.Text := UTF8Decode(s);
{$IFEND}
ShowMessage('New Message Received!');
end else
inherited;
end;
initialization
MyDataID := RegisterWindowMessage('MyDataID');
The difference between the two Delphi versions is string format. In Delphi 2007 and earlier, string uses 1-byte AnsiChar characters in ANSI format. In Delphi 2009 and later, string uses 2-byte WideChar characters in UTF-16 format. You need to convert the data to a common character encoding when sending it.
Related
I have got a DLL function that returns a pointer to ANSI text (PAnsiChar). I want to assign this to a (unicode-) string (This is Delphi XE2.). The following compiles but I get a warning
"W1057 Implicit String cast from 'AnsiChar' to 'string'":
function TProj4.pj_strerrno(_ErrorCode: Integer): string;
var
Err: PAnsiChar;
begin
Err := Fpj_strerrno(_ErrorCode);
Result := Err;
end;
EDIT: The text in question is an error message in English, so there are unlikely to be any conversion problems here.
I am now tempted to just explicitly typecast Err to string like this ...
Result := String(Err);
.. to get rid of the warning. Could this go wrong? Should I rather use a temporary AnsiString variable instead?
var
s: AnsiString;
[...]
s := Err;
Result := String(s);
If yes, why?
Or should I make it explicit, that the code first converts a PAnsiChar to AnsiString and then the AnsiString to a String?
Result := String(AnsiString(Err));
And of course I could make it a function:
function PAnsicharToString(_a: PAnsiChar): string;
begin
// one of the above conversion codes goes here
end;
All these options compile, but will they work? And what's the best practice here?
Bonus points: The code should ideally compile and work with Delphi 2007 and newer versions as well.
If the text is encoded in the users current locale then I'd say it is simplest to write:
var
p: PAnsiChar;
str: string;
....
str := string(p);
Otherwise if you wish to convert from a specific code page to a Unicode string then you would use UnicodeFromLocaleChars.
I think the general solution is assigning c char pointer to RawByteString, then set its codepage corresponding to c null-terminated string encoding.
var
bys :TBytes;
rbstr :RawByteString;
ustr :string;
pastr :PAnsiChar;
begin
SetLength(bys,5);
bys[0] := $ca;
bys[1] := $e9;
bys[2] := $d2;
bys[3] := $b5;
bys[4] := 0;
pastr := #bys[0]; // just simulate char* returned by c api
rbstr := pastr; // assign PAnsiChar to RawByteString
// assume text encoded as codepage 936
// Note here: set 3rd param to false!
SetCodePage(rbstr,936,false);
ustr := string(rbstr);
ShowMessage(ustr);
end;
And the other cross-platform solution is (vcl,fmx,fmx with mobile platform)
function CString2TBytes(ptr :{$IFDEF NEXTGEN} MarshaledAString {$ELSE} PAnsiChar {$ENDIF}) :TBytes;
var
pby :PByte;
len :Integer;
begin
pby := PByte(ptr);
while pby^<>0 do Inc(pby);
len := pby - ptr;
SetLength(Result,len);
if len>0 then Move(ptr^,Result[0],len);
end;
procedure TForm5.Button1Click(Sender: TObject);
var
bys, cbys: TBytes;
ustr: string;
// PAnsiChar is undefined in mobile platform
// remap param foo(outSting:PAnsiString) => foo(outString:MarshaledAString)
ptr: {$IFDEF NEXTGEN} MarshaledAString {$ELSE} PAnsiChar {$ENDIF}; //
encoding : TEncoding;
begin
SetLength(bys, 5);
bys[0] := $CA;
bys[1] := $E9;
bys[2] := $D2;
bys[3] := $B5;
bys[4] := 0;
ptr := #bys[0]; // just simulate char* returned by c api
cbys := CString2TBytes(ptr);
// assume text encoded as codepage 936
encoding := TEncoding.GetEncoding(936);
try
ustr := encoding.GetString(cbys);
ShowMessage(ustr);
finally
encoding.Free;
end;
end;
I am compiling my application in Delphi XE2.It was developed in delphi 7.. My code is as follows:
type
Name = array[0..100] of PChar;
PName = ^Name;
var
HEnt: pHostEnt;
HName: PName;
WSAData: TWSAData;
i: Integer;
begin
Result := False;
if WSAStartup($0101, WSAData) <> 0 then begin
WSAErr := 'Winsock is not responding."';
Exit;
end;
IPaddr := '';
New(HName);
if GetHostName(HName^, SizeOf(Name)) = 0 then <-----ERROR
begin
HostName := StrPas(HName^);
HEnt := GetHostByName(HName^);
"
"
so on...
end;
When i try to compile the code ,i get the following error:
When i try this code in another application it works fine in Delphi 7.
How do i convert from character pointer to PAnsiChar type to make it run on Delphi XE2??.
My Delphi knowledge might be a little rusty, but as far as I remember:
PChar is (kind of like, not exactly) a pointer to a string in itself, so this type is actually an array of 101 PChars (strings):
Name = array[0..100] of PChar;
I think you should change it to array [0..100] of Char, or why not declare HName as a PAnsiChar right from the start?
This is not the correct way to use gethostname(). Use this instead:
var
HName: array[0..100] of AnsiChar;
HEnt: pHostEnt;
WSAData: TWSAData;
i: Integer;
begin
Result := False;
if WSAStartup($0101, WSAData) <> 0 then begin
WSAErr := 'Winsock is not responding."';
Exit;
end;
IPaddr := '';
if gethostname(HName, SizeOf(Name)) = 0 then
begin
HostName := StrPas(HName);
HEnt := gethostbyname(HName);
...
end;
...
end;
yes...i got it:)
I declared HName as HName:PAnsiChar; and
if GetHostName(PAnsiChar(HName^), SizeOf(Name)) = 0
HostName := StrPas(PAnsiChar(HName^));
HEnt := GetHostByName(PAnsiChar(HName^));
i need to get paper status information from a printer. I have a list of esc/pos commands.
I'm trying to send these comands with escape function
http://msdn.microsoft.com/en-us/library/windows/desktop/dd162701%28v=vs.85%29.aspx
This is my code
type
TPrnBuffRec = record
bufflength: Word;
Buff_1: array[0..255] of Char;
end;
procedure TFTestStampa.SpeedButton2Click(Sender: TObject);
var
Buff: TPrnBuffRec;
BuffOut: TPrnBuffRec;
TestInt: Integer;
cmd : string;
begin
printer.BeginDoc;
try
TestInt := PassThrough;
if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TESTINT),
#testint, nil) > 0 then
begin
cmd := chr(10) + chr(04) + '4';
StrPCopy(Buff.Buff_1, cmd);
Buff.bufflength := StrLen(Buff.Buff_1);
Escape(Printer.Canvas.Handle, Passthrough, 0, #buff,
#buffOut);
ShowMessage( conver(strPas(buffOut.Buff_1)) );
end
finally
printer.EndDoc;
end;
function TFTestStampa.Conver(s: string): String;
var
i: Byte;
t : String;
begin
t := '';
for i := 1 to Length(s) do
t := t + IntToHex(Ord(s[i]), 2) + ' ';
Result := t;
end;
Problem is with different cmds I obtain always the same string ....
Can you give me an example of escape function with last parameter not nill ?
Alternatives to obtain paper status ?
I suppose you are using Delphi 2009 above and you used this source for your example, so your problem might be caused by Unicode parameters. In Delphi since version 2009, string type is defined as UnicodeString whilst in Delphi 2009 below as AnsiString, the same stands also for Char which is WideChar in Delphi 2009 up and AnsiChar below.
If so, then I think you have a problem at least with your buffer data length, because Char = WideChar takes 2 bytes and you were using StrLen function which returns the number of chars what cannot correspond to the data size of number of chars * 2 bytes.
I hope this will fix your problem, but I can't verify it, because I don't have your printer :)
type
TPrinterData = record
DataLength: Word;
Data: array [0..255] of AnsiChar; // let's use 1 byte long AnsiChar
end;
function Convert(const S: AnsiString): string;
var
I: Integer; // 32-bit integer is more efficient than 8-bit byte type
T: string; // here we keep the native string data type
begin
T := '';
for I := 1 to Length(S) do
T := T + IntToHex(Ord(S[I]), 2) + ' ';
Result := T;
end;
procedure TFTestStampa.SpeedButton2Click(Sender: TObject);
var
TestInt: Integer;
Command: AnsiString;
BufferIn: TPrinterData;
BufferOut: TPrinterData;
begin
Printer.BeginDoc;
try
TestInt := PASSTHROUGH;
if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TestInt), #TestInt, nil) > 0 then
begin
Command := Chr(10) + Chr(04) + '4';
StrPCopy(BufferIn.Data, Command);
BufferIn.DataLength := StrLen(Command);
FillChar(BufferOut.Data, Length(BufferOut.Data), #0);
BufferOut.DataLength := 0;
Escape(Printer.Canvas.Handle, PASSTHROUGH, 0, #BufferIn, #BufferOut);
ShowMessage(Convert(StrPas(BufferOut.Data)));
end
finally
Printer.EndDoc;
end;
end;
I'm trying to send a string between two Delphi forms using code adapted from here: http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm.
The string that is displayed by the receiver is partially garbage. I suspect this is because of Unicode I issues when Delphi 2010 is communicating with the Windows API.
I want to be able to handle Unicode if possible.
I have been unable to figure out where in the code below a cast is wrong. Any help?
Sending form:
procedure TForm1.gridDetailsDblClick(Sender: TObject);
var
StringToSend : String;
CopyDataStruct : TCopyDataStruct;
begin
StringToSend := StringGrid1.Cells[0, StringGrid1.Row];
CopyDataStruct.dwData := 0;
CopyDataStruct.cbData := 1 + Length(StringToSend) ;
CopyDataStruct.lpData := PChar(StringToSend) ;
SendDataToAppearanceForm(copyDataStruct) ;
end;
procedure TForm1.SendDataToAppearanceForm(const CopyDataStruct: TCopyDataStruct) ;
var
ReceiverHandle : THandle;
begin
ReceiverHandle := FindWindow(PChar('TForm2'), nil);
if (ReceiverHandle <> 0) then
SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle), Integer(#CopyDataStruct)) ;
end;
Receiving form: (Which results in the edit box containing a part of the string, but then garbage.)
procedure TForm2.WMCopyData(var Msg: TWMCopyData);
var
S: String;
begin
edText.Text := PChar(Msg.CopyDataStruct.lpData);
end; { WMCopyData }
Your problem is that you are setting cbData incorrectly. This is the number of bytes and not the number of characters.
The +1 is needed since your receiver is interpreting it as a null-terminated string. Therefore your code should read:
(1 + Length(StringToSend))*SizeOf(Char)
Alternatively you could, at the receiving end, make use of SetString() and cbdata to avoid the need for the +1.
I just tried
procedure TForm1.Button1Click(Sender: TObject); // Project1.exe
var
CDS: TCopyDataStruct;
begin
CDS.dwData := 0;
CDS.cbData := (length(Edit1.Text) + 1) * sizeof(char);
CDS.lpData := PChar(Edit1.Text);
SendMessage(FindWindow(nil, 'RecForm'),
WM_COPYDATA, Integer(Handle), Integer(#CDS));
end;
procedure TForm1.WndProc(var Message: TMessage); // Project2.exe
begin
inherited;
case Message.Msg of
WM_COPYDATA:
begin
Edit1.Text := PChar(TWMCopyData(Message).CopyDataStruct.lpData);
Message.Result := Integer(True);
end;
end;
end;
to copy and it works. The difference between this code and yours is that, since one Unicode character is two bytes long, the cbData member needs to be the number of characters in the string times two, that is, times sizeof(char). In addition, you need to add a whole character so that the null terminator is sent along with the string! Otherwise the receiver will not know when the string ends!
Ok, here is how i do it:
procedure TMainWindow.btnRawPrintClick(Sender: TObject);
begin
BeginPrint;
SendStr(#27#69);
SendStr('MyData');
SendStr(#10);
EndPrint;
end;
procedure TMainWindow.SendStr(Text: String);
var
i: Integer;
data : Array of Char;
begin
for i := 1 to Length(Text) do
begin
SetLength(data,i);
data[Pred(i)] := Text[i];
end;
if (PrintRawData(printHandle,
data,
Length(data)) < 0) then begin
ShowMessage('PrintRawData Failed');
EndRawPrintPage(printHandle);
EndRawPrintJob(printHandle);
exit;
end;
end;
procedure TMainWindow.BeginPrint;
begin
printHandle := StartRawPrintJob('EPSON TM-T70 Receipt','ESDPRT001','Test Document');
if printHandle < 0 then
begin
ShowMessage('StartRawPrintJob Failed!');
exit;
end;
if (StartRawPrintPage(printHandle) < 0) then begin
ShowMessage('StartRawPrintPage Failed!');
EndRawPrintJob(printHandle);
exit;
end;
end;
procedure TMainWindow.EndPrint;
begin
if (EndRawPrintPage(printHandle) < 0) then begin
ShowMessage('EndRawPrintPage Failed');
EndRawPrintJob(printHandle);
exit;
end;
if (EndRawPrintJob(printHandle) < 0) then begin
ShowMessage('EndRawPrintJob Failed');
exit;
end;
end;
Also i changed a little code that i took from here:
function PrintRawData(hPrn : THandle;
Buffer : pointer;
NumBytes : SpoolInt) : integer;
{$IFDEF WIN32}
var
BytesWritten : DWORD;
{$ENDIF}
begin
NumBytes := NumBytes * 2; //<-- I added this line
...
However, something is wrong as some commands (escape sequences) don't work as expected!
You're using the wrong function. Use Escape, passing the PASSTHROUGH flag as the second parameter. This sends the raw, unprocessed escape codes to the printer directly.
Joe Hecht (formerly of Borland) has posted a unit several times that makes this easier. I found unit PrtRaw here.
Your current code is sending data to the printer in the wrong format due to changes between Ansi and Unicode characters. The printer you're using is evidently able to tolerate some amount of error, which is why some of your commands worked, but there's a limit.
In your version of Delphi, Char is equivalent to WideChar, so change your Char code to use AnsiChar instead, so you can send one-byte characters, as the printer expects. Your PrintRawData function was fine before. Your change is wrong. The printer does not expect to receive two-byte Unicode characters, but that's what your change amounts to.
After restoring the original PrintRawData code, change your SendStr function to this:
procedure TMainWindow.SendStr(const Text: string);
var
data: AnsiString;
begin
data := Text;
if (PrintRawData(printHandle,
PAnsiChar(data),
Length(data)) < 0) then begin
ShowMessage('PrintRawData Failed');
EndRawPrintPage(printHandle);
EndRawPrintJob(printHandle);
end;
end;
I made the following changes to the code:
Replace the Char array with an AnsiString.
Instead of growing the data array one character at a time with a loop, do the Unicode-to-Ansi conversion with a single assignment statement and let the RTL take care of the conversion.
Type-cast the data string to PAnsiChar for passing to PrintRawData.
Pass string parameters as const unless you need to modify their contents.
No need for an explicit exit statement when the function is already finished.
Procedure StrLstYazdir(pYazilacakListe: TStringList; pYazici: String);
var
hPrn: THandle;
yazilacakVeri: AnsiString;
intA: Integer;
begin
hPrn := StartRawPrintJob(PChar(pYazici), '', 'Varakim');
if (Integer(hPrn) < 0) then
Begin
ShowMessage('StartRawPrintJob Hatalı');
Exit;
End;
if (StartRawPrintPage(hPrn) < 0) then
Begin
ShowMessage('StartRawPrintPage Hatalı');
EndRawPrintJob(hPrn);
Exit;
end;
For intA := 0 to pYazilacakListe.Count - 1 do
Begin
yazilacakVeri := pYazilacakListe[intA] + #13 + #10;
if (PrintRawData(hPrn, PAnsiChar(yazilacakVeri), Length(yazilacakVeri)) < 0)
then
begin
ShowMessage('PrintRawData Hatalı');
EndRawPrintPage(hPrn);
EndRawPrintJob(hPrn);
Exit;
End;
End;
if (EndRawPrintPage(hPrn) < 0) then
begin
ShowMessage('EndRawPrintPage Hatalı');
EndRawPrintJob(hPrn);
Exit;
End;
if (EndRawPrintJob(hPrn) < 0) then
begin
ShowMessage('EndRawPrintJob Hatalı');
Exit;
End;
End;
Usage:
StrLstYazdir(Memo1.Lines ,'Lexmark Forms Printer 2491')