Delphi printing to Generic text driver (Intermec PM4i)? - delphi

(edit This question has received few downvotes. I don't know a reason and still cannot see what's wrong with it. I can edit this if downvoters could comment about what they wish to see better written or lack of valuable info I have not given).
I have Intermec PM4i label printer and Generic text print driver. I am able to print text file script from Notepad or Delphi calls ShellExecute('printto',..) shellapi function.
I found few raw printing examples but none work. How can Delphi app print to Generic_text_driver without shellapi function? This is not GDI printer.canvas capable driver.
I have tried "everything" even legacy PASSTHROUGH printing examples. Only working method is Notepad.exe or shellexecute('printto', 'tempfile.txt',...) which I guess is using Notepad internally. I can see notepad printing dialog flashing on screen. I would like to control this directly from Delphi application.
This printFileToGeneric did not work.
// https://groups.google.com/forum/#!topic/borland.public.delphi.winapi/viHjsTf5EqA
Procedure PrintFileToGeneric(Const sFileName, printerName, docName: string; ejectPage: boolean);
Const
BufSize = 16384;
Var
Count : Integer;
BytesWritten: DWORD;
hPrinter: THandle;
DocInfo: TDocInfo1;
f: file;
Buffer: Pointer;
ch: Char;
Begin
If not WinSpool.OpenPrinter(PChar(printerName), hPrinter, nil) Then
raise Exception.Create('Printer not found');
Try
DocInfo.pDocName := PChar(docName);
DocInfo.pOutputFile := nil;
DocInfo.pDatatype := 'RAW';
If StartDocPrinter(hPrinter, 1, #DocInfo) = 0 Then
Raise Exception.Create('StartDocPrinter failed');
Try
If not StartPagePrinter(hPrinter) Then
Raise Exception.Create('StartPagePrinter failed');
System.Assign(f, sFileName);
Try
Reset(f, 1);
GetMem(Buffer, BufSize);
Try
While not eof(f) Do Begin
Blockread(f, Buffer^, BufSize, Count);
If Count > 0 Then Begin
If not WritePrinter(hPrinter, Buffer, Count, BytesWritten) Then
Raise Exception.Create('WritePrinter failed');
End;
End;
Finally
If ejectPage Then Begin
ch:= #12;
WritePrinter( hPrinter, #ch, 1, BytesWritten );
End;
FreeMem(Buffer, BufSize);
End;
Finally
EndPagePrinter(hPrinter);
System.Closefile( f );
End;
Finally
EndDocPrinter(hPrinter);
End;
Finally
WinSpool.ClosePrinter(hPrinter);
End;
End;
The following prtRaw helper util example did not work.
prtRaw.StartRawPrintJob/StartRawPrintPage/PrintRawData/EndRawPrintPage/EndRawPrintJob
http://www.swissdelphicenter.ch/torry/showcode.php?id=940
The following AssignPrn method did not work.
function testPrintText(params: TStrings): Integer;
var
stemp:String;
idx: Integer;
pOutput: TextFile;
begin
Result:=0;
idx := getPrinterIndexByName( params.Values['printer'] );
if (idx<0) then Raise Exception.Create('Printer not found');
WriteLn('Use printer(text) ' + IntToStr(idx) + ' ' + Printer.Printers[idx] );
Printer.PrinterIndex := idx;
stemp := params.Values['jobname'];
if (stemp='') then stemp:='rawtextprint';
Printer.Title:=stemp;
AssignPrn(pOutput);
ReWrite(pOutput);
stemp := 'INPUT ON'+#10;
stemp := stemp + 'NASC 1252'+#10;
stemp := stemp + 'BF OFF'+#10;
stemp := stemp + 'PP 30,480:FT "Swiss 721 BT",8,0,100'+#10;
stemp := stemp + 'PT "Test text 30,480 position ABC'+#10;
Write(pOutput, stemp);
//Write(pOutput, 'INPUT ON:');
//Write(pOutput, 'NASC 1252:');
//Write(pOutput, 'BF OFF:');
//Write(pOutput, 'PP 30,480:FT "Swiss 721 BT",8,0,100:');
//Write(pOutput, 'PT "Test text 30,480 position ABC":');
//Write(pOutput, 'Text line 3 goes here#13#10');
//Write(pOutput, 'Text line 4 goes here#13#10');
CloseFile(pOutput);
end;
This Printer.Canvas did not print anything, it should have had print something because Notepad is internally using GDI printout. Something is missing here.
function testPrintGDI(params: TStrings): Integer;
var
filename, docName:String;
idx: Integer;
lines: TStrings;
begin
Result:=0;
idx := getPrinterIndexByName( params.Values['printer'] );
if (idx<0) then Raise Exception.Create('Printer not found');
WriteLn('Use printer(gdi) ' + IntToStr(idx) + ' ' + Printer.Printers[idx] );
docName := params.Values['jobname'];
if (docName='') then docName:='rawtextprint';
filename := params.values['input'];
If Not FileExists(filename) then Raise Exception.Create('input file not found');
Printer.PrinterIndex := idx;
Printer.Title := docName;
Printer.BeginDoc;
lines := readTextLines(filename);
try
for idx := 0 to lines.Count-1 do begin
Printer.Canvas.TextOut(10, 10*idx, lines[idx]);
end;
finally
FreeAndNil(lines);
Printer.EndDoc;
end;
end;
Only three methods working are printing from Notepad.exe, Delphi ShellExecute call or open a raw TCP socket to IP:PORT address and write text lines to a socket outputstream.
function testPrintPrintTo(params: TStrings): Integer;
var
filename, printerName:String;
idx: Integer;
exInfo: TShellExecuteInfo;
device,driver,port: array[0..255] of Char;
hDeviceMode: THandle;
timeout:Integer;
//iRet: Cardinal;
begin
Result:=0;
idx := getPrinterIndexByName( params.Values['printer'] );
if (idx<0) then Raise Exception.Create('Printer not found');
WriteLn('Use printer(printto) ' + IntToStr(idx) + ' ' + Printer.Printers[idx] );
filename := params.values['input'];
If Not FileExists(filename) then Raise Exception.Create('input file not found');
filename := uCommon.absoluteFilePath(filename);
Printer.PrinterIndex := idx;
Printer.GetPrinter(device, driver, port, hDeviceMode);
printerName := Format('"%s" "%s" "%s"', [device, driver, port]);
FillChar(exInfo, SizeOf(exInfo), 0);
with exInfo do begin
cbSize := SizeOf(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
Wnd := 0;
exInfo.lpVerb := 'printto';
exInfo.lpParameters := PChar(printerName);
lpFile := PChar(filename);
lpDirectory := nil;
nShow := SW_HIDE;
end;
WriteLn('printto ' + printerName);
if Not ShellExecuteEx(#exInfo) then begin
Raise Exception.Create('ShellExecuteEx failed');
exit;
end;
try
timeout := 30000;
while WaitForSingleObject(exInfo.hProcess, 50) <> WAIT_OBJECT_0 do begin
Writeln('wait timeout ' + IntToStr(timeout));
dec(timeout, 50);
if (timeout<1) then break;
end;
finally
CloseHandle(exInfo.hProcess);
end;
{iRet:=ShellExecute(0, 'printto',
PChar(filename),
PChar(printerName), //Printer.Printers[idx]),
nil, //PChar(filepath),
SW_HIDE // SW_SHOWNORMAL
);
Writeln('printto return code ' + IntToStr(iRet)); // >32 OK }
end;

you can use this procedure:
where the LabelFile is the full path of the label file,we are using this code and works with generic text driver printer and the printer is set as the default printer.
it works with zebra printer and windows xp operating system.
i hope this will help you.
function PrintLabel(LabelName: String): Boolean;
var
EfsLabel,dummy: TextFile;
ch : Char;
begin
try
try
Result:= False;
AssignFile(EfsLabel,LabelName);
Reset(EfsLabel);
Assignprn(dummy);
ReWrite(Dummy);
While not Eof(EfsLabel) do
begin
Read(EfsLabel, Ch);
Write(dummy, Ch);
end;
Result:= True;
except
Result:=False;
LogMessage('Error Printing Label',[],LOG_ERROR);
end;
finally
CloseFile(EfsLabel);
CloseFile(dummy);
end;
end;

You are able to print to this printer from Notepad. Notepad prints using the standard GDI mechanism. If Notepad can do this then so can you. Use Printer.BeginDoc, Printer.Canvas etc. to print to this printer.

Related

Win7 IE11 IShellWindows returns 'Unknown Error' only on some machines

I use the following code to check, if there already exists an Internet Explorer 11 Tab with a given Url Location.
I started with code from here: http://francois-piette.blogspot.de/2013/01/internet-explorer-automation-part-1.html
function GetIERunningInstanceByUrl(FLogWriter: ILogWriter; const Url : String): IWebBrowser2;
function GetClassName(aHWND : HWND) : String;
var
L : Integer;
begin
SetLength(Result, MAX_PATH * SizeOf(Char));
L := WinApi.Windows.GetClassName(aHWND, PChar(Result), Length(Result));
SetLength(Result, L);
end;
var
aShWindows : IShellWindows;
aIdx : Integer;
aDisp: IDispatch;
aClassName: string;
begin
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Url: ''' + Url + '''.');
aShWindows := CoShellWindows.Create;
if not Assigned(aShWindows) then begin
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / After CoShellWindows.Create, not Assigned(aShWindows) = TRUE.');
end;
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / aShWindows.Count: ' + IntToStr(aShWindows.Count) + '.');
for aIdx := 0 to aShWindows.Count - 1 do begin
aDisp := aShWindows.Item(aIdx);
if not Assigned(aDisp) then begin
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / After aDisp := aShWindows.Item(aIdx=' + IntToStr(aIdx) + '), not Assigned(aDisp) = TRUE.');
end
else begin
if not Supports(aDisp, IID_IWebBrowser2) then begin
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Supports(aDisp, IID_IWebBrowser2) = FALSE.');
end
else begin
Result := aDisp as IWebBrowser2;
if not Assigned(Result) then begin
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / After Result := aDisp as IWebBrowser2, not Assigned(Result) = TRUE.');
end
else begin
aClassName := GetClassName(Result.HWND);
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / GetClassName(aShWindows.Item(aIdx=' + IntToStr(aIdx) + ').Result): ''' + aClassName + '''.');
end;
end;
end;
if Supports(aDisp, IID_IWebBrowser2) then begin
if Assigned(Result) then begin
if SameText(GetClassName(Result.HWND), 'IEFrame') then begin
//if SameText(Url, Result.LocationURL) then begin
if ContainsText(Result.LocationURL, Url) then begin
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Found, Result.HWND: ' + IntToStr(Result.HWND) + ', Result.LocationURL: ''' + Result.LocationURL + '''.');
Exit;
end
else begin
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Not found, ContainsText(Result.LocationURL, Url) = FALSE, Result.LocationURL: ''' + Result.LocationURL + ''' .');
end;
end
else begin
aClassName := GetClassName(Result.HWND);
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Not found, SameText(GetClassName(Result.HWND), ''IEFrame'') = FALSE, aClassName: ''' + aClassName + ''' .');
end;
end;
end;
end;
// Not found
Result := nil;
FLogWriter.LogMessage(ltDebug, 'GetIERunningInstanceByUrl / Not found, After Result = nil, Url: ''' + Url + '''.');
end;
The application is installed on machines, that all have Windows 7 Professional Service Pack 1, 64Bit and Internet Explorer 11 (Version 11.0.9600.18762).
The code works fine on most machines, but there are some machines, where I get an 'Unknown error' in this method, after it was running correctly for several times.
When having the error once, the only way to get the application running again, is to logoff from windows and login again.
Unfortunatelly, I may not debug on those (production) machines, so I got to use poor man's debugging, logging every line... (that is also the reason, why my above code became a little bit ugly to read on some lines, sorry.)
Doing that, I found, that it must be something related to the IShellWindows interface.
10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / Url: 'https://example.com/'.
10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / aShWindows.Count: 3.
10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / GetClassName(aShWindows.Item(aIdx=0).Result): 'CabinetWClass'.
10.08.2017 10:33:05 ThreadID: 0x00001A08 - GetIERunningInstanceByUrl / Not found, SameText(GetClassName(Result.HWND), 'IEFrame') = FALSE, aClassName: 'CabinetWClass' .
10.08.2017 10:33:05 ThreadID: 0x00001A08 - Meldung - Exception: Unbekannter Fehler Retry: 1
(translation: 'Unbekannter Fehler' means 'Unknown error')
In the above log sample, it seems that only the first item from the List of 3 items can be iterated using IShellWindows. Then an exception is raised.
Any help would be appreciated...
I had a similar problem - or still have with a program that uses the IShellWindows interface. My experience is that it does not depend on the machine, but can happen on any machine, but I did not found out what to do to prevent it.
What helps for me is to stop all explorer processes (not the Internet Explorer process!). I do this from within my program, but you can also do this via the Task Manager for testing. If the Task Bar runs in a seperate Explorer process, you will have to stop that too.
After you have restarted the Explorer, the interface works again. This is slightly better than to have to log off, since you do not need to restart all your applications and you can do this from within your code, but this is of course still no good solution, since the Task Bar will be rebuild during this process.
The code I use to Close all Explorer processes and restart is the following:
function isexplorerwindow(exwnd: hwnd): boolean;
var
p: array[0..max_path] of Char;
begin
GetClassName(exwnd, p, SizeOf(p));
result := ((strcomp(p, 'CabinetWClass') = 0) or (strcomp(p, 'ExploreWClass') = 0));
end;
procedure restartshell;
var
wnd: hwnd;
pid: dword;
processhandle: thandle;
SL: tstringlist;
z: integer;
StartUpInfo: TStartUpInfo;
ProcessInfo: TProcessInformation;
begin
if messagebox(0, pchar(_('Restarting the shell will close all explorer windows and the task bar.') + #13#10 +
_('Do you really want to continue?')), __('Warning'), mb_yesno or mb_defbutton2 or mb_iconquestion) = idno then
Exit;
SL := tstringlist.Create;
wnd := getwindow(getdesktopwindow, gw_child);
while (wnd <> 0) do
begin
if isexplorerwindow(wnd) then
SL.Add(inttostr(wnd));
wnd := getwindow(wnd, gw_hwndnext);
end;
for z := 0 to SL.count - 1 do
postMessage(strtoint(SL[z]), $10, 0, 0);
SL.Free;
application.ProcessMessages;
sleep(1000);
application.ProcessMessages;
wnd := findwindow('Progman', nil);
if wnd > 0 then
begin
GetWindowThreadProcessId(wnd, pid);
if (pid > 0) then
begin
processhandle := OpenProcess(1, false, pid);
if (processhandle > 0) then
begin
TerminateProcess(processhandle, 0);
CloseHandle(processhandle);
end;
end;
end;
application.ProcessMessages;
sleep(1000);
application.ProcessMessages;
FillChar(StartUpInfo, SizeOf(StartUpInfo), #0);
StartUpInfo.cb := SizeOf(StartUpInfo);
StartUpInfo.dwFlags := STARTF_USESHOWWINDOW;
if not CreateProcess(nil, 'explorer.exe', nil, nil, false, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil,
StartUpInfo, ProcessInfo) then
messagebeep(mb_iconstop);
You can try this. It should return the topmost IE window that is navigated to the given URL:
function TryGetWebBrowser(const URL: WideString; out Browser: IWebBrowser2): Boolean;
var
Handle: HWND;
Unused: OleVariant;
Location: OleVariant;
WndIface: IDispatch;
ShellWindows: IShellWindows;
begin
Result := False;
if Succeeded(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER, IID_IShellWindows, ShellWindows)) then
begin
Unused := Unassigned;
Location := URL;
WndIface := ShellWindows.FindWindowSW(Location, Unused, SWC_BROWSER, Integer(Handle), SWFO_NEEDDISPATCH);
Result := Assigned(WndIface) and Succeeded(WndIface.QueryInterface(IWebBrowser2, Browser));
end;
end;

How to create Picture and HTML formats all together on the clipboard?

I need to create the following formats all together on the clipboard:
CF_BITMAP
CF_DIB
CF_DIB5
HTML Format
This is a console program which can create either the picture formats OR the HTML Format, but not all together on the clipboard:
program CopyImageFromFile;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
Vcl.Clipbrd,
Vcl.ExtCtrls,
Vcl.Imaging.pngimage,
System.SysUtils;
function FormatHTMLClipboardHeader(HTMLText: string): string;
const
CrLf = #13#10;
begin
Result := 'Version:0.9' + CrLf;
Result := Result + 'StartHTML:-1' + CrLf;
Result := Result + 'EndHTML:-1' + CrLf;
Result := Result + 'StartFragment:000081' + CrLf;
Result := Result + 'EndFragment:°°°°°°' + CrLf;
Result := Result + HTMLText + CrLf;
Result := StringReplace(Result, '°°°°°°', Format('%.6d', [Length(Result)]), []);
end;
procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
gMem: HGLOBAL;
lp: PChar;
Strings: array[0..1] of AnsiString;
Formats: array[0..1] of UINT;
i: Integer;
ThisImage: TImage;
MyFormat: Word;
Bitmap: TBitMap;
AData: THandle;
APalette: HPALETTE;
begin
gMem := 0;
//{$IFNDEF USEVCLCLIPBOARD}
//Win32Check(OpenClipBoard(0));
//{$ENDIF}
Clipboard.Open;
try
//most descriptive first as per api docs
Strings[0] := FormatHTMLClipboardHeader(htmlStr);
Strings[1] := str;
Formats[0] := RegisterClipboardFormat('HTML Format');
Formats[1] := CF_TEXT;
{$IFNDEF USEVCLCLIPBOARD}
Win32Check(EmptyClipBoard);
{$ENDIF}
for i := 0 to High(Strings) do
begin
if Strings[i] = '' then Continue;
//an extra "1" for the null terminator
gMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, Length(Strings[i]) + 1);
{Succeeded, now read the stream contents into the memory the pointer points at}
try
Win32Check(gmem <> 0);
lp := GlobalLock(gMem);
Win32Check(lp <> nil);
CopyMemory(lp, PChar(Strings[i]), Length(Strings[i]) + 1);
finally
GlobalUnlock(gMem);
end;
Win32Check(gmem <> 0);
SetClipboardData(Formats[i], gMEm);
Win32Check(gmem <> 0);
gmem := 0;
end;
ThisImage := TImage.Create(nil);
try
ThisImage.Picture.LoadFromFile(APngFile);
// Comment this out to copy only the HTML Format:
Clipboard.Assign(ThisImage.Picture);
{MyFormat := CF_PICTURE;
ThisImage.Picture.SaveToClipBoardFormat(MyFormat, AData, APalette);
ClipBoard.SetAsHandle(MyFormat, AData);}
finally
ThisImage.Free;
end;
finally
//{$IFNDEF USEVCLCLIPBOARD}
//Win32Check(CloseClipBoard);
//{$ENDIF}
Clipboard.Close;
end;
end;
var
HTML: string;
begin
try
// Usage: CopyImageFromFile.exe test.png
// test.png is 32 bit with alpha channel
if ParamCount = 1 then
begin
if FileExists(ParamStr(1)) then
begin
if LowerCase(ExtractFileExt(ParamStr(1))) = '.png' then
begin
HTML := '<img border="0" src="file:///' + ParamStr(1) + '">';
CopyHTMLAndImageToClipBoard('test', ParamStr(1), HTML);
end;
end;
end;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
Readln;
end;
end;
end.
So how can I create all these formats together on the clipboard?
TClipboard empties the clipboard the first time you use a TClipboard method to put data on the clipboard (TClipboard.Assign(), TClipboard.SetBuffer(), TClipboard.SetAsHandle(), etc) after calling Open(). TClipboard expects you to use only its methods for accessing the clipboard, so your use of SetClpboardData() directly to store your string data is bypassing TClipboard's internal logic, thus your call to Assign() is seen as the first clipboard write and TClipboard wipes out any data you stored with SetClipboardData().
To avoid that, you have a few choices:
Assign() your image to the clipboard first, then save your string items with SetClipboardData() afterwards.
don't use Assign() at all. Use TPicture.SaveToClipboardFormat() directly and then call SetClipboardData().
don't use SetClipboardData() directly unless USEVCLCLIPBOARD is not defined. Use TClipboard.SetAsHandle() instead.
I would suggest #3. Let TClipboard do all of the work:
var
CF_HTML: UINT = 0;
// TClipboard.SetBuffer() allows a format and an arbitrary buffer
// to be specified and handles the global memory allocation.
// However, it is protected, so using an accessor class to reach it.
//
// TClipboard.AsText and TClipboard.SetTextBuf() always use
// CF_(UNICODE)TEXT, and TClipboard.SetAsHandle() requires manual
// allocation...
//
type
TClipboardAccess = class(TClipboard)
end;
procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
TmpHtmlStr: AnsiString;
ThisImage: TPicture;
begin
Clipboard.Open;
try
//most descriptive first as per api docs
TmpHtmlStr := FormatHTMLClipboardHeader(htmlStr);
TClipboardAccess(Clipboard).SetBuffer(CF_HTML, PAnsiChar(TmpHtmlStr)^, Length(TmpHtmlStr) + 1);
TClipboardAccess(Clipboard).SetBuffer(CF_TEXT, PAnsiChar(Str)^, Length(Str) + 1);
ThisImage := TPicture.Create;
try
ThisImage.LoadFromFile(APngFile);
Clipboard.Assign(ThisImage);
finally
ThisImage.Free;
end;
finally
Clipboard.Close;
end;
end;
initialization
CF_HTML := RegisterClipboardFormat('HTML Format');
If you really need to support {$IFNDEF USEVCLCLIPBOARD} then you cannot use TClipboard at all, eg:
var
CF_HTML: UINT = 0;
{$IFDEF USEVCLCLIPBOARD}
// TClipboard.SetBuffer() allows a format and an arbitrary buffer
// to be specified and handles the global memory allocation.
// However, it is protected, so using an accessor class to reach it.
//
// TClipboard.AsText and TClipboard.SetTextBuf() always use
// CF_(UNICODE)TEXT, and TClipboard.SetAsHandle() requires manual
// allocation...
//
type
TClipboardAccess = class(TClipboard)
end;
{$ENDIF}
procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
ThisImage: TPicture;
{$IFNDEF USEVCLCLIPBOARD}
ImgData: THandle;
ImgFormat: Word;
ImgPalette: HPALETTE;
{$ENDIF}
procedure SetAsText(Format: UINT; const S: AnsiString);
{$IFNDEF USEVCLCLIPBOARD}
var
gMem: HGLOBAL;
lp: PAnsiChar;
{$ENDIF}
begin
{$IFDEF USEVCLCLIPBOARD}
TClipboardAccess(Clipboard).SetBuffer(Format, PAnsiChar(S)^, Length(S) + 1);
{$ELSE}
//an extra "1" for the null terminator
gMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, Length(S) + 1);
Win32Check(gmem <> 0);
try
{Succeeded, now read the stream contents into the memory the pointer points at}
lp := GlobalLock(gMem);
Win32Check(lp <> nil);
try
CopyMemory(lp, PAnsiChar(S), Length(S) + 1);
finally
GlobalUnlock(gMem);
end;
except
GlobalFree(gMem);
raise;
end;
SetClipboardData(Format, gMem);
{$ENDIF}
end;
begin
{$IFDEF USEVCLCLIPBOARD}
Clipboard.Open;
{$ELSE}
Win32Check(OpenClipBoard(0));
{$ENDIF}
try
//most descriptive first as per api docs
SetAsText(CF_HTML, FormatHTMLClipboardHeader(htmlStr));
SetAsText(CF_TEXT, Str);
ThisImage := TPicture.Create;
try
ThisImage.LoadFromFile(APngFile);
{$IFDEF USEVCLCLIPBOARD}
Clipboard.Assign(ThisImage);
{$ELSE}
ImgPalette := 0;
ThisImage.SaveToClipboardFormat(ImgFormat, ImgData, ImgPalette);
SetClipboardData(ImgFormat, ImgData);
if ImgPalette <> 0 then
SetClipboardData(CF_PALETTE, ImgPalette);
{$ENDIF}
finally
ThisImage.Free;
end;
finally
{$IFDEF USEVCLCLIPBOARD}
Clipboard.Close;
{$ELSE}
Win32Check(CloseClipBoard);
{$ENDIF}
end;
end;
initialization
CF_HTML := RegisterClipboardFormat('HTML Format');
David is right. You need to have one pair of open/close, and only one EmptyClipboard. You need to iterate through your formats and call SetClipboardData for each one.
RegisterClipboardFormat should only be called once, so do that in some initialization routine.
I would also try to avoid doing any file I/O once you've opened the clipboard, as you don't want to hold it open longer than necessary. i.e. read your pictures from disk first, if possible.

Pipes in Delphi for Command Prompt

How can I get Delphi to pass a string to the input pipe to a CMD process. I am able to get an error pipe and output pipe functioning properly, unfortunately not the input pipe. The code I am using is taken from an online tutorial for piping. There were several errors in the original code causing problems when it was compiled. They have been fixed but I am left with problems when trying to pass input still.
Here is the code in the Form.Create event. I also have included the WritePipe and ReadPipe methods. WritePipe does not work, ReadPipe does work. Both WriteFile and ReadFile in the Pipe methods return a successful message, only the ReadPipe actually works however.
var
DosApp: String;
DosSize: Integer;
Security : TSecurityAttributes;
start : TStartUpInfo;
byteswritten: DWord;
WriteString : ansistring;
begin
CommandText.Clear;
// get COMSPEC variable, this is the path of the command-interpreter
SetLength(Dosapp, 255);
DosSize := GetEnvironmentVariable('COMSPEC', #DosApp[1], 255);
SetLength(Dosapp, DosSize);
// create pipes
With Security do
begin
nlength := SizeOf(TSecurityAttributes) ;
binherithandle := true;
lpsecuritydescriptor := nil;
end;
CreatePipe(InputPipeRead, InputPipeWrite, #Security, 0);
CreatePipe(OutputPipeRead, OutputPipeWrite, #Security, 0);
CreatePipe(ErrorPipeRead, ErrorPipeWrite, #Security, 0);
// start command-interpreter
FillChar(Start,Sizeof(Start),#0) ;
//start.hStdInput := InputPipeRead;
start.hStdOutput := OutputPipeWrite;
start.hStdError := ErrorPipeWrite;
start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
start.wShowWindow := SW_Show;//SW_HIDE;
start.cb := SizeOf(start) ;
if CreateProcess('', PChar(DosApp), #Security, #Security, true,
CREATE_NEW_CONSOLE or SYNCHRONIZE, // CREATE_NO_WINDOW,
nil, nil, start, ProcessInfo) then
begin
MyThread := MainUnit.monitor.Create; // start monitor thread
MyThread.Priority := tpHigher;
end;
Button1.Enabled := true;
cmdcount := 1;
end;
Write Pipe:
procedure WritePipeOut(OutputPipe: THandle; InString: PWideChar);
// writes Instring to the pipe handle described by OutputPipe
var
count : integer;
byteswritten: DWord;
outputstring : PAnsiChar;
TextBuffer: array[1..32767] of AnsiChar;// char;
TextString: String;
begin
// most console programs require CR/LF after their input.
InString := PWideChar(InString + #13#10);
WriteFile(InputPipeWrite, InString[1], Length(InString), byteswritten, nil);
end;
Read Pipe:
function ReadPipeInput(InputPipe: THandle; var BytesRem: Integer): String;
{
reads console output from InputPipe. Returns the input in function
result. Returns bytes of remaining information to BytesRem
}
var
TextBuffer: array[1..32767] of AnsiChar;// char;
TextString: String;
BytesRead: Cardinal;
PipeSize: Integer;
begin
Result := '';
PipeSize := length(TextBuffer);
// check if there is something to read in pipe
PeekNamedPipe(InputPipe, nil, PipeSize, #BytesRead, #PipeSize, #BytesRem);
if bytesread > 0 then
begin
ReadFile(InputPipe, TextBuffer, pipesize, bytesread, nil);
// a requirement for Windows OS system components
OemToChar(#TextBuffer, #TextBuffer);
TextString := String(TextBuffer);
SetLength(TextString, BytesRead);
Result := TextString;
end;
end;
Further note; this is for use with the Java Debugger, which requires input in stages and so I do not believe there is any alternative method other than manipulating input directly to the JDB.
Any help is much appreciated!
1) You should pass InputPipeRead as hStdInput into CreateProcess: uncomment your line start.hStdInput := InputPipeRead;
2) The WritePipeOut function has two errors: it writes a Unicode (UTF-16LE) string into a pipe, and it skips the first character (since it writes a memory area beginning at InString[1]). Instead of WriteFile(InputPipeWrite, InString[1], Length(InString),... you should write something like:
var AnsiBuf: AnsiString;
...
AnsiBuf := String(InString) + #13#10;
Write(InputPipeWrite, AnsiBuf[1], Length(AnsiBuf), byteswritten, nil);

Execute an EXE File from Resource into Memory

I want to execute an EXE File that is compiled with my application as a Resource. I want to execute it directly in Memory.
I have seen this Topic :
Is it possible to embed and run exe file in a Delphi executable app?
And this Code :
http://www.coderprofile.com/networks/source-codes/138/execute-resource-directly-in-memory
I used this Code :
type
TSections = array [0..0] of TImageSectionHeader;
...
{$IMAGEBASE $10000000}
function GetAlignedSize(Size: dword; Alignment: dword): dword;
begin
if ((Size mod Alignment) = 0) then
Result := Size
else
Result := ((Size div Alignment) + 1) * Alignment;
end;
function ImageSize(Image: pointer): dword;
var
Alignment: dword;
ImageNtHeaders: PImageNtHeaders;
PSections: ^TSections;
SectionLoop: dword;
begin
ImageNtHeaders := pointer(dword(dword(Image)) + dword(PImageDosHeader(Image)._lfanew));
Alignment := ImageNtHeaders.OptionalHeader.SectionAlignment;
if ((ImageNtHeaders.OptionalHeader.SizeOfHeaders mod Alignment) = 0) then
begin
Result := ImageNtHeaders.OptionalHeader.SizeOfHeaders;
end
else
begin
Result := ((ImageNtHeaders.OptionalHeader.SizeOfHeaders div Alignment) + 1) * Alignment;
end;
PSections := pointer(pchar(#(ImageNtHeaders.OptionalHeader)) + ImageNtHeaders.FileHeader.SizeOfOptionalHeader);
for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
begin
if PSections[SectionLoop].Misc.VirtualSize <> 0 then
begin
if ((PSections[SectionLoop].Misc.VirtualSize mod Alignment) = 0) then
begin
Result := Result + PSections[SectionLoop].Misc.VirtualSize;
end
else
begin
Result := Result + (((PSections[SectionLoop].Misc.VirtualSize div Alignment) + 1) * Alignment);
end;
end;
end;
end;
procedure CreateProcessEx(FileMemory: pointer);
var
BaseAddress, Bytes, HeaderSize, InjectSize, SectionLoop, SectionSize: dword;
Context: TContext;
FileData: pointer;
ImageNtHeaders: PImageNtHeaders;
InjectMemory: pointer;
ProcInfo: TProcessInformation;
PSections: ^TSections;
StartInfo: TStartupInfo;
begin
ImageNtHeaders := pointer(dword(dword(FileMemory)) + dword(PImageDosHeader(FileMemory)._lfanew));
InjectSize := ImageSize(FileMemory);
GetMem(InjectMemory, InjectSize);
try
FileData := InjectMemory;
HeaderSize := ImageNtHeaders.OptionalHeader.SizeOfHeaders;
PSections := pointer(pchar(#(ImageNtHeaders.OptionalHeader)) + ImageNtHeaders.FileHeader.SizeOfOptionalHeader);
for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
begin
if PSections[SectionLoop].PointerToRawData < HeaderSize then HeaderSize := PSections[SectionLoop].PointerToRawData;
end;
CopyMemory(FileData, FileMemory, HeaderSize);
FileData := pointer(dword(FileData) + GetAlignedSize(ImageNtHeaders.OptionalHeader.SizeO fHeaders, ImageNtHeaders.OptionalHeader.SectionAlignment));
for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
begin
if PSections[SectionLoop].SizeOfRawData > 0 then
begin
SectionSize := PSections[SectionLoop].SizeOfRawData;
if SectionSize > PSections[SectionLoop].Misc.VirtualSize then SectionSize := PSections[SectionLoop].Misc.VirtualSize;
CopyMemory(FileData, pointer(dword(FileMemory) + PSections[SectionLoop].PointerToRawData), SectionSize);
FileData := pointer(dword(FileData) + GetAlignedSize(PSections[SectionLoop].Misc.VirtualSize, ImageNtHeaders.OptionalHeader.SectionAlignment));
end
else
begin
if PSections[SectionLoop].Misc.VirtualSize <> 0 then FileData := pointer(dword(FileData) + GetAlignedSize(PSections[SectionLoop].Misc.VirtualSize, ImageNtHeaders.OptionalHeader.SectionAlignment));
end;
end;
ZeroMemory(#StartInfo, SizeOf(StartupInfo));
ZeroMemory(#Context, SizeOf(TContext));
CreateProcess(nil, pchar(ParamStr(0)), nil, nil, False, CREATE_SUSPENDED, nil, nil, StartInfo, ProcInfo);
Context.ContextFlags := CONTEXT_FULL;
GetThreadContext(ProcInfo.hThread, Context);
ReadProcessMemory(ProcInfo.hProcess, pointer(Context.Ebx + 8), #BaseAddress, 4, Bytes);
VirtualAllocEx(ProcInfo.hProcess, pointer(ImageNtHeaders.OptionalHeader.ImageBase), InjectSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ProcInfo.hProcess, pointer(ImageNtHeaders.OptionalHeader.ImageBase), InjectMemory, InjectSize, Bytes);
WriteProcessMemory(ProcInfo.hProcess, pointer(Context.Ebx + 8), #ImageNtHeaders.OptionalHeader.ImageBase, 4, Bytes);
Context.Eax := ImageNtHeaders.OptionalHeader.ImageBase + ImageNtHeaders.OptionalHeader.AddressOfEntryPoint;
SetThreadContext(ProcInfo.hThread, Context);
ResumeThread(ProcInfo.hThread);
finally
FreeMemory(InjectMemory);
end;
end;
procedure Execute;
var
RS : TResourceStream;
begin
RS := TResourceStream.Create(HInstance, 'MrResource', RT_RCDATA);
try
CreateProcessEx(RS.Memory);
finally
RS.Free;
end;
end;
but I got " Out of Memory " error in this line ( of CreateProcessEX ) :
GetMem(InjectMemory, InjectSize);
can someone help me solve this error ? or give me some working code/solution ?
thanks before ...
An excelent unit for what you need has already been done with support for windows 64 bit.
you can find it here:
uExecFromMem by steve10120 fixed by test
here is a trivial approach written by me if you don't want to use that unit
var
eu:array of byte;
FS:TFileStream;
CONT:TContext;
imgbase,btsIO:DWORD;
IDH:PImageDosHeader;
INH:PImageNtHeaders;
ISH:PImageSectionHeader;
i:Integer;
PInfo:TProcessInformation;
SInfo:TStartupInfo;
begin
if OpenDialog1.Execute then
begin
FS:=TFileStream.Create(OpenDialog1.FileName,fmOpenRead or fmShareDenyNone);
SetLength(eu,FS.Size);
FS.Read(eu[0],FS.Size);
FS.Free;
Sinfo.cb:=Sizeof(TStartupInfo);
CreateProcess(nil,Pchar(paramstr(0)),nil,nil,FALSE,CREATE_SUSPENDED,nil,nil,SInfo,PInfo);
IDH:=#eu[0];
INH:=#eu[IDH^._lfanew];
imgbase:=DWORD(VirtualAllocEx(PInfo.hProcess,Ptr(INH^.OptionalHeader.ImageBase),INH^.OptionalHeader.SizeOfImage,MEM_COMMIT or MEM_RESERVE,PAGE_EXECUTE_READWRITE));
ShowMessage(IntToHex(imgbase,8));
WriteProcessMemory(PInfo.hProcess,Ptr(imgbase),#eu[0],INH^.OptionalHeader.SizeOfHeaders,btsIO);
for i:=0 to INH^.FileHeader.NumberOfSections - 1 do
begin
ISH:=#eu[IDH^._lfanew + Sizeof(TImageNtHeaders) + i * Sizeof(TImageSectionHeader)];
WriteProcessMemory(PInfo.hProcess,Ptr(imgbase + ISH^.VirtualAddress),#eu[ISH^.PointerToRawData],ISH^.SizeOfRawData,btsIO);
end;
CONT.ContextFlags:=CONTEXT_FULL;
GetThreadContext(PInfo.hThread,CONT);
CONT.Eax:=imgbase + INH^.OptionalHeader.AddressOfEntryPoint;
WriteProcessMemory(PInfo.hProcess,Ptr(CONT.Ebx+8),#imgbase,4,btsIO);
ShowMessage('Press ok on ENTER');
SetThreadContext(PInfo.hThread,CONT);
ResumeThread(PInfo.hThread);
CloseHandle(Pinfo.hThread);
CloseHandle(PInfo.hProcess);
end;
end;
To get opc0de's answer working on both 32bit and 64bit platforms change the context setting as follows,
GetThreadContext(PInfo.hThread,CONT);
{$IFDEF WIN64}
CONT.P6Home:=imgbase + INH^.OptionalHeader.AddressOfEntryPoint;
WriteProcessMemory(PInfo.hProcess,Ptr(CONT.P3Home+8),#imgbase,4,btsIO);
{$ELSE}
CONT.Eax:=imgbase + INH^.OptionalHeader.AddressOfEntryPoint;
WriteProcessMemory(PInfo.hProcess,Ptr(CONT.Ebx+8),#imgbase,4,btsIO);
{$ENDIF}
ShowMessage('Press ok on ENTER');
SetThreadContext(PInfo.hThread,CONT);
Your expected API pointer layout sounds not correct, and the returned size is not.
How did you define all the PImageNtHeaders and such TSections types? What is the record alignment? Shouldn't it need to be packed or aligned with some granularity? Perhaps you forgot some {$A..} or enumeration size when copy/paste the original code into your unit...
Difficult to guess without the whole source code.

Not getting path of various system processes by GetModuleFileNameEx()

I have created this function to get the path of various network processes, like svchost, Firefox, etc. Here is the code:
function GetProcessPath(var pId:Integer):String;
var
Handle: THandle;
begin
Result := '';
try
Handle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, pID);
if Handle <> 0 then
begin
try
SetLength(Result, MAX_PATH);
if GetModuleFileNameEx(Handle, 0, PChar(Result), MAX_PATH) > 0 then
SetLength(Result, StrLen(PChar(Result)))
else
Result := '';
finally
CloseHandle(Handle);
end;
end;
except
on E:Exception do
ShowMessage(E.ClassName + ':' + E.Message);
end;
end;
My problem is that I do not get the path of all the processes. It works fine for getting the path of Firefox, and other similar user level processes. But for processes like alg, Svchost, I cannot get the path by this method. My guess is I must use some different API. How can I fix this problem?
I am using Windows XP, 32 bits.
You need to set debug privileges. Here is how it is done:
function NTSetPrivilege(sPrivilege: string; bEnabled: Boolean): Boolean;
var
hToken: THandle;
TokenPriv: TOKEN_PRIVILEGES;
PrevTokenPriv: TOKEN_PRIVILEGES;
ReturnLength: Cardinal;
begin
Result := True;
// Only for Windows NT/2000/XP and later.
if not (Win32Platform = VER_PLATFORM_WIN32_NT) then Exit;
Result := False;
// Obtain the processes token
if OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then
begin
try
// Get the locally unique identifier (LUID) .
if LookupPrivilegeValue(nil, PChar(sPrivilege),
TokenPriv.Privileges[0].Luid) then
begin
TokenPriv.PrivilegeCount := 1; // One privilege to set
case bEnabled of
True: TokenPriv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
False: TokenPriv.Privileges[0].Attributes := 0;
end;
ReturnLength := 0; // Replaces a var parameter
PrevTokenPriv := TokenPriv;
// Enable or disable the privilege
AdjustTokenPrivileges(hToken, False, TokenPriv, SizeOf(PrevTokenPriv),
PrevTokenPriv, ReturnLength);
end;
finally
CloseHandle(hToken);
end;
end;
end;
NtSetPrivilege('SeDebugPrivilege', TRUE); // Call this on form create

Resources