Freigeben über


Windows Vista C++ application is unable to print out documents

             

Hi there,

I know some of you have been waiting for a while before I made my first technical post. I’ve been busy helping you guys solving development issues, isn’t that a good justification? J

Today, I’m going to give you an example of a typical scenario application’s developer faces with when a new version of Windows is released and we discover that our application that was working perfectly fine on previous version now has a completely different behavior.

When that happens, we usually think that because our code was working fine before it means that something is going wrong with this new version of Windows. That can be true but experience shows that most of the time, we’ve to reconsider our assumption. To insure application backward compatibility, this is very unlikely that an API with the exact same name and parameter has his behavior changed from a version of Windows to another.

I had a customer who was facing the following issue:

PROBLEM:

He has written a native C++ application to print out his documents. The program has its own feature to connect a printer on network and printing out the documents. The application is working fine on Windows XP for years, but it seems that it is not compatible with Windows Vista. He’s getting an "invalid handle" error.

EXPECTATIONS / AGREED RESOLUTION:

What is happening? How to get rid of this issue?

ENVIRONMENT:

Windows Vista

Shared printed accessible from any machines from local area network

SUMMARY OF TROUBLESHOOTING:

In such circumstances the first thing we need to find out is what API’s call generates the error.

This can be done either by tracing the application with a debugger or by taking a process memory dump when the issue occurs. I’ll explain in a later post both of these different approaches.

