Using SendData results in a mangled string when received - delphi

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!

Related

How to determine the size of a buffer for a DLL call when the result comes from the DLL

Using both Delphi 10.2 Tokyo and Delphi XE2.
I have a DLL that posts XML data to a site. The DLL is built with Delphi 10 in order to use TLS 1.2, which is not available with Delphi XE2.
The call to the DLL comes from a Delphi XE2 EXE, but I don't believe that is relevant, but I am noting it nonetheless.
The call to post data to a site will often return text data. Sometimes very large amounts of text data. Greater than 150K characters.
My original DLL convention was basically not correct, as I returned the contents of the returned text data as a PChar. In my readings here and elsewhere, that's a big no-no.
That "bad" methodology worked well until I started to get very large amounts of data returned. I tested it, and it failed on anything greater than 132,365 characters.
I restructured my DLL and calling code to pass in a buffer as a PChar to fill in, but I get an error trying to fill the output value!
Secondly, since I never know how big the returned data will be, how to I specify how big a buffer to fill from my calling method?
My DLL code where I get the error:
library TestDLL;
uses
SysUtils,
Classes,
Windows,
Messages,
vcl.Dialogs,
IdSSLOpenSSL, IdHTTP, IdIOHandlerStack, IdURI,
IdCompressorZLib;
{$R *.res}
function PostAdminDataViaDll(body, method, url: PChar; OutData : PChar; OutLen : integer): integer; stdcall
var HTTPReq : TIdHTTP;
var Response: TStringStream;
var SendStream : TStringStream;
var IdSSLIOHandler : TIdSSLIOHandlerSocketOpenSSL;
var Uri : TIdURI;
var s : string;
begin
Result := -1;
try
HTTPReq := TIdHTTP.Create(nil);
IdSSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
IdSSLIOHandler.SSLOptions.Mode := sslmClient;
IdSSLIOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2, sslvTLSv1_1];
if Assigned(HTTPReq) then begin
HTTPReq.Compressor := TIdCompressorZLib.Create(HTTPReq);
HTTPReq.IOHandler := IdSSLIOHandler;
HTTPReq.ReadTimeout := 180000;//set read timeout to 3 minutes
HTTPReq.Request.ContentType := 'text/xml;charset=UTF-8';
HTTPReq.Request.Accept := 'text/xml';
HTTPReq.Request.CustomHeaders.AddValue('SOAPAction', 'http://tempuri.org/Administration/' + method);
HTTPReq.HTTPOptions := [];
end;
SendStream := TStringStream.Create(Body);
Response := TStringStream.Create(EmptyStr);
try
HTTPReq.Request.ContentLength := Length(Body);
Uri := TiDUri.Create(url);
try
HTTPReq.Request.Host := Uri.Host;
finally
Uri.Free;
end;
HTTPReq.Post(url + 'admin.asmx', SendStream,Response);
if Response.Size > 0 then begin
if assigned(OutData) then begin
s := Response.DataString;// Redundant? Probably can just use Response.DataString?
StrPLCopy(OutData, s, OutLen);// <- ACCESS VIOLATION HERE
//StrPLCopy(OutData, s, Response.Size);// <- ACCESS VIOLATION HERE
Result := 0;
end;
end
else begin
Result := -2;
end;
finally
Response.Free;
SendStream.Free;
IdSSLIOHandler.Free;
HTTPReq.Free;
end;
except
on E:Exception do begin
ShowMessage(E.Message);
Result := 1;
end;
end;
end;
exports
PostAdminDataViaDll;
begin
end.
My Calling method code:
function PostAdminData(body, method, url : string): IXMLDOMDocument;
type
TMyPost = function (body, method, url: PChar; OutData : PChar; OutLen : integer): integer; stdcall;
var Handle : THandle;
var MyPost : TMyPost;
var dataString : string;
var returnData : string;
begin
if not (FileExists(ExtractFilePath(Application.ExeName) + 'TestDLL.DLL')) then begin
Application.MessageBox(pchar('Unable to find TestDLL.DLL.'), pchar('Error posting'),MB_ICONERROR + MB_OK);
Exit;
end;
dataString := EmptyStr;
returnData := '';
Handle := LoadLibrary(PChar(ExtractFilePath(Application.ExeName) + 'TestDLL.DLL'));
if Handle <> 0 then begin
try
try
MyPost := GetProcAddress(Handle, 'PostAdminDataViaDll');
if #MyPost <> nil then begin
// NOTE 32767 is not big enough for the returned data! Help!
if MyPost(PChar(body), PChar(method), PChar(url), PChar(returnData), 32767) = 0 then begin
dataString := returnData;
end;
end;
except
end;
finally
FreeLibrary(Handle);
end;
end
else begin
Application.MessageBox(pchar('Unable to find TestDLL.DLL.'), pchar('Error posting'),MB_ICONERROR + MB_OK);
end;
if not sametext(dataString, EmptyStr) then begin
try
Result := CreateOleObject('Microsoft.XMLDOM') as IXMLDOMDocument;
Result.async := False;
Result.loadXML(dataString);
except
end;
end;
end;
I have a DLL that posts XML data to a site. The DLL is built with Delphi 10 in order to use TLS 1.2, which is not available with Delphi XE2.
Why not simply update Indy in XE2 to a newer version that supports TLS 1.2? Then you don't need the DLL at all.
My original DLL convention was basically not correct, as I returned the contents of the returned text data as a PChar. In my readings here and elsewhere, that's a big no-no.
It is not a "big no-no", especially if the response data is dynamic in nature. Returning a pointer to dynamically allocated data is perfectly fine. You would simply have to export an extra function to free the data when the caller is done using it, that's all. The "big no-no" is that this does introduce a potential memory leak, if the caller forgets to call the 2nd function. But that is what try..finally is good for.
That "bad" methodology worked well until I started to get very large amounts of data returned. I tested it, and it failed on anything greater than 132,365 characters.
That is not a lot of memory. Any failure you were getting with it was likely due to you simply misusing the memory.
I restructured my DLL and calling code to pass in a buffer as a PChar to fill in, but I get an error trying to fill the output value!
That is because you are not filling in the memory correctly.
Secondly, since I never know how big the returned data will be, how to I specify how big a buffer to fill from my calling method?
You can't, when using POST. You would have to cache the response data somewhere off to the side, and then expose ways to let the caller query that cache for its size and data afterwards.
My DLL code where I get the error:
My Calling method code:
I see a number of logic mistakes in that code.
But, the most important reason for the Access Violation error is that your EXE is simply not allocating any memory for its returnData variable.
Casting a string to a PChar never produces a nil pointer. If the input string is not empty, a pointer to the string's first Char is returned. Otherwise, a pointer to a static #0 Char is returned instead. This ensures that a string casted to PChar always results in a non-nil, null-terminated, C style character string.
Your EXE is telling the DLL that returnData can hold up to 32767 chars, but in reality it can't hold any chars at all! In the DLL, OutData is not nil, and OutLen is wrong.
Also, StrPLCopy() always null-terminates the output, but the MaxLen parameter does not include the null-terminator, so the caller must allocate room for MaxLen+1 characters. This is stated in the StrPLCopy() documentation.
With all of this said, try something more like this:
library TestDLL;
uses
SysUtils,
Classes,
Windows,
Messages,
Vcl.Dialogs,
IdIOHandlerStack, IdSSLOpenSSL, IdHTTP, IdCompressorZLib;
{$R *.res}
function PostAdminDataViaDll(body, method, url: PChar;
var OutData : PChar): integer; stdcall;
var
HTTPReq : TIdHTTP;
SendStream : TStringStream;
IdSSLIOHandler : TIdSSLIOHandlerSocketOpenSSL;
s : string;
begin
OutData := nil;
try
HTTPReq := TIdHTTP.Create(nil);
try
IdSSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTPReq);
IdSSLIOHandler.SSLOptions.Mode := sslmClient;
IdSSLIOHandler.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
HTTPReq.IOHandler := IdSSLIOHandler;
HTTPReq.Compressor := TIdCompressorZLib.Create(HTTPReq);
HTTPReq.ReadTimeout := 180000;//set read timeout to 3 minutes
HTTPReq.HTTPOptions := [];
HTTPReq.Request.ContentType := 'text/xml';
HTTPReq.Request.Charset := 'UTF-8';
HTTPReq.Request.Accept := 'text/xml';
HTTPReq.Request.CustomHeaders.AddValue('SOAPAction', 'http://tempuri.org/Administration/' + method);
SendStream := TStringStream.Create(Body, TEncoding.UTF8);
try
s := HTTPReq.Post(string(url) + 'admin.asmx', SendStream);
finally
SendStream.Free;
end;
Result := Length(s);
if Result > 0 then begin
GetMem(OutData, (Result + 1) * Sizeof(Char));
Move(PChar(s)^, OutData^, (Result + 1) * Sizeof(Char));
end;
finally
HTTPReq.Free;
end;
except
on E: Exception do begin
ShowMessage(E.Message);
Result := -1;
end;
end;
end;
function FreeDataViaDll(Data : Pointer): integer; stdcall;
begin
try
FreeMem(Data);
Result := 0;
except
on E: Exception do begin
ShowMessage(E.Message);
Result := -1;
end;
end;
end;
exports
PostAdminDataToCenPosViaDll,
FreeDataViaDll;
begin
end.
function PostAdminData(body, method, url : string): IXMLDOMDocument;
type
TMyPost = function (body, method, url: PChar; var OutData : PChar): integer; stdcall;
TMyFree = function (Data Pointer): integer; stdcall;
var
hDll : THandle;
MyPost : TMyPost;
MyFree : TMyFree;
dataString : string;
returnData : PChar;
returnLen : Integer;
begin
hDll := LoadLibrary(PChar(ExtractFilePath(Application.ExeName) + 'TestDLL.DLL'));
if hDll = 0 then begin
Application.MessageBox('Unable to load TestDLL.DLL.', 'Error posting', MB_ICONERROR or MB_OK);
Exit;
end;
try
try
MyPost := GetProcAddress(hDll, 'PostAdminDataViaDll');
MyFree := GetProcAddress(hDll, 'FreeDataViaDll');
if Assigned(MyPost) and Assigned(MyFree) then begin
returnLen := MyPost(PChar(body), PChar(method), PChar(url), returnData);
if returnLen > 0 then begin
try
SetString(dataString, returnData, returnLen);
finally
MyFree(returnData);
end;
end;
end;
finally
FreeLibrary(hDll);
end;
except
end;
if dataString <> '' then begin
try
Result := CreateOleObject('Microsoft.XMLDOM') as IXMLDOMDocument;
Result.async := False;
Result.loadXML(dataString);
except
end;
end;
end;

