I am using C++ Builder XE7 VCL.
Around August 11 2016 2:00pm UTC, I started receiving multiple complaints from my user base about printing problems. Most of these printing modules have proven stable for many years, and there were no updates to my project within the past 24 hours. I was able to reproduce similar problems on my development/test environment.
Without going into many details of my project, let me present a very simple printing program that is failing:
void __fastcall TForm1::PrintButtonClick(TObject *Sender)
{
// Test Print:
TPrinter *Prntr = Printer();
Prntr->Title = "Test_";
Prntr->BeginDoc();
Prntr->Canvas->Font->Size = 10;
Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *");
if (Prntr->Printing) {
Prntr->EndDoc();
}
}
On the first attempt to print, everything works perfectly as expected. If I click the button a second time, TPrinter produces a small PDF, but the PDF file is actually corrupted and appears to have a file handle stuck to it.
If I click the button a third time, I get no printing and the following error message appears:
Printer is not currently printing.
My own test was done using a PDF printer driver, but the complaints I am receiving from users include a variety of local printers, network printers, PDF printers, etc.
In my actual project, I have try/catch exception handling, so the actual results are slightly different, but substantially similar to this result. The results show the hallmarks of instability and/or memory leaks without much in terms of error messages.
I suspect there may have been some Microsoft Windows updates that are tangling with Embarcadero DLLs, but I have not been able to verify this so far.
Is anyone else having similar problems?
The reason using a TPrintDialog or TPrinterSetupDialog "works" to fix the error is because they force the singleton TPrinter object (returned by the Vcl.Printers.Printer() function) to release its current handle to a printer if it has one, thus causing TPrinter.BeginDoc() to create a new handle. TPrinter releases its printer handle when:
it is being destroyed.
its NumCopies, Orientation, or PrinterIndex property is set.
its SetPrinter() method is called (internally by the PrinterIndex property setter and SetToDefaultPrinter() method, and by TPrintDialog and TPrinterSetupDialog).
Without doing that, calling TPrinter.BeginDoc() multiple times will just keep re-using the same printer handle. And apparently something about the recent Microsoft security update has now affected that handle reuse.
So, in short, (without uninstalling the Microsoft update) in between calls to BeginDoc() you need to do something that causes TPrinter to release and recreate its printer handle, and then the problem should go away. At least until Embarcadero can release a patch to TPrinter to address this issue. Maybe they could update TPrinter.EndDoc() or TPrinter.Refresh() to release the current printer handle (they currently do not).
Therefore, the following workaround resolves the printing issue without requiring any changes to the user interface:
void __fastcall TForm1::PrintButtonClick(TObject *Sender)
{
// Test Print:
TPrinter *Prntr = Printer();
Prntr->Title = "Test_";
Prntr->Copies = 1; // Here is the workaround
Prntr->BeginDoc();
if (Prntr->Printing) {
Prntr->Canvas->Font->Size = 10;
Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *");
Prntr->EndDoc();
}
}
There are no Embarcadero DLLs involved in printing. TPrinter simply calls Win32 API GDI-based print functions directly.
The "Printer is not currently printing" error occurs when one of the following operations is performed on a TPrinter when its Printing property is false:
TPrinter::NewPage()
TPrinter::EndDoc()
TPrinter::Abort()
a TPrinter::Canvas subproperty is being changed.
the TPrinter::Canvas is being drawn on.
You are performing half of these operations in the test code you have shown, but you did not specify which line of code is actually throwing the error.
The Printing property simply returns the current value of the TPrinter::FPrinting data member, which is set to false only when:
the TPrinter object is initially created (the Printer() function returns a singleton object that is reused for the lifetime of the executable).
the Win32 API StartDoc() function fails inside of TPrinter::BeginDoc() (FPrinting is set to true before StartDoc() is called).
TPrinter::EndDoc() is called when Printing is true.
So, given the test code you have shown, there are two possibilities:
StartDoc() fails and you are not checking for that condition. BeginDoc() will not throw an error (VCL bug?!?), it will simply exit normally but Printing will be false. Add a check for that:
Prntr->BeginDoc();
if (Prntr->Printing) { // <-- here
Prntr->Canvas->Font->Size = 10;
Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *");
Prntr->EndDoc();
}
the Printing property is getting set to false prematurely while you are in the process of printing something. The only ways that could happen in the code shown are if:
random memory is being corrupted, and TPrinter happens to be the victim.
multiple threads are manipulating the same TPrinter object at the same time. TPrinter is not thread-safe.
Since you can reproduce the problem in your development system, I suggest you enable Debug DCUs in the project options, then run your app in the debugger, and put a data breakpoint on the TPrinter::FPrinting data member. The breakpoint will be hit when FPrinting changes value, and you will be able to look at the call stack to see exactly which code is making that change.
Based on this information, I am going to go out on a limb and guess that the cause of your error is StartDoc() failing. Unfortunately, StartDoc() is not documented as returning why it fails. You certainly cannot use GetLastError() for that (most GDI errors are not reported by GetLastError()). You might be able to use the Win32 API Escape() or ExtEscape() function to retrieve an error code from the print driver itself (use TPrinter::Canvas::Handle as the HDC to query). But if that does not work, you won't be able to determine the reason of the failure, unless Windows is reporting an error message in its Event Log.
If StartDoc() really is failing, it is because of an Win32 API failure, not a VCL failure. Most likely the printer driver itself is failing internally (especially if a PDF print driver is leaving an open file handle to its PDF file), or Windows is failing to communicate with the driver correctly. Either way, it is outside of the VCL. This is consistent with the fact that the error started happening without you making any changes to your app. A Windows Update likely caused a breaking change in the print driver.
Try removing the following Windows update:
Security Update for Microsoft Windows (KB3177725)
MS16-098: Description of the security update for Windows kernel-mode drivers: August 9, 2016
This appears to resolve the issue for several test cases so far.
It started happening here today too. On my Windows 10 setup this would happen after calling TForm's Print() 3 times. I tried both the Microsoft Print to PDF and the Microsoft XPS Document Writer and both gave the same error.
I did some quick debugging and found that it's the call to StartDoc() that returns a value <= 0.
A temp fix until I can figure out what is really causing this is to re-create the Printer object in Printers by calling
Vcl.Printers.SetPrinter(TPrinter.Create).Free;
after calling anything that is using the Printer object. Might not be advisable to do that, but it solved my issue for now.
Looks like something is not released properly when calling EndDoc()
It seems that this is really a Microsoft problem and they should fix this buggy update. You can find more info at this on the company site.
Using a Printer Setup Dialog is only a workaround, not real solution, for this Microsoft bug. After confirmation, the printer setup dialog always creates a new handle for the printer. The next one or two print jobs will then succeed.
The patch should come from Microsoft, not from Embracadero. Thousands of programs are affected and it would be a massive waste of time and money to implement a workaround in all of them, if there is a bug in MS update.
I started experiencing the same weird behavior at more or less the same time, when running 32 bit Delphi applications built in XE7 on a Windows 10 64 bit system.
After uninstalling the latest security upgrade for Windows 10 (KB3176493), printing from these applications works normally again.
A rather surprising side effect of uninstalling this update seems to be that the file associations - which are the default programs for handling specific file types - are being reverted to Microsoft Windows default values...
The following variation of the code in the question will resolve the problem, but requires a TPrinterSetupDialog component added to the form:
void __fastcall TForm1::PrintButtonClick(TObject *Sender)
{
// Test Print:
TPrinter *Prntr = Printer();
Prntr->Title = "Test_";
PrinterSetupDialog1->Execute();
Prntr->BeginDoc();
if (Prntr->Printing) {
Prntr->Canvas->Font->Size = 10;
Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *");
Prntr->EndDoc();
}
}
For program usage, the user will be presented with the printer setup dialog before proceeding to printing.
As to "why", my best guess at this point is that TPrinter used alone does not have all necessary permissions to access all necessary resources from Windows after the Security Update for Microsoft Windows (KB3177725) was implemented on Aug 10, 2016. Somehow a call to TPrinterSetupDialog (or TPrintDialog) before calling BeginDoc() sets up the necessary conditions for TPrinter to perform successfully.
Related
I have code that calls RenameFile().
The Result passed back is sometimes False; this appears to happen when the (.pdf) file is in use.
I have seen what appears to be a 'standard' message loop in everyday programs where, if an operation can not be executed against a file that is in use, the message prompts the user to close the file and try again (or Cancel).
Is there a function in Delphi that does this, or does anyone have a coding suggestion that mimics this behavior?
I am working in Delphi-Tokyo.
Thanks!
I'm struggling with exception handling inside my Datasnap REST Service (Delphi XE3 but also tried with Delphi 10 Seattle). I have written half a dozen Windows Service over the years and I always include a TApplicationEvents component so that I can log any Application Exceptions to the Windows Event Log.
However, this behaviour is not happening with a Datasnap Service. The TApplicationEvents.OnException event never gets fired, so I assume something else is eating the exception and handling it before it gets here.
The exception is displayed in the web service method's result, which is fine because it means I can at least display something on the client side, but I'd also like to catch it before then so that I might be able to handle different exceptions server-side.
The only consistent way I have managed so far is to wrap each individual method in a try..except block, and handle the exception in each method, before re-raising the exception. However, with a Web Service of 20 methods and growing, this isn't really going to scale up.
I have also tried implementing the OnError, OnTrace and other events of the some of the Datasnap components (TDSServer, TDSHTTPService, TDSTCPServerTransport, etc.) but these never seem to get fired either.
Has anyone come across anything like this, please?
Tl;Dr: it's not implemented in a usable fashion (in 10.1 Berlin).
I came across the same problem and after reading through a lot of source, I found no practical solution.
So an exemplary (my) StackTrace would look like this:
MyClass::MyServerMethod()
/* skipping some funny unimportant RTTI/TValue handling here */
System::Rtti::TRttiMethod::Invoke
Dsreflect::TDSMethod::Invoke(TObject, TDSMethodValues)
TDSServerConnectionHandler::DbxExecute(const TDBXExecuteMessage)
TDSServerCommand::DerivedExecuteUpdate
TExecuteCallback
TDSService::Execute(const string, const TRequestCommandHandler, TExecuteCallback)
TDSService::ProcessRequest(const string, const TRequestCommandHandler, TExecuteCallback)
TDSRESTService::ProcessREST(const string, const string, const TArray<Byte>, const TRequestCommandHandler)
TDSRESTService::ProcessGETRequest(const string, TStrings, TArray<Byte>, TRequestCommandHandler)
TDSRESTServer::DoDSRESTCommand(TDSHTTPRequest, TDSHTTPResponse, string)
TDSRESTServer::DoCommand(TDSHTTPContext, TDSHTTPRequest, TDSHTTPResponse)
Dshttpwebbroker::TDSRESTWebDispatcher::DispatchRequest(TObject, Web::Httpapp::TWebRequest, Web::Httpapp::TWebResponse)
Note: This depends entirely on your usage of DataSnap. In the above case requests are passed into the DataSnap API through TDSRESTWebDispatcher (comming from TIdCustomHTTPServer).
Every Exception raised in a ServerMethod will end up in TDSService::ProcessRequest.
In this procedure every Exception is caught and ONLY its Message is added to a TRequestCommandHandler->CommandList.
Further down the Message is written as JSON/DBX command to the output.
So we can never handle the Exception Object and access the StackTrace or other information. So this alone is unacceptable and has to change
The good news is, that this procedure is virtual and can be overwritten. The bad news is, that in the example above you would have to extend TDSRESTService with your own ProcessRequest procedure (including your errorhandler), TDSRESTServer with own DoDSRESTCommand (in there the TDSRESTService is created in a monstrously large procedure) and TDSRESTWebDispatcher (depending on your usage).
My personal recommendation is to don't use DataSnap.
Note: At the point of writing this, I haven't found any invoke of the OnError event.
I am developing this Delhi 2009 app with SQLite database version 3.7.9, accessed by the Zeos components. Started off OK - I can create a DB if none exists, create tables and insert records - the 'normal stuff'. There is a little table that contains serial params for a connection to some old equipments and the serial comms handler needs to be informed of connection parameter changes, but the Zeos components do not support events on SQLite.
I looked at the SQLite API and there is a 'Data change notification callback', so I set about making this happen. The setup prototype is:
void *sqlite3_update_hook(
sqlite3*,
void(*)(void *,int ,char const *,char const *,sqlite3_int64),
void*
);
So, in Delphi, I imported the DLL call:
function sqlite3_update_hook(pDB:pointer;pCallback:pointer;aux:pointer): pointer; stdcall;
..
function sqlite3_update_hook; external 'sqlite3.dll' name 'sqlite3_update_hook';
..and declared an empty callback for testing:
function DBcallback(aux:pointer;CBtype:integer;CBdatabaseName:PChar;CBtableName:PChar;CBrowID:Int64):pointer; stdcall;
begin
end;
..and called the setup:
sqlite3_update_hook(SQLiteHandle,#DBcallback,dmOilmon);
SQLiteHandle is the sqlite3* pointer for the database as retrieved from the driver after connection. dmOilmon is the DataModule instance so that, when working correctly, I can call some method from the callback, (yes, I know callback is in an unknown thread - I can deal with that OK, I'll just be signaling semaphores).
Good news: when I ran the app, the DBcallback was called, aparrently successfully, upon the first insert. Bad news: some little time later, the app blew up with 'Too many exceptions', and usually a CPU window full of '??', (the occasional alternative was system death and a reboot - Vista Ultimate 64). Breaking in the callback, aux, CBtype and CBrowID were all as expected but the debugger tooltip showed the CBdatabaseName / CBtableName PChars to be pointing at a string of Chinese characters..
I tried to trace where the callback is called from - the call chain passes through in the Zeos driver code, for some reason. Breaking there and stepping through, I checked the stack pointer and it's the same before and after the callback.
So, I set up the 'empty' callback, the callback gets called at the expected point but with a coupe of dodgy-looking parameters, the callback returns to whence it came with the SP correct. I seem to have done everything right, but... :((
Has anyone seen this, (or fixed it even:)?
Can you suggest a mechanism whereby an aparrently successful callback can pesuade an app to later generate recursive exceptions?
Does anyone have any suggestions for further debugging?
Rgds,
Martin
You have the problems with correct translation of SQLite header to Delphi and with linking:
Not stdcall, but must be cdecl.
Your function DBcallback must be a procedure. Because void(*)(...) is a pointer to C function returning void.
Not PChar, but must be PAnsiChar. For non-Unicode Delphi's it may be not important, for Unicode Delphi's, it is only a correct translation.
You are using compile-time dynamic linking (external 'sqlite3.dll' ...) to sqlite3.dll. But ZeosLib uses run-time dynamic linking (LoadLibrary & GetProcAddress). That may lead to "DLL hell" issue, when your EXE loads one sqlite3.dll, and ZeosLib plans to load another sqlite3.dll.
FATAL ERROR: CALL_AND_RETRY_2 Allocation Failed - process out of memory
I'm seeing this error and not quite sure where it's coming from. The project I'm working on has this basic workflow:
Receive XML post from another source
Parse the XML using xml2js
Extract the required information from the newly created JSON object and create a new object.
Send that object to connected clients (using socket.io)
Node Modules in use are:
xml2js
socket.io
choreographer
mysql
When I receive an XML packet the first thing I do is write it to a log.txt file in the event that something needs to be reviewed later. I first fs.readFile to get the current contents, then write the new contents + the old. The log.txt file was probably around 2400KB around last crash, but upon restarting the server it's working fine again so I don't believe this to be the issue.
I don't see a packet in the log right before the crash happened, so I'm not sure what's causing the crash... No new clients connected, no messages were being sent... nothing was being parsed.
Edit
Seeing as node is running constantly should I be using delete <object> after every object I'm using serves its purpose, such as var now = new Date() which I use to compare to things that happen in the past. Or, result object from step 3 after I've passed it to the callback?
Edit 2
I am keeping a master object in the event that a new client connects, they need to see past messages, objects are deleted though, they don't stay for the life of the server, just until their completed on client side. Currently, I'm doing something like this
function parsingFunction(callback) {
//Construct Object
callback(theConstructedObject);
}
parsingFunction(function (data) {
masterObject[someIdentifier] = data;
});
Edit 3
As another step for troubleshooting I dumped the process.memoryUsage().heapUsed right before the parser starts at the parser.on('end', function() {..}); and parsed several xml packets. The highest heap used was around 10-12 MB throughout the test, although during normal conditions the program rests at about 4-5 MB. I don't think this is particularly a deal breaker, but may help in finding the issue.
Perhaps you are accidentally closing on objects recursively. A crazy example:
function f() {
var shouldBeDeleted = function(x) { return x }
return function g() { return shouldBeDeleted(shouldBeDeleted) }
}
To find what is happening fire up node-inspector and set a break point just before the suspected out of memory error. Then click on "Closure" (below Scope Variables near the right border). Perhaps if you click around something will click and you realize what happens.
I am using the following code to copy text to the clipboard:
Clipboard.Open;
try
Clipboard.AsText := GenerateClipboardText;
finally
Clipboard.Close;
end;
Seemingly at random I get "Cannot open clipboard: Access Denied" errors. I'm guessing that these errors are caused by other application locking the clipboard, but I never seem to be doing anything with other applications that should cause the locks.
Strangely my users seem to be reporting more of the errors with Vista and Windows 7 than with XP.
Is there a way to check if the clipboard is locked before trying to access it?
This is not a Delphi problem. Because the clipboard can be locked any moment, even if you check, if the clipboard is currently not locked, it might become locked directly after the check.
You have two possibilities here:
Don't use the Delphi clipboard class. Instead use raw API functions, where you have a little more fine-grained control over possible error situations.
Expect your code to fail by adding an exception handler. Then add some retry code, i.e. retry to set the text three times, perhaps with exponential backoff, before throwing your own error.
I'd recommend the second solution, because it'd be the more Delphi-like approach and in the end will result in cleaner code.
var
Success : boolean;
RetryCount : integer;
begin
RetryCount := 0;
Success := false;
while not Success do
try
//
// Set the clipboard here
//
Success := True;
except
on E: EClipboardException do
begin
Inc(RetryCount);
if RetryCount < 3 then
Sleep(RetryCount * 100)
else
raise Exception.Create('Cannot set clipboard after three attempts');
end else
raise; // if not a clipboard problem then re-raise
end;
end;
Strangely my users seem to be
reporting more of the errors with
Vista and Windows 7 than with XP
This may have to do with how Vista/Win7 deal with clipboard viewer notification. While they still support the XP "clipboard viewer chain", which sends one notification message that must be re-sent to each listener in turn (and if one app fails to do this, the other apps aren't notified). Starting with Vista, apps are notified directly. And there's nothing to keep them from trying to access the clipboard all at once.
Analogy: I have 3 children. I have a cake. With XP rules, I tell the oldest child to have some cake, then tell the next oldest child to have a slice. She gets her slice, tells her brother, he gets his, and tells his brother, who gets his, and everything proceeds in an orderly fashion.
Problem: The middle child takes the cake to his room, doesn't tell the youngest, and the youngest misses out.
With Vista/Windows7, that system still exists. But newer apps can request to be notified immediately, by me, as soon as the cake arrives in the kitchen. I shout "cake is ready!" and they all show up at the same time and try to grab some. But there's only one serving knife, so they have to keep reaching for the knife, failing to get it, and waiting for the next opportunity.
Try to check GetClipboardOwner, if it's not null and not your Application.Handle, you cannot Open to modify it's content.
And even it seems good to go, it might not be anymore when you actually do it.
So add a try except in a loop until you get it or give up nicely (notifying the user for instance).
There is no way to check for something and then depending on the result do something else with the expectation that it could not fail, because unless the check and the action are one atomic operation there is always the possibility that another process or thread does the same in parallel.
This holds whether you try to open the clipboard, open a file, create or delete a directory - you should simply try to do it, maybe several times in a loop, and gracefully handle errors.
First of all please notice that this probably isn't a problem in your application. Other applications locked the clipboard or messed up the notification chain and now your application fails to access it. When I do have problems like this I restart the computer and they magically go away... well... at least until I run again the application that creates the problem.
This code (not checked in Delphi) may help you. It won't fix the problem is the notification chain is broken (nothing except a PC restart will ever fix it) but it will fix the problem if an application is locking the clipboard for a while. Increase the MaxRetries if that pesky application keeps the clipboard locked for A REALLY LONG time (seconds):
procedure Str2Clipboard(CONST Str: string; iDelayMs: integer);
CONST
MaxRetries= 5;
VAR RetryCount: Integer;
begin
RetryCount:= 0;
for RetryCount:= 1 to MaxRetries DO
TRY
inc(RetryCount);
Clipboard.AsText:= Str;
Break;
EXCEPT
on Exception DO
if RetryCount = MaxRetries
then RAISE Exception.Create('Cannot set clipboard')
else Sleep(iDelayMs)
END;
end;
Also, it may be a good idea to drop the 'raise' and convert it to a function and use it like this:
if not Str2Clipboard
then Log.AddMsg('Dear user, other applications are blocking the clipboard. We have tried. We really did. But it didn''t work. Try again in a few seconds.');
I guess you are running your app on Win 8 or higher.
Just right-click on your App .exe file, go to Compatibility tab and change compatibility mode on Windows XP or lower versions. It'll work, guaranteed!