The error was returned by calling the CreateFile API (https://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx ).

C++

HANDLE WINAPI CreateFile(

  __in LPCTSTR lpFileName,

  __in DWORD dwDesiredAccess,

  __in DWORD dwShareMode,

  __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,

  __in DWORD dwCreationDisposition,

  __in DWORD dwFlagsAndAttributes,

  __in_opt HANDLE hTemplateFile

);

 

This function creates or opens a file or I/O device and returns a handle that can be used to access the file or device for various types of I/O depending on the file or device and the flags and attributes specified.

The game is now to understand why the call fails on Windows Vista returning VALID_HANDLE_VALUE.

Reading the documentation we should have a look at the “return value” section.

It says the following:

Return Value :

If the function succeeds, the return value is an open handle to the specified file, device, named pipe, or mail slot.

If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError() .

We could see that the ERROR_FILE_NOT_FOUND was returned by GetLastError() . This makes us think that for any reason, the API is not able to access the target file. At this step we can try to find in the documentation any hints that may lead us to any root cause, for instance any implementation changes or restriction coming with Windows Vista. Any reference like this one: https://support.microsoft.com/kb/942448/en-us (Changes to the file system and to the storage stack to restrict direct disk access and direct volume access in Windows Vista and in Windows Server 2008).

The ultimate path to follow is to read all the information provided by the documentation regarding ERROR_FILE_NOT_FOUND. We are lucky more details regarding this error can be found in the documentation in the “Opening LPT Ports” section. It’s said that specifying OPEN_EXISTING as dwCreationDisposition parameter will cause the function to fail when called on Windows Vista or Windows 2008 server to open an LPT port that is mapped to a network share. GetLastError() returns ERROR_FILE_NOT_FOUND.

The customer’s application was calling the API with the following parameters:

C++

CreateFile(“\\MyServer\MyPrinter” , FILE_FLAG_OVERLAPPED, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);

 

At this step we can notice that this is actually a known issue. To avoid this you should use the OPEN_ALWAYS flag instead that will force the driver to start the device so that the WriteFile interface is available. The code was modified that way:

C++

CreateFile(“\\MyServer\MyPrinter” , FILE_FLAG_OVERLAPPED, FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0);

 

 With this solution the application was able to run successfully on both version of the operating system.

To put it in a nutshell, when developing an application the first thing you should do is to read carefully the documentation and use the appropriate parameters. Most of the time this will prevent you for facing to this kind of issues. If any system change is susceptible to have an impact on an API, the documentation may mention it. If this is not the case, please don’t hesitate to raise the issue on the article in the appropriate section.

I hope that you found this post useful.

Cheers,

Bonjour à tous,

Oui c’est vrai, j’ai un peu tardé à écrire mon premier post technique, mais j’ai une bonne excuse, j’étais occupé à aider certains d’entre vous à résoudre leurs problèmes de développement J

Aujourd’hui je vais vous décrire un scenario typique que les développeurs d’application rencontrent lors d’une migration d’application pour la dernière mouture du système d’exploitation. Comment une application qui fonctionne parfaitement sur une version de Windows donnée se comporte-t-elle de manière différente sur son successeur ?

De manière générale quand ce type de situations se présente, le premier réflexe est de penser que quelque chose ne va pas avec cette nouvelle mouture de l’OS. C’est une assertion possible, mais dans la plupart des cas il faut savoir remettre en question cette hypothèse. Afin d’assurer la rétrocompatibilité des applications, il est vraiment peu probable qu’une API appelée avec les mêmes paramètres ait un comportement différent d’une version de l’OS à l’autre. Ce n’est cependant pas une hypothèse à exclure.

Un développeur a été confronté à la situation suivante :

PROBLEME :

Il développe une application native en C++ qui imprime des documents. L’application gère d’elle-même la connexion à l’imprimante réseau et y envoie les documents à imprimer. L’application fonctionne correctement depuis plusieurs années sous Windows XP, mais il semblerait qu’elle ne soit pas compatible avec Windows Vista. Il obtient le message d’erreur suivant quand il tente d’imprimer un document : "invalid handle".

ATTENTES DU DEVELOPPEUR :

Savoir ce qui pose problème et comment faire en sorte que son application marche aussi bien sous Windows XP que Windows Vista.

ENVIRONMENT:

Windows Vista

Imprimante partagée et accessible par toutes les machines du réseau local

RESUME :

Dans de telles circonstances la première chose à faire est d’identifier l’appel à la fonction qui soulève le problème.

Cela se fait généralement en traçant l’exécution du code avec débogueur ou alors par l’analyse d’une capture mémoire complète de la du processus. Je donnerai des exemples de ces deux approches dans un prochain article.

Dans ce cas précis, comme le problème a pu être facilement reproduit dans une application d’exemple minimaliste, il a été possible d’identifier que l’appel à la fonction suivante posait problème :

API CreateFile (https://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx ).

C++

HANDLE WINAPI CreateFile(

  __in LPCTSTR lpFileName,

  __in DWORD dwDesiredAccess,

  __in DWORD dwShareMode,

  __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,

Cette fonction crée puis ouvre un fichier ou Entrée/Sortie de périphérique et en retourne un handle qui peut être utilisé pour accéder au fichier ou périphérique en question selon différents types d’E/S en fonction des attributs spécifiés.

Le but du jeu est maintenant de comprendre pourquoi l’appel à cette fonction échoue et renvoie la valeur : INVALID_HANDLE_VALUE.

En lisant la documentation de l’API dans la section “valeur de retour” nous pouvons lire les informations suivantes :

Valeur de retour :

Si la fonction réussit, la valeur de retour est un handle ouvert au fichier, périphérique, named pipe (canal nommé) ou mailslot spécifié.

Si la fonction échoue, la valeur de retour est INVALID_HANDLE_VALUE. Afin d’obtenir plus d’information sur l’erreur faire un appel à GetLastError() .

L’erreur ERROR_FILE_NOT_FOUND a été retournée par GetLastError() . Cela laisse à penser que pour une raison encore inconnue, l’API n’a pas pu accéder à ressource cible. A ce stade nous pouvons chercher des indications dans la documentation qui pourrait expliquer ce comportement. Des indices comme des changements d’implémentation ou de politique de sécurité et restriction sont à surveiller de près.

La référence suivante : Modifications dans le système de fichiers et dans la pile de stockage pour limiter les accès disque direct et des accès directs volume dans Windows Vista et dans Windows Server 2008 ( https://support.microsoft.com/kb/942448/en-us ) est typiquement le genre d’éléments qui peut avoir une impacte sur le comportement d’une API.

La dernière piste à suivre est de lire en détails les informations fournies dans la documentation MSDN de l’API au sujet de l’erreur ERROR_FILE_NOT_FOUND. Dans ce cas précis nous avons dans la section “Opening LPT Ports” une référence intéressante. Il est expliqué que l’utilisation de l’API qui nous intéresse en spécifiant le paramètre dwCreationDisposition avec pour valeur OPEN_EXISTING aura pour effet de faire échouer l’appel sous Windows Vista et Windows Server 2008 lors des tentatives d’ouverture de port de type LPT mappé sur un partage réseau. GetLastError() retourne dans ce cas ERROR_FILE_NOT_FOUND.

Le code source qui reproduit le problème appel l’API de la manière suivante :

C++

CreateFile(“\\MyServer\MyPrinter” , FILE_FLAG_OVERLAPPED, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);

Nous sommes exactement dans le cas de figure décrit par la documentation. Afin d’éviter que cela se produise, il faudrait spécifier la valeur OPEN_ALWAYS pour le paramètre dwCreationDisposition ce qui aura pour effet de forcer le pilote d’impression à démarrer le périphérique afin que l’interface WriteFile utilisée par l’API CreateFile soit disponible. Le code doit être modifié de la manière suivante :

C++

CreateFile(“\\MyServer\MyPrinter” , FILE_FLAG_OVERLAPPED, FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0);

Avec cette solution, l’application est maintenant capable de s’exécuter correctement sous sur les différentes versions du système d’exploitation Windows.

Pour résumer, il est vraiment fondamental dans le cadre d’un développement ou d’un débogage d’application de se fier à la documentation et d’utiliser les paramètres appropriés en fonction du contexte d’appel aux différentes APIs. De manière générale, toute impact potentiel d’un changement au niveau du système est signalé dans la documentation de l’API. Si tel n’est pas le cas il ne faut pas hésiter à le signaler au niveau de l’article MSDN dans la section appropriée.

Voilà, j’espère que cet exemple vous a été utile et vous permettra d’anticiper ou résoudre les problèmes de ce type rencontrés lors d’une migration.

--Michel