Replacing string assignment by PChar operation

I have a puzzling result that I'm struggling to understand.
I've been attempting to improve the speed of this routine
function TStringRecord.GetWord: String;
begin
// return the next word in Input
Result := '';
while (PC^ <> #$00) and not PC^.IsLetter do begin
inc(FPC);
end;
while (PC^ <> #$00) and PC^.IsLetter do begin
Result := Result + PC^;
inc(FPC);
end;
end;
by replacing the Result := Result + PC^ by a pointer-based operation. This
is my attempt:
function TStringRecord.GetWord2: String;
var
Len : Integer;
StartPC,
DestPC : PChar;
begin
// return the next word in Input
Result := '';
while (PC^ <> #$00) and not PC^.IsLetter do begin
inc(FPC);
end;
Len := Length(Input);
SetLength(Result, Len);
StartPC := PChar(Result);
DestPC := PChar(Result);
while (PC^ <> #$00) and PC^.IsLetter do begin
WStrPLCopy(DestPC, PC, 1);
inc(FPC);
inc(DestPC);
end;
SetLength(Result, DestPC - StartPC);
end;
According to my line profiler, WStrPLCopy(DestPC, PC, 1) takes 50 times longer
than Result := Result + PC^. As far as I can tell, this is because on entry
to WStrPLCopy there is a call to _WStrFromPWChar which seems to copy many more
characters than the one necessary. How can I avoid this, or can someone suggest
an alternative PChar-based method?
The remainder of my code is below:
TStringRecord = record
private
FPC: PChar;
FInput: String;
procedure SetInput(const Value: String);
public
function NextWord : String;
function NextWord2 : String;
property Input : String read FInput write SetInput;
property PC : PChar read FPC;
end;
procedure TStringRecord.SetInput(const Value: String);
begin
FInput := Value;
FPC := PChar(Input);
end;
This is how I would write it:
function TStringRecord.GetWord: String;
var beg: PChar;
begin
// return the next word in Input
while (FPC^ <> #0) and not FPC^.IsLetter do
inc(FPC);
beg := FPC;
while (FPC^ <> #0) and FPC^.IsLetter do
inc(FPC);
SetString(result, beg, FPC-beg);
end;
With this, code is very readable, and you have a single memory allocation, and I guess you could not write anything faster (but by inlining PC^.IsLetter, which is the only call to an external piece of code).

SendMessage(WM_COPYDATA) + Record + String

I want to send a record, that right now have only a string on it, but I will add more variables. Is the first time I work with records, so this maybe is a silly question. But, why this works:
type
TDataPipe = record
WindowTitle: String[255];
end;
var
Data: TDataPipe;
copyDataStruct : TCopyDataStruct;
begin
Data.WindowTitle:= String(PChar(HookedMessage.lParam));
copyDataStruct.dwData := 0;
copyDataStruct.cbData := SizeOf(Data);
copyDataStruct.lpData := #Data;
SendMessage(FindWindow('TForm1', nil), WM_COPYDATA, Integer(hInstance), Integer(#copyDataStruct));
end;
Receiving side:
type
TDataPipe = record
WindowTitle: String[255];
end;
procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
sampleRecord : TDataPipe;
begin
sampleRecord.WindowTitle:= TDataPipe(Msg.CopyDataStruct.lpData^).WindowTitle;
Memo1.Lines.Add(sampleRecord.WindowTitle);
end;
Why if on the record, I use:
WindowTitle: String; //removed the fixed size
and on the sending side I use:
Data.WindowTitle:= PChar(HookedMessage.lParam); //removed String()
it simply doesn't go?
I get access violations / app freeze...
The scenario is: sending side is a DLL hooked using SetWindowsHookEx, receiving side a simple exe that loaded / called SetWindowsHookEx...
A String[255] is a fixed 256-byte block of memory, where the character data is stored directly in that memory. As such, it is safe to pass as-is across process boundaries without serialization.
A String, on the other hand, is a dynamic type. It just contains a pointer to character data that is stored elsewhere in memory. As such, you can't pass a String as-is across process boundaries, all you would be passing is the pointer value, which has no meaning to the receiving process. You have to serialize String data into a flat format that can safely by passed to, and deserialized by, the receiving process. For example:
Sending side:
type
PDataPipe = ^TDataPipe;
TDataPipe = record
WindowTitleLen: Integer;
WindowTitleData: array[0..0] of Char;
//WindowTitleData: array[0..WindowTitleLen-1] of Char;
end;
var
Wnd: HWND;
s: String;
Data: PDataPipe;
DataLen: Integer;
copyDataStruct : TCopyDataStruct;
begin
Wnd := FindWindow('TForm1', nil);
if Wnd = 0 then Exit;
s := PChar(HookedMessage.lParam);
DataLen := SizeOf(Integer) + (SizeOf(Char) * Length(s));
GetMem(Data, DataLen);
try
Data.WindowTitleLen := Length(s);
StrMove(Data.WindowTitleData, PChar(s), Length(s));
copyDataStruct.dwData := ...; // see notes further below
copyDataStruct.cbData := DataLen;
copyDataStruct.lpData := Data;
SendMessage(Wnd, WM_COPYDATA, 0, LPARAM(#copyDataStruct));
finally
FreeMem(Data);
end;
end;
Receiving side:
type
PDataPipe = ^TDataPipe;
TDataPipe = record
WindowTitleLen: Integer;
WindowTitleData: array[0..0] of Char;
//WindowTitleData: array[0..WindowTitleLen-1] of Char;
end;
procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
Data: PDataPipe;
s: string;
begin
Data := PDataPipe(Msg.CopyDataStruct.lpData);
SetString(s, Data.WindowTitleData, Data.WindowTitleLen);
Memo1.Lines.Add(s);
end;
That being said, in either situation, you really should be assigning your own custom ID number to the copyDataStruct.dwData field. The VCL itself uses WM_COPYDATA internally, so you don't want to get those messages confused with yours, and vice versa. You can use RegisterWindowMessage() to create a unique ID to avoid conflicts with IDs used by other WM_COPYDATA users:
var
dwMyCopyDataID: DWORD;
...
var
...
copyDataStruct : TCopyDataStruct;
begin
...
copyDataStruct.dwData := dwMyCopyDataID;
...
end;
...
initialization
dwMyCopyDataID := RegisterWindowMessage('MyCopyDataID');
var
dwMyCopyDataID: DWORD;
...
procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
...
begin
if Msg.CopyDataStruct.dwData = dwMyCopyDataID then
begin
...
end else
inherited;
end;
...
initialization
dwMyCopyDataID := RegisterWindowMessage('MyCopyDataID');
Lastly, the WPARAM parameter of WM_COPYDATA is an HWND, not an HINSTANCE. If the sender does not have its own HWND, just pass 0. Do not pass your sender's HInstance variable.
Preparation:
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited;
StrCopy(Params.WinClassName, PChar(SingleInstClassName)); // Copies a null-terminated string. StrCopy is designed to copy up to 255 characters from the source buffer into the destination buffer. If the source buffer contains more than 255 characters, the procedure will copy only the first 255 characters.
end;
Sender:
procedure TAppData.ResurectInstance(Arg: string);
VAR
Window: HWND;
DataToSend: TCopyDataStruct;
begin
Arg:= Trim(Arg);
{ Prepare the data you want to send }
DataToSend.dwData := CopyDataID; // CopyDataID = Unique ID for my apps
DataToSend.cbData := Length(Arg) * SizeOf(Char);
DataToSend.lpData := PChar(Arg);
{ We should never use PostMessage() with the WM_COPYDATA message because the data that is passed to the receiving application is only valid during the call. Finally, be aware that the call to SendMessage will not return until the message is processed.}
Window:= WinApi.Windows.FindWindow(PWideChar(SingleInstClassName), NIL); // This is a copy of cmWindow.FindTopWindowByClass
SendMessage(Window, WM_COPYDATA, 0, LPARAM(#DataToSend));
end;
Receiver:
procedure TMainForm.WMCopyData(var Msg: TWMCopyData);
VAR
FileName: string;
begin
{ Receives filename from another instance of this program }
if (Msg.CopyDataStruct.dwData = AppData.CopyDataID) { Only react on this specific message }
AND (Msg.CopyDataStruct.cbData > 0) { Do I receive an empty string? }
then
begin
SetString(FileName, PChar(Msg.CopyDataStruct.lpData), Msg.CopyDataStruct.cbData div SizeOf(Char));
msg.Result:= 2006; { Send something back as positive answer }
AppData.Restore;
...
end
else
inherited;
end;

Correct way to send commands directly to printer!

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')

How can I remotely read binary registry data using Delphi 2010?

I am trying to remotely read a binary (REG_BINARY) registry value, but I get nothing but junk back. Any ideas what is wrong with this code? I'm using Delphi 2010:
function GetBinaryRegistryData(ARootKey: HKEY; AKey, AValue, sMachine: string; var sResult: string): boolean;
var
MyReg: TRegistry;
RegDataType: TRegDataType;
DataSize, Len: integer;
sBinData: string;
bResult: Boolean;
begin
bResult := False;
MyReg := TRegistry.Create(KEY_QUERY_VALUE);
try
MyReg.RootKey := ARootKey;
if MyReg.RegistryConnect('\\' + sMachine) then
begin
if MyReg.KeyExists(AKey) then
begin
if MyReg.OpenKeyReadOnly(AKey) then
begin
try
RegDataType := MyReg.GetDataType(AValue);
if RegDataType = rdBinary then
begin
DataSize := MyReg.GetDataSize(AValue);
if DataSize > 0 then
begin
SetLength(sBinData, DataSize);
Len := MyReg.ReadBinaryData(AValue, PChar(sBinData)^, DataSize);
if Len <> DataSize then
raise Exception.Create(SysErrorMessage(ERROR_CANTREAD))
else
begin
sResult := sBinData;
bResult := True;
end;
end;
end;
except
MyReg.CloseKey;
end;
MyReg.CloseKey;
end;
end;
end;
finally
MyReg.Free;
end;
Result := bResult;
end;
And I call it like this:
GetBinaryRegistryData(
HKEY_LOCAL_MACHINE,
'\SOFTWARE\Microsoft\Windows NT\CurrentVersion',
'DigitalProductId', '192.168.100.105',
sProductId
);
WriteLn(sProductId);
The result I receive from the WriteLn on the console is:
ñ ♥ ???????????6Z ????1 ???????☺ ???♦ ??3 ? ??? ?
??
Assuming that you are already connected remotely, try using the GetDataAsString function
to read binary data from the registry.
sResult := MyReg.GetDataAsString(AValue);
You're using Delphi 2010, so all your characters are two bytes wide. When you set the length of your result string, you're allocating twice the amount of space you need. Then you call ReadBinaryData, and it fills half your buffer. There are two bytes of data in each character. Look at each byte separately, and you'll probably find that your data looks less garbage-like.
Don't use strings for storing arbitrary data. Use strings for storing text. To store arbitrary blobs of data, use TBytes, which is an array of bytes.

Resources