8

我编写了一个 Delphi 程序,该程序从单个 .XLS 文件的多个不同电子表格中提取数据并将其合并到一个文本文件中以供以后处理。它是一个 Delphi 7控制台程序。

一段最相关的代码片段将向您展示,显然,我的程序表现得非常好,或者至少与它需要的一样多。

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.

有时,当程序退出时,XLS 仍处于锁定状态。查看任务管理器,我看到客户端程序运行时启动的 Excel.exe 进程仍在运行,即使客户端程序已退出并成功卸载。

您是否知道这种行为的常见嫌疑人是什么?知道在客户端执行时在哪里寻找总是卸载 excel 吗?

4

3 回答 3

6

您需要发布ExcelApp变体。它的引用计数仍然为 1,因此 Excel 并未完全关闭。

将此添加到您的代码中(标记的行):

finally
  ExcelApp.Quit;
  ExcelApp := Unassigned;        // Add this line
  coUninitialize;
end;  

这是一些重现问题并测试解决方案的简单代码:

// 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.
于 2012-09-06T16:42:44.720 回答
4

我在 XE2 中遇到了同样的问题,我的解决方案是替换这样的代码示例:

fExcel.ActiveWorkBook.ActiveSheet.Range[
    fExcel.ActiveWorkBook.ActiveSheet.Cells[3, 2],
    fExcel.ActiveWorkBook.ActiveSheet.Cells[3+i,1+XL_PT_Tip_FieldCount]
].Formula := VarArr;

和:

cl := fExcel.ActiveWorkBook.ActiveSheet.Cells[3, 2];
ch := fExcel.ActiveWorkBook.ActiveSheet.Cells[3+i,1+XL_PT_Tip_FieldCount];
fExcel.ActiveWorkBook.ActiveSheet.Range[cl, ch].Formula := VarArr;

在这种情况下也会发生同样的情况,其中使用了工作表变量:

sheetDynamicHb := fExcel.ActiveWorkBook.Sheets['Dynamics Hb'];
cl := sheetDynamicHb.Cells[52, 2];
ch := sheetDynamicHb.Cells[52+i, 2+3];
sheetDynamicHb.Range[cl, ch].Formula := VarArr;

不知何故,引入临时变量 ( cl,ch: Variant) 可以解决问题。似乎嵌套的 Excel 变量访问做了一些奇怪的事情。我无法解释为什么会这样,但它确实有效..

于 2015-01-19T13:25:01.137 回答
3

我在尝试关闭“僵尸”Excel 进程时遇到了同样的问题(如果我从我的应用程序启动它们然后强制终止应用程序,这些进程会继续运行)。我尝试了所有建议的操作,但没有运气。最后,我创建了一个组合杀手程序,如果通常的 COM 方法没有帮助,它可以使用 WinApi 稳健地完成这个技巧。

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;
于 2015-10-23T13:25:05.450 回答