HotXLS, die native Delphi- und C++Builder-Excel-Bibliothek, liest den Wert, den Excel bereits neben einer Formel gespeichert hat, über TryGetCachedFormulaValue und IXLSFormulaCacheReader. Keiner der beiden Einsprungpunkte ruft den Rechner auf, dekompiliert Formel-Tokens, aktualisiert Dirty-State oder schreibt etwas zurück ins Modell, sodass eine Arbeitsmappe, die man nur liest, exakt so bleibt, wie man sie geöffnet hat
Das Szenario, das das antreibt, ist fad und extrem verbreitet. Ein nächtlicher Job öffnet einige hundert Arbeitsmappen, die jemand anderes erzeugt hat, zieht aus jeder eine Spalte mit Summen heraus und schiebt die Zahlen in ein Warehouse. Die Summen liegen bereits in den Dateien — Excel hat sie berechnet und gespeichert. Doch in dem Moment, in dem der Job eine Formelzelle nach ihrem Wert fragt, baut eine Bibliothek, die auf diese Frage nur eine Antwort hat, einen Abhängigkeitsgraphen und evaluiert das ganze Blatt, und ein Job, der I/O-gebunden sein sollte, wird zu einem Rechenbenchmark
Warum kostet das Lesen einer Formelzelle eine vollständige Neuberechnung?
Weil ein Value-Getter auf einer Formelzelle eine Aufforderung ist, einen Wert zu erzeugen, und der einzige universell korrekte Weg, einen zu erzeugen, die Formel zu evaluieren ist. Das ist der richtige Default für eine Anwendung, die Arbeitsmappen bearbeitet, und der falsche Default für eine Pipeline, die sie extrahiert. Schlimmer noch: Evaluation ist nicht frei von Seiteneffekten — sie schreibt Ergebnisse zurück in Zellen, sie kippt Dirty-Flags, und sie kann anders auflösen als die erzeugende Anwendung, wenn eine Funktion nicht unterstützt wird oder eine externe Referenz gebrochen ist. Ein Job, den Sie Ihrem Ops-Team als read-only beschrieben haben, erzeugt stillschweigend eine Arbeitsmappe, die nicht mehr zu der auf der Platte passt, und wenn später etwas sie speichert, ändert sich auch die Datei auf der Platte
Cached-Value-Lesen ist die andere Hälfte des Vertrags. Es beantwortet eine engere Frage — was hat die erzeugende Anwendung hier gespeichert? — und weigert sich, alles andere zu beantworten. Wenn Sie wirklich frische Zahlen wollen, gibt Ihnen HotXLS weiterhin die inkrementelle Neuberechnung, gesteuert von einem Abhängigkeitsgraphen; der Punkt ist, dass Extraktion und Evaluation zwei verschiedene Aufrufe sein sollten, nicht ein Aufruf mit zwei Stimmungen
Drei orthogonale Fakten über eine Zelle
Zuerst die Schlussfolgerung: Ein gecachter Formelwert trägt drei unabhängige Fakten, und sie in einen einzigen Variant zusammenzuklemmen verliert Information, die man braucht. TXLSFormulaCacheInfo hält sie auseinander als State, Kind und Value. TXLSFormulaCacheState zeichnet die Herkunft über fünf Fälle auf — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated und xlfcsInvalidated — während TXLSFormulaCacheValueKind den Payload klassifiziert als xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean oder xlfcvError. Diese Trennung ist es, die es erlaubt, Anwesenheit ehrlich zu melden: Ein gecachtes Blank, eine gecachte leere Zeichenkette, ein gecachtes False, eine gecachte Null und ein gecachter Fehler sind alle echte Werte, sodass Anwesenheit nie aus VarIsEmpty oder VarIsNull geschlossen werden darf. TryGetCachedFormulaValue liefert True nur für xlfcsLoaded und xlfcsCalculated und füllt trotzdem einen diagnostizierbaren State ein, wenn es False zurückgibt
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row und Col sind hier alle einsbasiert
if Book.TryGetCachedFormulaValue(1, 12, 5, Info) then
Writeln('cached value: ', VarToStr(Info.Value))
else
Writeln('no usable cache, state ordinal ', Ord(Info.State));
finally
Book.Free;
end;
end;
Warum fehlt der gecachte Wert?
Es gibt genau vier Gründe, warum TryGetCachedFormulaValue False zurückgibt, und der State sagt Ihnen, welcher zutrifft. xlfcsNotFormula bedeutet, die Zelle hält ein Literal oder gar nichts, und Koordinaten außerhalb des Bereichs kollabieren in dieselbe Antwort. xlfcsMissing bedeutet, die Zelle ist wirklich eine Formel, aber der Producer hat keinen Value-Payload für sie gespeichert — ein häufiges Ergebnis, wenn ein Generator Formeln schreibt und Excel die Ergebnisse beim ersten Öffnen einfüllen lässt. xlfcsInvalidated bedeutet, der Formeltext wurde nach dem Laden ersetzt, sodass der Wert, der dort war, einen Ausdruck beschreibt, der nicht mehr existiert. xlfcsCalculated dagegen ist ein Erfolgsfall: Er markiert einen Wert, den Ihr eigener Code oder der HotXLS-Evaluator in dieser Sitzung erzeugt hat, im Gegensatz zu xlfcsLoaded, der aus der Datei kam
Ehrlichkeit über einen fehlenden Cache zählt mehr als das Überkleben. HotXLS weigert sich, einen Wert zu erfinden, und beim Speichern ist es genauso streng — nur xlfcsLoaded und xlfcsCalculated geben einen gecachten Wert aus, während xlfcsMissing und xlfcsInvalidated allein die Formel schreiben, statt eine veraltete Zahl in die Datei einzufrieren. Das lässt Ihnen in einer Pipeline drei vernünftige Reaktionen: die Zeile überspringen und die Lücke protokollieren, genau diese eine Arbeitsmappe bewusst neu berechnen und die Kosten akzeptieren, oder evaluieren und abgleichen. Wenn die evaluierte Zahl nicht mit dem übereinstimmt, was die erzeugende Anwendung geschrieben hätte, ist der Formel-Evaluations-Tracer das Werkzeug, um herauszufinden, wo die beiden Berechnungen auseinanderlaufen, statt aus dem Ergebnis zu raten
Ein Reader über die classic-, OOXML- und ODF-Engines hinweg
Eine Pipeline sollte nicht interessieren, ob die Datei, die sie gerade geöffnet hat, BIFF, OOXML oder ODF war. IXLSFormulaCacheReader ist der einzige Read-only-Einsprungpunkt für alle drei: Sowohl TXLSWorkbook.CreateFormulaCacheReader als auch TXLSXWorkbook.CreateFormulaCacheReader geben einen leichten Adapter über die Sparse-Zellnachschlage zurück, die jede Engine bereits benutzt, mit identischen einsbasierten Sheet-, Row- und Column-Koordinaten. Die Workbook-Klassen implementieren das Interface bewusst nicht selbst — eine Interface-Referenz auf die Arbeitsmappe würde ihre Ownership-Semantik verändern und Aufrufern erlauben, am Lifetime-Lease vorbei zu schlüpfen. Stattdessen löscht das Zerstören der Arbeitsmappe den rohen Zeiger innerhalb dieses Leases, und jeder Reader, den Ihr Code noch hält, wirft bei seiner nächsten Abfrage EXLSFormulaCacheReaderInvalidated, statt freigegebenen Speicher zu dereferenzieren. Das ist Fail-fast-Lebensdauerprüfung, keine Concurrency-Garantie
var
Reader: IXLSFormulaCacheReader;
Info: TXLSFormulaCacheInfo;
Row, Missing, Errors: Integer;
Total: Double;
begin
Reader := Book.CreateFormulaCacheReader;
Total := 0;
Missing := 0;
Errors := 0;
for Row := 2 to LastRow do
if Reader.TryGetCachedFormulaValue(1, Row, 7, Info) then
begin
case Info.Kind of
xlfcvNumber: Total := Total + Double(Info.Value);
xlfcvError: Inc(Errors);
end;
end
else if Info.State = xlfcsMissing then
Inc(Missing);
// Kein Rechner lief, kein Dirty-Flag hat sich bewegt, Book ist unverändert
end;
Wo die gecachten Bytes wirklich leben
Bei classic .xls-Dateien ist der Cache das FormulaValue-Feld des Formula-Records, acht Bytes, beschrieben in [MS-XLS] §2.5.133. Wenn das High Word gleich $FFFF ist, ist der Payload kein IEEE-754-Double, sondern eine getaggte Variante, und das Layout ist leicht subtil falsch zu nehmen: Der Variant-Typ sitzt in val[0] und der Boolean- oder BErr-Payload sitzt in val[2], mit val[1] undefiniert. HotXLS hat den Payload früher aus val[1] gelesen, die Art von Off-by-one, die nur an den speziellen Dateien auftaucht, die einen Boolean oder einen Fehler statt einer Zahl cachen. Reader und Shared-Formula-Writer einigen sich jetzt auf dieselben Offsets, sodass ein gecachtes TRUE einen Lade-und-Speicher-Zyklus intakt überlebt, statt in Rauschen zu zerfallen
Typentreue in den Paketformaten ist ein eigenes Problem mit eigener Falle. In OOXML hängt der gecachte Wert am c-Element als <v>, wobei das t-Attribut den Typ benennt, gemäß ECMA-376 Part 1 §18.3.1.4. HotXLS liest t="e" direkt in einen varError-Variant und mappt ihn beim Speichern auf den Standardfehlertext zurück, sodass Fehler nie als gewöhnliche Integer verkleidet auftreten — aber die Delphi-RTL hilft Ihnen hier nicht, denn VarAsType(Integer, varError) wirft eine Konvertierungs-Exception. Die funktionierende Konstruktion setzt TVarData.VType und TVarData.VError direkt. Daten folgen derselben Disziplin in die Gegenrichtung: t="d" und der ODF-Date-Value-Typ sind explizite Typdeklarationen und werden varDate, während ein BIFF-numerischer Cache gar kein Date-Flag trägt und deshalb ein Double bleibt. HotXLS rät nie ein Datum aus einem Zellenzahlformat, denn das Zahlformat ist Präsentation und der Cache ist Daten. ODF bringt einen weiteren Fall mit, den man kennen sollte — office:value-type="void" drückt einen Cache aus, der vorhanden ist, aber keinen Wert trägt, und da ODF keinen Error-Value-Typ hat, wird fehlerähnlicher Text als Text bewahrt, statt zu einem Fehler befördert
function DescribeCache(const Info: TXLSFormulaCacheInfo): string;
begin
case Info.State of
xlfcsNotFormula: Result := 'not a formula cell';
xlfcsMissing: Result := 'formula stored with no cached value';
xlfcsInvalidated: Result := 'formula replaced since load';
else
case Info.Kind of
xlfcvError: Result := 'error code ' + IntToStr(TVarData(Info.Value).VError);
xlfcvDateTime: Result := DateTimeToStr(VarToDateTime(Info.Value));
xlfcvBoolean: Result := BoolToStr(Info.Value, True);
xlfcvNumber: Result := FloatToStr(Double(Info.Value));
xlfcvString: Result := VarToStr(Info.Value);
else
Result := 'present but blank';
end;
end;
end;
Teilen sich Shared Formulas ihre gecachten Werte?
Nein, und das Gegenteil anzunehmen ist der Weg, auf dem ein Sweep am Ende für eine ganze Spalte dieselbe Zahl meldet. Eine OOXML-Shared-Formula teilt nur den Formelausdruck und die Speicheroptimierung; jede Mitgliedszelle besitzt weiter ihr eigenes <v>. HotXLS propagiert deshalb nie den Root-Member-Cache zu einem Follower, der ohne Wert ankam, und ein Follower, der als xlfcsMissing geladen hat, meldet nach einem Speichern und Wiederöffnen weiter xlfcsMissing. Wenn Sie gerade durcharbeiten, wie die Gruppe überhaupt gespeichert und expandiert wird, werden die Mechaniken des shared-formula-si-Attributs und seiner Expansion separat behandelt; fürs Cache-Lesen reduziert sich die Regel auf eine Zeile — fragen Sie jede Zelle, und vertrauen Sie nichts, was Sie nicht erfragt haben
Cached-Value-Lesen, der vereinheitlichte Cross-Engine-Reader und die Neuberechnungs-Engine, die Sie bewusst nicht aufrufen können, kommen alle im standardmäßigen HotXLS Delphi Spreadsheet Component für Delphi und C++Builder, ohne Abhängigkeit von Excel oder irgendeinem OLE-Automatisierungsserver; die Produktseite trägt die vollständige API-Referenz für die hier gezeigten Workbook- und Reader-Einsprungpunkte