Autor Beitrag
JVS
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 38



BeitragVerfasst: Mi 20.04.16 11:43 
Hallo allerseits,

ich habe einen Beispielcode vom swisscenter adoptiert, um das erste Blatt eines Excelfiles in eine Listview einzulesen.

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
function ExcelFileToLV(AXLSFile: string;lv: TListView;addLineNumbers: boolean): boolean;
const
  xlCellTypeLastCell = $0000000B;
var
  XLApp, MySheet: OLEVariant;
  RangeMatrix: OLEVariant;
  x, y, k, r: Integer;
  rstart: integer;
  nlv: TListItem;
begin
  lv.Visible:=false;
  application.ProcessMessages;
  Result := False;
  lv.Items.Clear;
  lv.Columns.Clear;
  // Create Excel-OLE Object
  XLApp := CreateOleObject('Excel.Application');
  try
    // Hide Excel
    XLApp.Visible := false;

    // Open the Workbook
    XLApp.Workbooks.Open(AXLSFile);
    //Assign Sheet
    MySheet := XLApp.Workbooks[1].WorkSheets[1];

    // Wichtig, damit erstes Blatt ausgewählt wird, sonst funktioniert RangeMatrix nicht !
    MySheet.select;

    // In order to know the dimension of the WorkSheet, i.e the number of rows
    // and the number of columns, we activate the last non-empty cell of it
    MySheet.Cells.SpecialCells(xlCellTypeLastCell, EmptyParam).Activate;
    // Get the value of the last row
    x := XLApp.ActiveCell.Row;
    // Get the value of the last column
    y := XLApp.ActiveCell.Column;

    // Assign the Variant associated with the WorkSheet to the Delphi Variant
    RangeMatrix := XLApp.Range['A1', XLApp.Cells.Item[X, Y]].Value;

    //  Loop for filling Listview
    if addLineNumbers then rstart:=1 else rstart:=2;

    for k:=1 to x do
    begin
      if lv.Columns.Count=0 then lv.Columns.Add;

      if addLineNumbers then
      lv.AddItem(inttostr(k),nil)
      else
      lv.AddItem(RangeMatrix[K, 1],nil);

      nlv:=lv.Items[lv.Items.Count-1];
      for r := rstart to y do
      begin
        if lv.Columns.Count<r then lv.Columns.Add;
        try
        nlv.SubItems.Add(RangeMatrix[K, R]);
        except

        end;
      end;
    end;

  finally
    // Unassign the Range Matrix
    RangeMatrix := Unassigned;

    // Quit Excel
    if not VarIsEmpty(XLApp) then
    begin
      XLApp.DisplayAlerts := False;
      MySheet := Unassigned;
      XLApp.Workbooks[1].Close(False);
      XLApp.Quit;
      XLAPP := Unassigned;
      Result := True;
    end;
  end;
  lv.Visible:=true;
end;


Dies funktioniert bestens in folgendem Kontext:
- Delphicompiler 2010/Office 2007 unter Win 8.1
- Delphicompiler XE8/Office 2007 unter Win 8.1
- Delphicompiler 2010/Office 2010 unter Win 7 Pro
Hier kann man in Taskmanager sehen, wie die Excelinstanzen geöffnet und sofort wieder geschlossen werden.

Es funktionert nicht bei
- Delphicompiler XE8/Office 2010 unter Win 7 Pro
Dort bleiben die Excel-Instanzen im Speicher bis die Delphiapplikation geschlossen wird.

Das Problem liegt in der Nutzung der Rangematrix:
ausblenden Delphi-Quelltext
1:
   RangeMatrix := XLApp.Range['A1', XLApp.Cells.Item[X, Y]].Value;					

weil offensichtlich die Freigabe nicht greift:
ausblenden Delphi-Quelltext
1:
RangeMatrix := Unassigned;					


Wenn ich die Datei nur öffne und sofort wieder schließe, funktioniert die Speicherfreigabe in allen Kontexten:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
procedure JustOpenCloseXLS(AXLSFile: string);
var
  ExcelApp: OLEVariant;
begin
  // Excel Instanz erzeugen
  ExcelApp := CreateOleObject('Excel.Application');
  try
    ExcelApp.Visible:=false;
    ExcelApp.Workbooks.Open(AXLSFile);
    ExcelApp.Workbooks[1].close(false);

  finally
    // Excel wieder schliessen
    if not VarIsEmpty(ExcelApp) then
    begin
      ExcelApp.Quit;
      ExcelApp := Unassigned;
    end;
  end;
end;


Hat jemand eine Idee, warum die Speicherfreigabe mit XE8 / Office 2010 nicht funktionert?
GuaAck
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 376
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: Do 21.04.16 22:11 
Hallo JVS,

helfen kann ich wohl nicht wirklich, meldet sich aber sonst keiner.

Habe mal eben probiert: Delphi 7, Windows 8.1, Office 2013:

Eergbnis: Excel wird sauber geschlossen. (Und die Inhalte meiner EXCEL-Tabelle erhalte ich auch irgendwie im Listview angezeigt.)

Aber: Wenn ich das "RangeMatrix := unassigend!" auskomentiere, dann geht es trotzdem!?

Baue doch mal als Test zu Beginn des Finaly-Blocks etwas wie Application.ProcessMessages und sleep(100) ein, damit alle Beteiligten genug Zeit haben, sich zu beruhigen.

Gruß
GuaAck

P.S.: Habe aktuell mit einem anderen Programm als Excel ein ähnliches Problem.
JVS Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 38



BeitragVerfasst: Fr 22.04.16 14:13 
Hallo GuaAck,
und Danke, dass Du Dir die Mühe bemacht hast.
Du hast mit Delhi 7 getestet und ich mit D2010: in beiden Fällen funktioniert alles problemlos.

Mittlerweile muss ich sagen: mit XE8 funktioniert es überhaupt nicht, egal welches Windows oder Office.

Die im Speicher zurückbleibenden Excel-Instanzen sind aber offensichtlich "frei", wie folgende Beobachtung zeigt.

Öffnet man den Explorer mit aktivierter Detailansicht und klickt auf ein XLS-File, so wird eine Excel-Instanz vom Explorer gestartet, um die Dateivorschau zu erstellen.
Mit Schliessen des Explorerfensters verschwindet auch die Instanz.
Nun das Interessante:
liegt noch eine meiner hängengebliebenen Instanzen im Speicher, so generiert der Explorer keine neue Excel-Instanz, sondern verwendet meine.
Und noch besser:
Wird der Explorer nun wieder geschlossen, verschwindet auch meine Hängeinstanz.

Die Befehlskette
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
RangeMatrix := Unassigned;
MySheet := Unassigned;
XLApp.Workbooks[1].Close(False);
XLApp.Quit;
XLAPP := Unassigned;

reicht also nicht, um den Speicher zu bereinigen.
Unter .NET gibt es Aufrufe der GarbageCollection, die dies erledigen.
Aber bei Delphi/XE8 ?

Gruß,
JVS
JVS Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 38



BeitragVerfasst: Mi 27.04.16 10:35 
Das Problem hängt tatsächlich mit der verwendeten OLE-Variablen RangMatrix zusammen.

Wird
ausblenden Delphi-Quelltext
1:
2:
3:
RangeMatrix := XLApp.Range['A1', XLApp.Cells.Item[X, Y]].Value;
// etc.
listview.SubItems.Add(RangeMatrix[K, R]);

ersetzt durch den Direktzugriff
ausblenden Delphi-Quelltext
1:
listview.SubItems.Add(XLApp.Cells.Item[K, R].Value);					

so gibt es kein Speicherproblem mehr.