J'ai écrit un programme Delphi qui extrait et consolide des données de plusieurs feuilles de calcul différents d'un seul fichier .xls, à un fichier texte pour le traitement ultérieur. C'est un programme Delphi 7 Console Strong>. Un extrait des pièces de code les plus pertinentes vous montrera que, apparemment, mon programme est assez bien comporté ou au moins autant qu'il doit être . P> uses ... ActiveX, ComObj ... ;
procedure Fatal(s:string);
...
Halt(1);
var ExcelApp:Variant; (* global var *)
begin (* main program block *)
coInitialize(nil);
ExcelApp:=CreateOleObject('Excel.Application');
try
ExcelApp.Visible:=False;
ExcelApp.WorkBooks.Open(ExcelFileName);
...
XLSSheet := ExcelApp.Worksheets[ExcelSheetName];
...
try
XLSRange := XLSSheet.Range[ExcelRangeName];
except
Fatal('Range "'+ExcelRangeName+'" not found');
end;
if VarIsNull(XLSRange) then Fatal('Range '+ExcelRangeName+' not found');
for row:=XLSRange.Row to XLSRange.Rows[XLSRange.Rows.Count].Row do
for col:=XLSRange.Column to XLSRange.Columns[XLSRange.Columns.Count].Column do
CellValue:=XLSSheet.Cells[Row,Col].Value;
...
if CellValue<>'' then ...
...
ExcelApp.Workbooks.Close;
...
finally
ExcelApp.Quit;
coUninitialize;
end;
end.
3 Réponses :
Vous devez libérer la variante Ajoutez ceci à votre code (la ligne marquée): p> excélépp code>. Il tient toujours un nombre de référence de 1, et donc Excel n'est pas complètement fermé.
// Add two buttons to a form, and declare a private form field.
// Add OnClick handlers to the two buttons, and use the code provided.
// Run the app, and click Button1. Wait until Excel is shown, and then click
// Button2 to close it. See the comments in the Button2Click event handler.
type
TForm1=class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
ExcelApp: Variant;
end;
implementation
uses
ComObj;
procedure TForm1.Button1Click(Sender: TObject);
begin
ExcelApp := CreateOleObject('Excel.Application');
ExcelApp.Visible := True;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
ExcelApp.Visible := False;
ExcelApp.Quit;
// Leave the next line commented, run the app, and click the button.
// After exiting your app NORMALLY, check Task Manager processes, and you'll
// see an instance of Excel.exe still running, even though it's not
// in the Applications tab.
//
// Do an "end process" in Task Manager to remove the orphaned instance
// of Excel.exe left from the above. Uncomment the next line of code
// and repeat the process, again closing your app normally after clicking
// Button2. You'll note that Excel.exe is no longer in
// Task Manager Processes after closing your app.
// ExcelApp := Unassigned;
end;
end.
@David: Ce sera. J'ai rencontré ce problème précis dans le passé; Ce n'est pas juste une supposition. Vous pouvez toujours l'essayer, cependant; L'affiche a fourni suffisamment de code pour répliquer le problème et essayer ma solution. :-)
J'ai toujours pensé que le processus Excel est mort à l'appel à Quitter code>. N'est-ce pas le cas?
Non :-) C'est un problème particulièrement méchant si votre application se bloque pour une raison quelconque; Lorsque vous essayez de l'exécuter à nouveau, vous obtenez des erreurs qui semblent être hors de propos, jusqu'à ce que vous vérifiez le gestionnaire de tâches et trouvez Excel.exe dans l'onglet 'Processes' (pas d'applications!) et effectuez un processus de fin. Les erreurs s'arrêtent alors. :-) Comme je l'ai dit, je ne devrais pas deviner.
Eh bien, si vous êtes correct, alors le problème doit être de savoir si la référence est publiée ou non avant ou après l'appel sur CounInitialize code>. Parce que la référence sera libérée. Vous êtes juste en train de contrôler quand.
@David, éventuellement. Sauf lorsque votre application s'est déjà écrasée, clôturée totalement, et Excel reste orphelinée dans l'onglet TMC Processes, il ne se dégage pas (sinon, comment serait-il là-bas?).
Excélépp: = non attribué; code> libérera la référence ou lorsque Excélépp est hors de portée. Nous ne pouvons pas voir la portée d'Excélépp ici, donc je suppose que la définition à la non appelée fera le travail.
@Lurd: Vous avez raison, bien sûr. nil code> travaillé dans les versions antérieures de Delphi, avant que les variantes ne soient déplacées dans sa propre unité (D6?).
non attribué code> est la manière appropriée. Corrigé, et merci. :-)
Mes expériences vous soutiennent dans une certaine mesure. Excélépp: = non attribué code> ne tue pas le processus Excel. Toutefois, en sortant de la procédure qui a déclaré la variable
ExcelApp code> le truc. Mais pour faire ce travail, vous avez besoin du
Coinitialiser code> /
CounInitialize code> déplacé en dehors de la procédure qui déclare
ExcelApp code> variable.
@David: Code de test complet affiché pour que vous puissiez produire le problème et tester la solution fournie. De plus, comme je l'ai dit dans mon commentaire à la question elle-même, la coïnitialisation / la veinitialisation doit être supprimée entièrement, comme elle s'appelle automatiquement dans Comobj code>.
Eh bien, votre programme ne se comporte pas de la manière dont le commentaire dit que lorsque je l'exécute. Je ne peux pas non plus voir où dans le comobj le code appelle Coinitialize code>. Peu importe.
@David: comobj code> a une section code> initialisation code> qui attribue
@initcomobj code> à
initproc code>, appelé par le RTL à initialisation de l'unité.
initcoCOBJ code> (juste au-dessus de la section d'initialisation) appelle le précédent
initproc code>, puis
Coinitializeex code> ou
Coinitialize code> et définit un booléen
NeedStounitialize Code> Drapeau. La section suivante code> appelle la section
Coinitialisation code> Si le drapeau
NeedTounitialize code> a été défini. (Basé sur la version D2007 de COMOBJ-XE2 fonctionne la même chose, bien qu'il y ait ajouté du code entre
initcoCOBJ code> de la définition et de la
initialisation code> mot-clé.)
Oui je vois. Étrangement sur mon Xe2, je ne peux pas obtenir le débogueur pour s'arrêter sur InitCompobj.
Je comprends ce qui se passe maintenant. Votre exemple est faux. Vous tuez le processus avant d'avoir une chance de nettoyer ExcelApp code>. Bien sûr que quitte Excel en cours d'exécution. Mais il n'y a pas beaucoup de choses qui peuvent être faites à ce sujet. Dans le code de la question, supposons que le code soit contenu dans une procédure. Lorsque cette procédure se termine, alors
Excelapp code> est nettoyé, puis Excel s'en va. Ajout de la mission à
non attribué code> ne fait rien parce que le compilateur introduit une variable gérée implicite cachée qui fait référence à l'objet COM.
Ce que je veux dire par cette dernière déclaration est ce qui est discuté ici: Stackoverflow.com/Questtions/7759081/...
@David: "faux?" Non, ce n'est pas le cas, et votre phrasé est un peu impoli. Je ferme la demande normalement, après avoir cliqué sur un bouton. De quelle manière est-ce "tuer le processus"? Il passe par la messagerie d'application normale qui cliquant sur le «X» dans le coin supérieur droit passe. Ajout de l'affectation à non assigné code> fait disparaître le problème. Je n'ai jamais dit que je reproduisais le code exacte b> utilisé dans la question initiale; J'ai produit un exemple de même comportement B>.
@David: ah, je vois. Vous n'avez pas lu les commentaires dans Bouton2Click Code> correctement et mal interprété ce que je disais dans les instructions. Ajout de plus de détails pour améliorer la compréhension (espérons-le).
Oui, "Bogus" est inflammatoire. Désolé. Mais votre réponse n'est toujours pas utile ni exacte. Je compile dans XE2. Avec cette ligne commentée, cliquez sur Button1, le processus Excel Démarre, cliquez sur Button2, Fermer l'application, des arrêts de processus Excel. Avec la ligne, cliquez sur Button1, le processus Excel démarre, cliquez sur Button2, Excel Process Stops. Et c'est exactement ce que vous attendez. Vous voyez, avec la ligne commentée, Excélépp sort de la portée à mesure que le programme se ferme. Puis Excel ferme. C'est comme ça que ça devrait marcher. Si vous voyez quelque chose de différent, quelque chose brisé sur votre système.
L'essentiel est que lorsque la dernière référence à l'objet COM dépasse hors de portée, le processus Excel se termine. Maintenant, vous battez sur ce bit. Et je parlais de pipes quand je pensais quitter code> terminerait processus. Mais il n'y a rien dans la question qui conduirait à penser que le comptage de référence sur l'objet COM est mal géré. À un moment donné, le
Excelapp code> dans le Q sera nettoyé et Excel sera terminé. Voyez-vous d'où viens? Désolé de parler si longtemps pour m'exprimer.
@David: Cela ne fonctionne pas de cette façon à Delphi 2007 et j'ai testé le code avant de la publier, et c'est une installation relativement nouvelle de Win7 et D2007. Je peux le reproduire à plusieurs reprises et je viens de copier l'exécutable à une machine qui n'a pas de Delphi installé mais a Office 2007 (comme le mien) et peut le répéter là-bas. Peut-être un changement de xe2? En ce qui concerne ma réponse ne pas être utile, je suppose que nous devrons attendre que l'affiche détermine cela; Il n'y a aucune indication de quelle version de Delphi ou Excel est utilisée, alors peut-être que la question que j'ai présente existe également.
Oui, je suis sûr que vous pouvez le reproduire. Mais êtes-vous d'accord avec mon analyse? À savoir que vous n'avez pas besoin de définir explicitement des types gérés vers nil code> ou
non attribué code>, et vous pouvez simplement attendre que la dernière référence soit hors de portée. Dans ce cas n'est pas D2007 souffrant d'un bug? Bien sûr, si OP utilise la version buggée, votre réponse serait la solution!
Oui, c'est généralement le cas. C'est pourquoi j'ai été surpris lorsque la question avec Excel est d'abord surgi (mon premier commentaire à vous dans cette réponse). Le fait que je puisse reproduire à plusieurs reprises signifie que quelque chose ne va pas dans Excel ou à Delphi (peut être un problème d'excellence - voir la réponse supprimée de Lloyd) qui vissait le comptage de référence et laissant des instances orphelinées en cours d'exécution. (Je peux créer plusieurs instances orphelines en exécutant l'application plusieurs fois avec la ligne code> non attribue code> commentée et sortant chaque exécution normalement. Chacun laisse une nouvelle instance d'Excel derrière.)
@Ken, je viens de regarder dans l'une de mes applications D2007 (en cours d'exécution de 24h / jour générant des rapports Excel 100% de disponibilité). Tous les Excélépps sont déclarés localement et toutes finissent par cesser de fumer et non assigné. Si ma mémoire est correcte, c'était le seul moyen de fermer l'excellence correctement. Cela aurait pu pendre depuis, mais votre réponse se tient.
@Lurd: merci. Je ne me souviens pas de quelle version de Delphi je suis d'abord couru dans cela avec; Je ne fais plus grand nombre d'automatisation Excel, car nous utilisons XLSreadWrite et je viens de lire ou d'écrire directement dans le fichier sans excel d'être impliqué. :-)
Nous utilisons également xlsreadwrite également, mais pour l'impression de feuilles, nous avons dû aller à l'automatisation (au moins c'était le cas lorsque le logiciel a été écrit).
Il serait bien sûr de comprendre cela. Parce que si c'est ce que vous devez faire, quelque chose est très brisé.
@David: C'est un problème connu. Un membre de l'équipe B, Deborah Pate, qui était l'un des premiers "experts * références pour l'automatisation des offices, le mentionne même sur ses pages Web , qui ont été mises à jour sur Delphi 5. (voir la note à la fin de la section "Démarrer Excel (reliure tardive)" environ un quart de la route Page, quelques fois dans le sujet "Excel de fermeture" juste en dessous de cela, etc.) C'est un problème de longue date. (Je pense que les pages de Deborah ont fourni la solution lorsque je l'ai d'abord couru.)
Cela omet d'expliquer pourquoi la résiliation du programme ne prendra pas les Vars à l'excès de portée. J'ai toujours une forte aversion pour votre réponse. Nulle partez-vous que vous donnez une justification de ce que vous prétendez. Et nulle partez-vous que vous indiquez qu'il existe un bogue Delphi relatif au nettoyage de référence au moment de la résiliation du programme.
@David, avez-vous même pris la peine de regarder le site de Deborah que j'ai lié? Elle a mentionné le problème dans Delphi 5 b> et mentionne-le sur
@David, et les trois d'entre nous ont tous constaté que définir les variantes références à non assignées avant de quitter le problème. Je suis désolé que vous n'aimez pas cela (désolé - fortement n'aimez pas i>), mais ce n'est pas comme si je le faisais. C'est une question documentée b>; Si vous vérifiez, vous pouvez probablement le trouver à QC. (Et je ne vérifie pas, car dans la mesure où je suis confirmé que cela a été confirmé; comme je l'ai dit, je l'ai personnellement vécu, et deux autres ont dit avoir aussi.)
Oui j'ai lu cette page. Il dit que vous devez effacer toutes les références à l'objet COM. Mais vous n'avez pas besoin explicitement pour effacer les types gérés pour le faire. Cela se produit automatiquement lorsqu'une variable dépasse. Allons de retour à la base. Êtes-vous d'accord avec l'affirmation selon laquelle les types gérés sont autorisés automatiquement lorsqu'ils sortent de la portée?
@David, non. L'article dit explicitement que vous devez définir les références COM sur non assignées b> et que le code affiché de l'article montre clairement cela. Je conviens que les types gérés doivent être échangés automatiquement b>; J'ai fait ça 10/12 comments il y a. Mais dans ce cas, Ils ne sont pas b> étant effacés. Il est documenté qu'ils ne le font pas et que des efforts supplémentaires doivent être déployés pour le faire. Comme il apparaît uniquement pour affecter Excel et Outlook (mais aucune référence n'est rendue sur le site de Pate à Word), il semble être un problème avec eux spécifiquement. Je ne peux pas expliquer le bug; Je peux fournir la solution de contournement.
Eh bien, je ne suis pas d'accord avec ce que vous dites. Je ne suis pas d'accord avec votre lecture du site de Deborah. Je ne conviens pas que si 3 personnes croient une chose, et une seule personne croit autre chose que les 3 personnes doivent avoir raison. Je ne peux pas obtenir mon Delphi 6 de se comporter comme vous le décrivez. Dans ma D6, votre code dans cette réponse ne se comporte pas comme vous le décrivez. Je pense que nous sommes terminés. Vous croyez une chose. Je ne suis pas convaincu.
@David: Comment pouvez-vous ne pas être d'accord avec "Excel peut être exécuté de manière invisible, même après avoir pensé que vous l'avez libéré, si l'un de vos variables de votre carnet de travail ou de votre feuille de calcul est toujours" en direct "." et "Notez toutefois que Excel se traînera dans la mémoire, fonctionnant de manière invisible, sauf si vous avez publié tous vos variables de travail et de feuille de travail. Débranchez tous les composants, Définissez toutes les variables d'interface sur NIL B> et Définissez toute variante sur B> pour éviter cela. ». (Tiré des pages Excel du site de Deborah Verbatim.) Mais d'accord, j'abandonne. Vous n'êtes pas d'accord avec ce que j'ai écrit, malgré mes preuves et code fournis.
Je ne suis pas d'accord avec votre interprétation de cela. Vous n'avez pas besoin d'expliquer explicitement Excelapp. Le compilateur le fait pour vous.
@David: et définissez toutes les variantes sur non attribuées b> semble assez difficile à mal interpréter et je posterai un capuchon à l'écran lorsque je rentre à la maison ce soir en tant que modification à mon poste de quelques instances Excel orphelines qui prouvent que le compilateur ne (ou que Excel ne se comporte pas correctement de comportement). Mais vous avez évidemment arrêté d'écouter, alors je pense que j'ai fini ici. Si ma réponse est fausse, levoyez-le. Si vous venez d'être en désaccord, vous l'avez dit. De toute façon, acceptons simplement de ne pas être d'accord et de bouger. Je resterai avec la solution de travail si nécessaire dans mon code. :-)
Pourquoi la réponse ne se comporte-t-elle pas comme vous le dites quand je l'exécute? Vous pouvez voir pourquoi je suis sceptique.
Après quelques tests, j'ai accepté la réponse de Ken. Cela corrige mon problème. Au moins pour la combinaison de la version du compilateur, du type d'application et du code. En outre, la réponse explique pourquoi le code ne libérait pas Excel et m'a aidé à déduire la raison pour laquelle cela s'est passé parfois et non d'autres (à travers l'appel de fatal).
@David, comme je l'ai dit, je peux poster une capture d'écran plus tard (ne peut pas le faire d'ici; le proxy ne me laissera pas.) Quelles versions de Delphi et Excel utilisez-vous?
@PENNSYLVANIE. Peut-être devriez-vous arrêter d'utiliser HALT!
Oui, utiliser Halt est le problème ici. Arrêter de faire ça.
Toutes les versions de Delphi, Excel 2007 et Excel 2010
J'ai rencontré beaucoup le même problème dans XE2 et ma solution consistait à remplacer ces échantillons de code: avec: p> La même chose se produit dans ce cas, où la variable de feuille est utilisée: p> d'une manière ou d'une autre, introduisant des variables temporaires ( cl, ch: variante code>) fait le tour. Il semble que l'accès variable d'Excel imbriqué a quelque chose d'étrange. Je ne peux pas expliquer pourquoi cela fonctionne comme ça, mais ça marche .. p> p>
Merci @krom. Votre commentaire ci-dessus était exactement ce dont nous avions besoin lors de la migration de D7 à XE.
J'ai fait face au même problème à essayer de fermer les processus "Zombie" Excel (ceux qui restent en cours d'exécution si je les lance à partir de mon application puis forcé résiliant l'application). J'ai essayé toutes les actions suggérées sans chance. Enfin, j'ai créé une procédure de tueur combinée qui utilise de manière robuste l'affaire à l'aide de Winapi si les méthodes COM d'habitude n'aident pas.
procedure KillExcel(var App: Variant); var ProcID: DWORD; hProc: THandle; hW: HWND; begin hW := App.Application.Hwnd; // close with usual methods App.DisplayAlerts := False; App.Workbooks.Close; App.Quit; App := Unassigned; // close with WinApi if not IsWindow(hW) then Exit; // already closed? GetWindowThreadProcessId(hW, ProcID); hProc := OpenProcess(PROCESS_TERMINATE, False, ProcID); TerminateProcess(hProc, 0); end;
Avez-vous essayé d'ajouter Excelapp.Displayalerts: = False; tôt?
Au fait, vous avez besoin d'un autre essai / enfin bloc. L'appel à la dénincialisation peut être manqué. Je sais que c'est frustrant d'avoir imbriqué d'essayer / enfin, mais c'est ce dont vous avez besoin ici.
@David: En réalité, les appels (coïnitialize et counialisze) doivent être supprimés. COMOBJ les appelle à la fois car il est chargé / déchargé, et comme cela semble être exécuté dans le contexte du fil principal, les appels sont redondants.
@PENNSYLVANIE. Quelle est la portée de
Excelapp code>?
@David, dans le programme de test Barebones toutes les variables sont globales.
@Ken, si je supprimais la coïnitialise, je reçois une exception d'exécution.
Selon Ken, un bogue Delphi se rapporte au nettoyage des variables globales. Mais votre véritable programme n'utilise pas les globaux, non? Quelle version Delphi avez-vous?
Oui, sont globaux. J'ai Delphi 7. C'est compilé en tant que programme de console, pas d'interface graphique. Mais, une chose qui me étonne, c'est que cela ne se produit que de manière erratique, je n'ai pas pu identifier quand il le fait et quand ce n'est pas le cas.
Eh bien, je voudrais reproductibilité. Et je cesserais d'utiliser des globaux et de mettre le code dans une procédure. Cela signifiera que le ranger se produit lorsque l'ExcelApp local maintenant est hors de portée. À la fin de cette procédure.
Si vous supprimez la cocation de l'endroit où vous le montrez dans votre code vous donne une exception, vous devez poster un tas plus de code (et plus de détails que vous n'avez pas fourni en premier lieu, comme le fait que c'est une application de console). Est-ce aussi une application multi-threadée? Si tel est le cas, où le code est-il montrant excacue (fil principal ou secondaire)? Qu'est-ce que
fatal code> fait exactement?
Je pense que vous devez appeler de la coïnitialisation. Je ne vois pas que l'utilisation des résultats de ComoBJ en être appelée. Je fixe un point d'arrêt sur Comobj.initcomobj. Ça ne tire pas. Je fixe un point d'arrêt dans la finalisation de Comobj. Ça incendie, mais il faut avoir besoin de falsifier.
@David - Ça s'appelle à partir de 'tapsplication.initialize' (Comboj.Pas définit l'InitiProc). Ne sera pas appelé pour une application de console.
@SertaCakyuz merci. Cela l'effache! C'est pourquoi pa. doit l'appeler, mais Ken ne le fait pas.
Édité ma question avec quelques informations importantes. C'est une application de console. Excélépp est un Var global. Le code est le bloc principal. FATAL est une procédure qui met fin au programme avec un appel à arrêter. Je crois que ce sont les pièces qui font que le programme présente un tel comportement.