Entwickeln von Add-Ins (XLLs) in Excel 2007

Veröffentlicht: 13. Nov 2006
Von Steve Dalton

Hier finden Sie Informationen zu Features von Microsoft Office Excel 2007, die sich auf XLL-Add-Ins auswirken und neue XLL-Funktionalität sowie Änderungen an der XLL-C-API selbst ermöglichen. (36 gedruckte Seiten)

Auf dieser Seite

Entwickeln von XLLs in Excel 2007 Entwickeln von XLLs in Excel 2007
Übersicht über XLL-bezogene Excel 2007-Features Übersicht über XLL-bezogene Excel 2007-Features
Übersicht über XLLs Übersicht über XLLs
Änderungen an XLLs in Excel 2007 Änderungen an XLLs in Excel 2007
Erstellen von versionsübergreifenden XLLs Erstellen von versionsübergreifenden XLLs
Erstellen von threadsicheren XLLs und Arbeitsblattfunktionen Erstellen von threadsicheren XLLs und Arbeitsblattfunktionen
Schlussbemerkung Schlussbemerkung
Der Autor Der Autor
Weitere Ressourcen Weitere Ressourcen

Entwickeln von XLLs in Excel 2007

Dieser Artikel richtet sich an Microsoft Visual C- und Microsoft Visual C++-Entwickler, die bereits über Erfahrung mit der Entwicklung von Microsoft Office Excel-Add-Ins oder -XLLs verfügen. Der Artikel enthält zwar einen kurzen Überblick über die Entwicklung von XLLs, stellt aber keine Einführung in dieses Thema dar. Um diesen Artikel optimal nutzen zu können, sollten Sie mit den folgenden Themen vertraut sein:

  • Konzepte und Konstrukte der Sprachen C und C++. Die Code-Beispiele sind in C++ geschrieben.

  • Erstellen von DLLs, die Funktionen exportieren.

  • Datenstruktur XLOPER und andere Excel-Datentypen wie die Gleitkommazahlen-Matrixstruktur.

  • Schnittstellenfunktionen des Add-In-Managers wie xlAutoOpen und xlAutoClose.

  • XLOPER-Speicherverwaltung (xlAutoFree, xlFree und die Verwendung von xlbitDLLFree und xlbitXLFree).

Übersicht über XLL-bezogene Excel 2007-Features

Die auffälligste Änderung bei XLL-bezogenen Features in Microsoft Office Excel 2007 ist die Erweiterung von Arbeitsblättern von 224 auf 234 Zellen bzw., um es genauer zu sagen, von 256 Spalten x 65.536 Zeilen (2x 216) auf 16.384 x 1.048.576 (214 x 220). Diese neuen Grenzwerte führen zu einem Überlauf der Ganzzahltypen, die in den alten Bereichs- und Arraystrukturen enthalten sind. Aufgrund dieser Änderung sind für die Definition von Bereichen und Arrays neue Datenstrukturen mit umfassenderen Ganzzahltypen erforderlich.

Die maximale Anzahl der Argumente, die eine Funktion verwenden kann, wurde von 30 auf 255 erhöht. Darüber hinaus ist der Austausch von Zeichenfolgen zwischen XLLs und Excel nicht mehr auf Bytezeichenfolgen von begrenzter Länge beschränkt, sondern es können auch lange Unicode-Zeichenfolgen ausgetauscht werden.

Die Neuberechnung von Arbeitsmappen in Multithreadanwendungen wird auf Rechnern mit einem und mehreren Prozessoren unterstützt. Im Gegensatz zu benutzerdefinierten Funktionen (User-Defined Functions, UDFs) von Microsoft Visual Basic für Applikationen (VBA) lassen sich XLL-UDFs als threadsicher registrieren. Wie die meisten integrierten Arbeitsblattfunktionen in Excel können Sie sie parallelen Threads zuweisen, um die Neuberechung zu beschleunigen. Dieser Vorteil geht allerdings auf Kosten einiger Einschränkungen sowie der Verantwortung, die Multithreadingberechtigungen nicht durch unsicheres Verhalten zu missbrauchen.

Die Analysis Toolpak-Funktionen sind nun vollständig in Excel integriert. Für die Datenanalysetools ist allerdings weiterhin das Add-In erforderlich. Dadurch können Inkompatibilitäten bei für frühere Versionen entwickelten XLLs auftreten, die ATP-Funktionen mithilfe von xlUDF aufrufen.

Auch die Benutzeroberfläche wurde stark verändert. Die Anpassung der Benutzeroberfläche ist allerdings nicht Gegenstand dieses Artikels. Es sei lediglich darauf hingewiesen, dass die alten benutzerdefinierten XLL-Menüs und Menüelemente zwar noch aktiv sind, sich aber nicht an den von den Funktionen der alten C-API (Application Programming Interface, Anwendungsprogrammierungsschnittstelle) vorgesehenen Stellen befinden.

Übersicht über XLLs

Die Verknüpfung von XLLs und Excel wird seit Microsoft Excel 4.0 unterstützt. Darüber hinaus wird eine Schnittstelle (die so genannte C-API) unterstützt, über die die XLL auf Excel-Funktionen und Befehle zugreifen kann. XLL ist die Bezeichnung für eine DLL, die die vom Add-In Manager benötigten Rückrufe sowie die exportierten Befehle und Arbeitsblattfunktionen der XLL enthält. Diese Schnittstelle wurde seit Excel 5.0 nahezu unverändert beibehalten. Viele der neuen Funktionen von Excel 2007 und einige bislang nicht unterstützte Funktionen früherer Versionen stehen nun in einer aktualisierten C-API zur Verfügung. Dieser Artikel behandelt die Innovationen der neuen C-API und erörtert einige der Übergangsprobleme, mit denen Entwickler konfrontiert werden.

Mit Excel 97 veröffentlichte Microsoft ein Software Development Kit (SDK), das ein Upgrade einer früheren SDK-Version darstellte. Dieses umfasst die folgenden Komponenten:

  • Eine C-Headerdatei, xlcall.h, die Definitionen der von Excel für den Datenaustausch mit XLLs verwendeten Datenstrukturen enthält, aufgelistete Funktions- und Befehlsdefinitionen, die den integrierten Excel-Arbeitsblattfunktionen entsprechen, XLM-Informationsfunktionen und -befehle sowie Prototypen für die Excel-Rückruffunktionen Excel4, Excel4v und XLCallVer.

  • Eine statische Importbibliothek, xlcall32.lib. Die Excel-Rückrufe werden aus dieser Bibliothek mit der C-Namensdekoration exportiert.

  • Ein XLL-SDK-Frameworkprojekt, das ein vollständiges XLL-Projekt sowie eine Reihe von Routinen zur Verarbeitung von Excel-Datentypen enthält und Unterstützung bei Aufrufen von Excel4 und Excel4v bereitstellt.

Die einfachste XLL ist eine XLL, die eine Funktion exportiert, die von Excel beim Laden des Add-Ins aufgerufen wird: xlAutoOpen. Diese Funktion führt Initialisierungsaufgaben durch und registriert alle von der XLL exportierten Funktionen und Befehle. Dabei ruft das Add-In die C-API-Entsprechung der XLM-Funktion REGISTER() auf. Eine Excel-Bibliothek, xlcall32.dll, exportiert Funktionen, mit deren Hilfe XLLs Rückrufe in Excel ausführen können: Excel4 und Excel4v (ein Hinweis darauf, dass die XLL-Funktionalität in Version 4 eingeführt wurde), die mittlerweile in Excel 2007 durch Excel12 and Excel12v ergänzt werden.

Der Add-In-Manager von Excel lädt und verwaltet XLLs. Er sucht nach den folgenden XLL-Exporten:

  • xlAutoOpen: Wird aufgerufen, wenn die XLL geladen wird. Die ideale Stelle zum Registrieren von XLL-Funktionen und -Befehlen, Initialisieren von Datenstrukturen und Anpassen der Benutzeroberfläche.

  • xlAutoClose: Wird aufgerufen, wenn die XLL entladen wird. Dies ist die Stelle, an der die Registrierung von Funktionen und Befehlen aufgehoben wird sowie Ressourcen freigegeben und Anpassungen rückgängig gemacht werden.

  • xlAutoAdd: Wird aufgerufen, wenn die XLL während einer Sitzung aktiviert oder geladen wird.

  • xlAutoRemove: Wird aufgerufen, wenn die XLL während einer Sitzung deaktiviert oder entladen wird.

  • xlAddInManagerInfo (xlAddInManagerInfo12): Wird aufgerufen, wenn der Add-In-Manager zum ersten Mal aufgerufen wird. Wenn ein Argument = 1 übergeben wird, wird eine Zeichenfolge zurückgegeben (der Name des Add-Ins), andernfalls wird #VALUE! zurückgegeben.

  • xlAutoRegister (xlAutoRegister12): Wird aufgerufen, wenn REGISTER (XLM) oder xlfRegister (C-API) ohne die Rückgabe- und Argumenttypen der Funktion aufgerufen wird. Die XLL wird intern gesucht, um die Funktion mit dieser bereitgestellten Information zu registrieren.

  • xlAutoFree (xlAutoFree12): Wird aufgerufen, wenn eine XLOPER-Datenstruktur an Excel zurückgegeben wird und diese mit einem Verweis auf Speicher gekennzeichnet ist, der von der XLL freigegeben werden muss.

Die letzten drei Funktionen nehmen XLOPER-Datenstrukturen an bzw. geben sie zurück. In Excel 2007 werden sie sowohl mit XLOPER als auch mit XLOPER12 unterstützt.

Die einzige obligatorische Funktion ist xlAutoOpen, ohne die die XLL nicht geladen wird. Wenn Sie innerhalb der DLL Speicher oder andere Ressourcen zuweisen, sollten Sie außerdem xlFree (xlFree12) implementieren, um Speicherverluste zu vermeiden. Sie sollten xlAutoClose implementieren, um nach dem Entladen der XLL eine Bereinigung durchzuführen. Alle anderen Funktionen können ausgelassen werden.

Die C-API trägt ihren Namen nicht ohne Grund: Excel tauscht Daten mithilfe von C-Standarddatentypen aus. Die Bibliotheksfunktionen weisen die C-Namensdekoration auf, und die Datenstrukturen entsprechen ANSI C. Mit der entsprechenden Erfahrung erhöht die Verwendung von C++ die Verwaltbarkeit, Lesbarkeit und Stabilität eines XLL-Projekts. Daher werden im weiteren Verlauf des Artikels grundlegende Kenntnisse in Bezug auf C++-Klassen vorausgesetzt.

In Tabelle 1 sind die Datenstrukturen zusammengefasst, die von Excel für den Datenaustausch mit XLLs verwendet werden. Außerdem enthält sie den Typbuchstaben, der beim Registrieren von Arbeitsblatt-UDFs im dritten Argument von xlfRegister verwendet wird.

Tabelle 1: Von Excel für den Datenaustausch mit XLLs verwendete Datenstrukturen

Datentyp

Übergebener Wert

Übergebener Verweis (Zeiger)

Kommentare

Boolesch

A

L

short (0 = false oder 1 = true)

Double

B

E


char *


C,F

NULL-terminierte ASCII-Byte-Zeichenfolge

unsigned char *


D,G

ASCII-Byte-Zeichenfolge vom Typ „counted“

[v12+] unsigned short *


C%, F%

NULL-terminierte Unicode-Zeichenfolge vom Typ „wide-char“

[v12+] unsigned short *


D%, G%

Counted-Unicode-Zeichenfolge vom Typ „wide-char“

unsigned short [int]

H


DWORD, size_t, wchar_t

[signed] short [int]

I

M

16-Bit

[signed long] int

J

N

32-Bit

FP


K

Gleitkomma-Arraystruktur

[v12+] FP12


K%

Gleitkomma-Arraystruktur für größere Raster



R

Werte, Arrays und Bereichsbezüge

[v12+] XLOPER12


Q

Variablenartige Arbeitsblattwerte und -arrays



U

Werte, Arrays und Bereichsbezüge

Die Typen C%, F%, D%, G%, K%, Q und U sind alle neu in Excel 2007 und werden in früheren Versionen nicht unterstützt. Die Zeichenfolgentypen F, F%, G und G% werden für vor Ort modifizierte Argumente verwendet. Wenn die UDF-Funktionsargumente XLOPER oder XLOPER12 als Typen P oder Q registriert werden, konvertiert Excel bei der Vorbereitung der Argumente Verweise auf eine einzelne Zelle in einfache Werte und Verweise auf mehrere Zellen in Arrays. Die Typen P und Q werden von Funktionen immer als einer der folgenden Typen angenommen: xltypeNum, xltypeStr, xltypeBool, xltypeErr, xltypeMulti, xltypeMissing oder xltypeNil, aber nicht als xltypeRef oder xltypeSRef, da diese stets dereferenziert sind.

Bei Argument 3 von xlfRegister, type_text, handelt es sich um eine Zeichenfolge der oben genannten Codes. Diese Zeichenfolge kann auch mit dem Rautezeichen (#) als Suffix versehen werden, um anzuzeigen, dass die Funktion ein Makrovorlagenäquivalent ist. Außerdem kann die Zeichenfolge mit dem Ausrufezeichen (!) als Suffix versehen werden, um anzuzeigen, dass die Funktion ein Makrovorlagenäquivalent ist und/oder als flüchtig zu behandeln ist. Die Deklaration von Funktionen als Makrovorlagenäquivalente versetzt diese in die Lage, den Wert von nicht neu berechneten Zellen abzurufen (einschließlich des aktuellen Werts der aufrufenden Zelle oder Zellen) und XLM-Informationsfunktionen aufzurufen. Funktionen, die als # registriert sind und Argumente vom Typ R oder U annehmen, sind standardmäßig flüchtig.

Excel 2007 ermöglicht es Ihnen auch, ein Dollarzeichen ($) anzuhängen, um anzugeben, dass die Funktion threadsicher ist. Allerdings werden Makrovorlagenfunktionen nicht als threadsicher betrachtet. Daher können Sie die Zeichen # und $ nicht gleichzeitig an die type_text-Typzeichenfolge einer Funktion anhängen. Falls eine XLL versucht, eine Funktion mit # und $ zu registrieren, tritt ein Fehler auf.

Änderungen an XLLs in Excel 2007

Excel 2007 lädt sämtliche für frühere Versionen erstellten Add-Ins und führt sie aus. Das heißt jedoch nicht, dass alle XLLs in Excel 2007 in der gewünschten Weise ausgeführt werden. Bevor Sie eine XLL als vollständig kompatibel mit Excel 2007 betrachten können, müssen Sie einige potenzielle Probleme ausschließen. In diesem Abschnitt werden einige der impliziten oder expliziten Voraussetzungen untersucht, die möglicherweise nicht mehr gegeben sind.

Eine der wichtigsten Änderungen, die von den neuen Strukturen umgesetzt wird, ist die Einführung von größeren Rastern, für die Zeilen und Spalten mit zwei neuen Daten-Typedefs gezählt werden:

C++

        typedef INT32 RW;        /* XL 12 Row */
        typedef INT32 COL;        /* XL 12 Column */
      

Diese 32-Bit-Ganzzahlen ohne Vorzeichen, die in den neuen Strukturen XLOPER12 und FP12 verwendet werden, ersetzen die WORD-Zeilen und BYTE-Spalten in XLOPER-Bereichen und die WORD-Zeilen in XLOPER-Arrays und der FP-Datenstruktur. Die andere wichtige Änderung ist die Unterstützung von Unicode-Zeichenfolgen in XLLs. Die XLOPER12-Datenstruktur ist nichts anderes als eine XLOPER-Datenstruktur, die die Typen RW und COL einschließt und bei der die ASCII-Byte-Zeichenfolge durch eine Unicode-Zeichenfolge ersetzt wird.

Neue Arbeitsblattfunktionen

Die Analysis Toolpak-Funktionen sind nun in Excel 2007 integriert. Bislang riefen XLLs eine ATP-Add-In-Funktion mithilfe von xlUDF auf. In Excel 2007 sollten Sie diese Art von Aufrufen stets durch einen anderen Aufruf ersetzen, etwa durch einen Aufruf von xlfPrice. Darüber hinaus gibt es eine Vielzahl von neuen Arbeitsblattfunktionen, die Sie nur aufrufen können, wenn Sie Excel 2007 ausführen. Wenn sie in früheren Versionen aufgerufen werden, gibt die C-API xlretInvXlfn zurück. Weitere Informationen erhalten unter Erstellen von versionsübergreifenden XLLs.

Zeichenfolgen

Zum ersten Mal ermöglicht Excel 2007 XLLs den direkten Zugriff auf Unicode-Zeichenfolgen mit einer Länge von bis zu 32.767 (215–1) Zeichen. Diese Zeichenfolgen werden in Arbeitsblättern bereits seit mehreren Versionen unterstützt. Dies ist ein großer Fortschritt gegenüber der vorherigen C-API, bei der Zeichenfolgen nicht länger als 255 ASCII-Bytes sein konnten. Durch die Argumenttypen C, D, F und G und den XLOPER-Typ xltypeStr werden Byte-Zeichenfolgen weiterhin mit den alten Längenbeschränkungen unterstützt.

In Microsoft Windows ist die Konvertierung zwischen Byte-Zeichenfolgen und Unicode-Zeichenfolgen vom Gebietsschema abhängig. Das heißt, die Art, in der 255-Byte-Zeichen in und aus Unicode-Zeichen vom Typ „wide-char“ konvertiert werden, hängt von den Gebietsschemaeinstellungen ab. Der Unicode-Standard weist jedem Code eindeutige Zeichen zu. Dies gilt jedoch nicht für erweiterte ASCII-Codes. Bedenken Sie diese gebietsschemaspezifische Konvertierung. So ist es beispielsweise möglich, dass zwei nicht übereinstimmende Unicode-Zeichenfolgen nach der Konvertierung in Byte-Zeichenfolgen sich bei einem Vergleich als identisch erweisen.

In Excel 2007 sind normalerweise alle Zeichenfolgen, die der Benutzer sieht, intern als Unicode dargestellt. Daher ist es in Excel 2007 am effektivsten, diese für den Austausch von Zeichenfolgen zu verwenden. Frühere Versionen ermöglichen Ihnen bei der Interaktion über die C-API den Zugriff auf Byte-Zeichenfolgen. Über Zeichenfolgenvarianten können Sie jedoch mit Unicode-Zeichenfolgen vom Typ „wide-char“ operieren. Diese können von VBA aus oder unter Verwendung der COM-Schnittstelle von Excel 2007 an eine DLL oder XLL übergeben werden. Wenn Sie Excel 2007 verwenden, wird empfohlen, nach Möglichkeit stets mit Unicode-Zeichenfolgen zu arbeiten.

In der C-API von Excel verfügbare Zeichenfolgetypen

Tabelle 2 zeigt die XLOPER-Datenstrukturen vom Typ xltypeStr der C-API.

Tabelle 2: XLOPER-Datenstrukturen vom Typ xltypeStr der C-API

Byte-Zeichenfolgen: XLOPER

Wide-Character-Zeichenfolgen: XLOPER12

Alle Versionen von Excel

Nur Excel 2007 und aufwärts

Max. Länge: 255 erweiterte ASCII-Bytes

Maximale Länge 32.767 Unicode-Zeichen

Erstes Byte (ohne Vorzeichen) = Länge

Erster Wide-Character = Länge

Wichtig! Wichtig:

Gehen Sie nicht davon aus, dass NULL-Terminierung vorliegt

Tabelle 3 zeigt die C/C++-Zeichenfolgen.

Tabelle 3: C/C++-Zeichenfolgen.

Byte-Zeichenfolgen

Wide-Character-Zeichenfolgen

NULL-terminiert (char *) „C“Max. Länge: 255 erweiterte ASCII-Bytes

NULL-terminiert (wchar_t *) „C%“ Maximale Länge 32.767 Unicode-Zeichen

Length-counted (unsigned char *) „D“

Length-counted (wchar_t *) „D%“

Konvertieren zwischen Zeichenfolgetypen

Durch die neuen Zeichenfolgetypen für XLLs ergibt sich die Möglichkeit, Byte-Zeichenfolgen in Zeichenfolgen vom Typ „wide-char“ bzw. Zeichenfolgen vom Typ „wide-char“ in Byte-Zeichenfolgen zu konvertieren.

Wenn Sie Zeichenfolgen kopieren, stellen Sie sicher, dass die Quellzeichenfolge nicht zu lang für den Puffer der Zielzeichenfolge ist. Je nach Implementierung tritt in diesem Fall entweder ein Fehler auf, oder die Zeichenfolge wird abgeschnitten. Tabelle 4 zeigt die Bibliotheksroutinen zum Konvertieren und Kopieren.

Tabelle 4: Bibliotheksroutinen zum Konvertieren und Kopieren

Tabelle 4: Bibliotheksroutinen zum Konvertieren und Kopieren

Beachten Sie, dass alle Bibliotheksfunktionen in Tabelle 4 ein (maximum)-Argument für die Länge der Zeichenfolge annehmen. Dieses sollte stets angegeben werden, um einen Überlauf der begrenzten Excel-Puffer zu vermeiden.

Beachten Sie die folgenden Aspekte:

  • Wandeln Sie bei der Arbeit mit als „[signed] char *“ deklarierten Byte-Zeichenfolgen vom Typ „length-counted“ die Länge in BYTE um, um negative Ergebnisse für Zeichenfolgen mit einer Länge von mehr als 127 zu vermeiden.

  • Wenn Sie eine NULL-terminierte Zeichenfolge mit maximaler Länge in den Puffer einer Zeichenfolge vom Typ „length-counted“ kopieren, kopieren Sie nicht das NULL-Terminierungszeichen, da dies einen Pufferüberlauf verursachen kann.

  • Wenn Sie neuen Speicher für NULL-terminierte Zeichenfolgen reservieren, weisen Sie auch Speicher für die NULL-Terminierung zu.

  • Wenn Sie Zeichenfolgen vom Typ „length-counted“ in NULL-terminierte Zeichenfolgen kopieren, legen Sie die NULL-Terminierung explizit fest, es sei denn, Sie verwenden eine API-Funktion wie lstrcpynA(), die dies automatisch erledigt.

  • Wenn die Geschwindigkeit eine Rolle spielt und Sie die genaue Anzahl der zu kopierenden Bytes kennen, verwenden Sie memcpy() anstelle von strcpy(), strncpy(), wcscpy() oder wcsncpy().

Die folgenden Funktionen konvertieren eine Byte-Zeichenfolge vom Typ „length-counted“ sicher in eine NULL-terminierte C-Byte-Zeichenfolge und eine NULL-terminierte C-Byte-Zeichenfolge in eine Byte-Zeichenfolge vom Typ „length-counted“. Für den ersten Vorgang muss ein ausreichend großer Zielpuffer und für den zweiten Vorgang ein maximal 256 Byte großer Puffer (einschließlich Längenbyte) übergeben werden:

C++

          char *BcountToC(char *cStr, const char *bcntStr)
          {
          BYTE cs_len = (BYTE)bcntStr[0];
          if(cs_len) memcpy(cStr, bcntStr+1, cs_len);
          cStr[cs_len] = '\0';
          return cStr;
          }
          #define MAX_XL4_STR_LEN 255u
          char *CToBcount(char *bcntStr, const char *cStr)
          {
          size_t cs_len = strlen(cStr);
          if(cs_len > MAX_XL4_STR_LEN) cs_len = MAX_XL4_STR_LEN;
          memcpy(bcntStr+1, cStr, cs_len);
          bcntStr[0] = (BYTE)cs_len;
          return bcntStr;
          }
        

Auswirkungen von größeren Rastern in Code

In Microsoft Office Excel 2003 betrug die maximale Größe eines Einzelblockbereichs 224 Zellen, was innerhalb der Grenzwerte von 32-Bit-Ganzzahlen liegt. In Excel 2007 liegt das Maximum bei 234 Zellen. Das Speicherlimit von 2 GB, dem alle Anwendungen unterliegen, wird mit einem einfachen Array aus Double-Werten bei ungefähr 228 Zellen erreicht. Es ist also sicher, die Größe eines xltypeMulti-Arrays oder eines FP- oder FP12-Typs mit einer 32-Bit-Ganzzahl mit Vorzeichen aufzuzeichnen. Um jedoch die Größe eines großen Bereichs wie des gesamten Excel 2007-Rasters sicher anzugeben, benötigen Sie einen 64-Bit-Ganzzahltyp wie __int64 oder INT64.

Bereichsnamen

Die Regeln, mit denen festgelegt wird, wann ein Bereichsname gültig ist, ändern sich mit Excel 2007. Die höchste Spalte ist nun XFD. Die Formel „=RNG1“ wird nun als Bezug auf die erste Zelle in der 371. Spalte interpretiert, es sei denn, die Arbeitsmappe wird im Kompatibilitätsmodus ausgeführt, während eine Arbeitsmappe im Excel 2003-Format geöffnet ist. Wenn eine 2003-Arbeitsmappe im Excel 2007-Format gespeichert wird, definiert Excel Namen wie RNG1 als _RNG1 um und weist den Benutzer auf diese Änderung hin. Alle Vorkommen von Arbeitsmappen werden ersetzt. Eine Ausnahme bilden Arbeitsmappen im VBA-Code, da dieser Code und eventuelle externe Bezüge in anderen Arbeitsmappen beschädigt werden. Prüfen Sie alle Codeabschnitte, in denen solche Namen erstellt oder überprüft werden bzw. in denen nach solchen Namen gesucht wird. Eine Methode, den Code so zu ändern, dass er in beiden gespeicherten Formaten funktioniert, besteht darin, nach _RNG1 zu suchen, falls RNG1 nicht definiert ist.

Behandeln von großen Arrays und von Bedingungen mit nicht ausreichendem Speicher

In Excel 2007 ist die Größe von XLOPER-Arrays (xltypeMulti), bei denen es sich um umgewandelte XLOPER-Bereichstypen handelt, nicht durch die Anzahl der Zeilen und Spalten oder die Breite der ganzen Zahlen eingeschränkt, die zum Zählen der Zeilen und Spalten verwendet werden, sondern durch den Speicherplatz für die 32-Bit-Adresse von Excel. Mit der Unterstützung deutlich größerer Raster erreicht Excel 2007 die Speichergrenze sehr viel leichter. Versuche, einen Bereichsbezug umzuwandeln (entweder explizit innerhalb von Code mit xlCoerce oder implizit, indem XLOPER-Argumente von exportierten Funktionen als Typ P bzw. XLOPER12-Argumente als Typ Q registriert werden), schlagen fehl, wenn der Bereich zu groß für den verfügbaren Speicher ist. Im ersten Fall schlägt ein Aufruf von Excel4 oder Excel12 mit xlretFailed fehl. Im zweiten Fall gibt Excel an die aufrufende Zelle #VALUE! zurück.

Wenn ein beträchtliches Risiko besteht, dass ein Benutzer ein großes Array an das Add-In übergibt, sollten Sie aus Leistungsgründen entweder die Größe des Arrays ermitteln und es zurückweisen, falls es eine bestimmte Größe überschreitet, oder Benutzerabbrüche von Arbeitsblattfunktionen mit xlAbort ermöglichen.

Behandeln von großen Bereichsbezügen und Neuberechnungen

In Excel 2003 kann ein Bezug maximal auf alle 224 Zellen auf einem Arbeitsblatt zeigen. Selbst wenn Sie nur einen Bruchteil dieser Zellen verarbeiten, kann beim Benutzer der Eindruck entstehen, dass der Rechner hängt. Untersuchen Sie daher die Bereichsgröße, und beurteilen Sie, ob Sie die Verarbeitung in kleineren Segmenten durchführen möchten. Stellen Sie außerdem wie bei großen Arrays Benutzerabbrüche mit xlAbort bereit. Mit Excel 2007 können die Bereiche sogar noch ungefähr 1.000 Mal größer sein. Eine sorgfältige Prüfung der Bereichsgröße ist daher noch wichtiger. Ein Befehl, dessen Ausführung in Excel 2003 für das gesamte Arbeitsblatt eine Sekunde dauert, kann in Excel 2007 bei identischen sonstigen Bedingungen über 15 Minuten in Anspruch nehmen.

Mehr Funktionsargumente

Excel 2007 ermöglicht XLLs das Exportieren von Funktionen mit bis zu 255 Argumenten. In der Praxis wäre eine Funktion mit einer derart hohen Anzahl von Argumenten, die jeweils eine andere Bedeutung haben, vermutlich viel zu komplex und müsste weiter aufgeteilt werden. Diese Anzahl von Argumenten wird eher von einer Funktion verwendet, die eine veränderliche Anzahl ähnlicher Eingabetypen verarbeitet.

Multithread-Neuberechnung

Sie können Excel 2007 so konfigurieren, dass für die Neuberechnung von als threadsicher registrierten Arbeitsblattfunktionen mehrere Threads verwendet werden. Dies kann auf Computern mit mehreren Prozessoren zu einer Reduzierung der Berechnungszeiten führen, ist aber unter Umständen auch auf Rechnern mit einem Prozessor von Vorteil, insbesondere wenn mithilfe von UDFs auf Funktionen auf einem Multithreadserver zugegriffen wird. Ein Vorzug von XLLs gegenüber VBA-, COM- und C#-Add-Ins besteht darin, dass sie ihre Funktionen als threadsicher registrieren können.

Zugriff auf neue Befehle und Arbeitsblattfunktionen

Die aufgelisteten Funktionsdefinitionen werden erweitert, sodass sie alle seit Excel 97 (Version 9) hinzugefügten Arbeitsblattfunktionen und einige neue Befehle umfassen. Tabelle 5 enthält die neuen Funktionen.

Tabelle 5: Neue Funktionen

xlfAccrint

xlfCumpararinc

xlfImlog10

xlfQuotient

xlfAccrintm

xlfDec2formattingin

xlfImlog2

xlfRandformattingetween

xlfAmordegrc

xlfDec2hex

xlfImparaower

xlfReceived

xlfAmorlinc

xlfDec2oct

xlfImpararoduct

xlfRoundformattingahtdown

xlfAveragea

xlfDelta

xlfImreal

xlfRoundformattingahtupara

xlfAverageif

xlfDisc

xlfImsin

xlfRtd

xlfAverageifs

xlfDollarde

xlfImsqrt

xlfSeriessum

xlfformattingahttext

xlfDollarfr

xlfImsuformatting

xlfSqrtparai

xlfformattingesseli

xlfDuration

xlfImsum

xlfStdeva

xlfformattingesselj

xlfEdate

xlfIntrate

xlfStdevparaa

xlfformattingesselk

xlfEffect

xlfIseven

xlfSumifs

xlfformattingessely

xlfEomonth

xlfIsodd

xlfTformattingilleq

xlfformattingin2dec

xlfErf

xlfIsthaidigit

xlfTformattingillpararice

xlfformattingin2hex

xlfErfc

xlfLcm

xlfTformattingillyield

xlfformattingin2oct

xlfFactdouformattingle

xlfMaxa

xlfThaidayofweek

xlfComparalex

xlfFvschedule

xlfMduration

xlfThaidigit

xlfConvert

xlfGcd

xlfMina

xlfThaimonthofyear

xlfCountifs

xlfGestepara

xlfMround

xlfThainumsound

xlfCouparadayformattings

xlfGetparaivotdata

xlfMultinomial

xlfThainumstring

xlfCouparadays

xlfHex2formattingin

xlfNetworkdays

xlfThaistringlength

xlfCouparadaysnc

xlfHex2dec

xlfNominal

xlfThaiyear

xlfCouparancd

xlfHex2oct

xlfOct2formattingin

xlfVara

xlfCouparanum

xlfHyparaerlink

xlfOct2dec

xlfVarparaa

xlfCouparaparacd

xlfIferror

xlfOct2hex

xlfViewGet

xlfCuformattingekparaimemformattinger

xlfImaformattings

xlfOddfpararice

xlfWeeknum

xlfCuformattingememformattinger

xlfImaginary

xlfOddfyield

xlfWorkday

xlfCuformattingememformattingerpararoparaerty

xlfImargument

xlfOddlpararice

xlfXirr

xlfCuformattingerankedmemformattinger

xlfImconjugate

xlfOddlyield

xlfXnparav

xlfCuformattingeset

xlfImcos

xlfparahonetic

xlfYearfrac

xlfCuformattingesetcount

xlfImdiv

xlfpararice

xlfYield

xlfCuformattingevalue

xlfImexpara

xlfpararicedisc

xlfYielddisc

xlfCumiparamt

xlfImln

xlfpararicemat

xlfYieldmat

Tabelle 6 enthält die neuen Befehle.

Tabelle 6: Neue Befehle

xlcActivateNotes

xlcInsertdatatable

xlcOptionsSettings

xlcUnprotectRevisions

xlcAddPrintArea

xlcInsertMapObject

xlcOptionsSpell

xlcVbaactivate

xlcAutocorrect

xlcLayout

xlcPicklist

xlcViewDefine

xlcClearPrintArea

xlcMoveBrk

xlcPivotTableChart

xlcViewDelete

xlcDeleteNote

xlcNewwebquery

xlcPostDocument

xlcViewShow

xlcEditodc

xlcNormal

xlcProtectRevisions

xlcWebPublish

xlcHideallInkannots

xlcOptionsMe

xlcRmPrintArea

xlcWorkgroupOptions

xlcHideallNotes

xlcOptionsMenono

xlcSheetBackground


xlcHidecurrNote

xlcOptionsSave

xlcTraverseNotes


Tabelle 7 enthält die entfernten Befehle.

Tabelle 7: Entfernte Befehle

xlcStart

xlcVbaObjectBrowser

xlcVbaAddWatch

xlcVbaReferences

xlcVbaClearBreakpoints

xlcVbaReset

xlcVbaDebugWindow

xlcVbaStepInto

xlcVbaEditWatch

xlcVbaStepOver

xlcVbaEnd

xlcVbaToggleBreakpoint

xlcVbaInstantWatch


Funktionen der C-API: Excel4, Excel4v, Excel12, Excel12v

Erfahrene XLL-Entwickler sollten mit den Funktionen der alten C-API sehr gut vertraut sein:

C++

          int _cdecl Excel4(int xlfn, LPXLOPER operRes, int count,... );
          /* followed by count LPXLOPERs */
          int pascal Excel4v(int xlfn, LPXLOPER operRes, int count, LPXLOPER opers[]);
        

In Excel 2007 umfasst das neue SDK auch ein Quellcodemodul, das Definitionen zweier weiterer Funktionen der C-API enthält, die in identischer Weise funktionieren, aber XLOPER12-Argumente annehmen. Wenn diese Funktionen in einer früheren Version von Excel aufgerufen werden, geben beide xlretFailed zurück:

C++

          int _cdecl Excel12(int xlfn, LPXLOPER12 operRes, int count,... );
          /* followed by count LPXLOPER12s */
          int pascal Excel12v(int xlfn, LPXLOPER12 operRes, int count, LPXLOPER12 opers[]);
        

Die beiden Funktionen geben dieselben Erfolgs- und Fehlerwerte wie Excel4 und Excel4v in früheren Versionen zurück. In Excel 2007 können alle vier jedoch einen neuen Fehler zurückgeben: xlretNotThreadSafe, definiert als 128. Dieser Fehler wird zurückgegeben, wenn eine als threadsicher registrierte Funktion versucht, eine nicht threadsichere Funktion aufzurufen. Typische Beispiele hierfür sind eine Makrovorlagenfunktion oder eine unsichere UDF.

Die Funktionen der Schnittstelle des Add-In Managers

Einige Funktionen vom Typ xlAuto- nehmen XLOPER-Datenstrukturen an oder geben solche zurück. Diese funktionieren in Excel 2007 weiterhin wie erwartet. Zudem werden mittlerweile aber auch die XLOPER12-Versionen erkannt. Die betroffenen Funktionen sind:

C++

          xloper * __stdcall xlAutoRegister(xloper *p_name);
          xloper * __stdcall xlAddInManagerInfo(xloper *p_arg);
        

Die neuen Funktionen sind:

C++

          xloper12 * __stdcall xlAutoRegister12(xloper12 *p_name); xloper12 * __stdcall xlAddInManagerInfo12(xloper12 *p_arg);
        

Keine dieser vier Funktionen ist obligatorisch. Wenn sie nicht vorhanden sind, verwendet Excel Standardverhalten. Wenn die XLOPER12-Versionen in Excel 2007 vorhanden sind, werden sie im Vergleich zu den XLOPER-Versionen vorzugsweise aufgerufen. Falls Sie XLLs mit mehreren Versionen erstellen, vergewissern Sie sich, dass die beiden Versionen in gleicher Weise funktionieren.

Gleitkomma-Matrixtypen

Die folgende als Typ K registrierte Struktur wird in Excel seit vielen Versionen unterstützt. Die Struktur wird in der SDK-Headerdatei xlcall.h für Excel 2007 definiert:

C++

          typedef struct
          {
          WORD rows;
          WORD columns;
          double array[1]; // Start of array[rows * columns]
          }
          FP;
        

Es folgt ein Beispiel für die Art von Annahmen, die Probleme verursachen können. Vor Excel 2007 konnten Sie annehmen, dass folgender Code zwar schlechter Stil, aber sicher ist:

C++

          // Unsafe function: returns the offset (from 0) of the column with max sum.
          int __stdcall max_column_index(FP *pArray)
          {
          int c, columns = pArray->columns;
          int r, rows = pArray->rows;
          double *p, column_sums[256]; // Explicit assumption!!!

          for(c = 0; c < columns; c++)
          column_sums[c] = 0.0; // Overrun possible if columns > 256!!!

          for(r = 0, p = pArray->array; r < rows; r++)
          for(c = 0; c < columns; c++)
          column_sums[c] += *p++; // overrun possible!!!

          int max_index = 0;
          for(c = 1; c < columns; c++)
          if(column_sums[c] > column_sums[max_index]) // overrun!!!
          max_index = c;
          return max_index;
          }
        

Auch wenn der FP-Typ nicht zu den neuen Datentypen gehört, nimmt diese Struktur in Excel 2007 Arrays auf, die die gesamte Breite des neuen Rasters umfassen (214 Spalten), die aber bei maximal 216 Zeilen abgeschnitten werden. In diesen Fall lässt sich das Problem ganz einfach beheben: Weisen Sie einen Puffer dynamisch zu, der stets die richtige Größe besitzt:

C++

          double *column_sums = new double[columns];
          // ...
          delete[] column_sums;
          return max_index;
        

Um die Übergabe von mehr als 216 Zeilen zu ermöglichen, unterstützt Excel 2007 außerdem einen neuen als K% registrierten Datentyp:

C++

          typedef struct
          {
          RW rows;
          COLS columns;
          double array[1]; // Start of array[rows * columns]
          }
          FP12;
        

XLCallVer

Die Signatur für die XLCallVer-Funktion lautet:

C++

          int pascal XLCallVer(void);
        

Von Microsoft Excel 97 bis Excel 2003 gab XLCallVer 1280 = 0x0500 = 5*256 zurück, wodurch Excel Version 5 angegeben wird (die letzte Version, in der Änderungen an der C-API vorgenommen wurden). In Excel 2007 lautet der Rückgabewert 0x0C00, wodurch auf ähnliche Weise Version 12 angegeben wird.

Damit können Sie zwar feststellen, ob die neue C-API zur Laufzeit verfügbar ist, es wird jedoch empfohlen, die ausgeführte Version von Excel mit Excel4(xlfGetWorkspace, &version, 1, &arg) zu ermitteln. Dabei entspricht arg einem numerischen XLOPER-Wert, der auf 2 gesetzt ist, und version ist eine XLOPER-Zeichenfolge, die in eine ganze Zahl umgewandelt werden kann. Auf diese Weise können Sie, falls erforderlich, im Add-In auch die vorhandenen Unterschiede zwischen den Versionen Microsoft Excel 2000, Microsoft Excel 2002 und Excel 2003 erkennen. So wurden beispielsweise Änderungen an der Genauigkeit einiger Statistikfunktionen vorgenommen, was Sie auf diese Weise erkennen können.

Funktionen der C-API mit unterschiedlichem Verhalten in verschiedenen Versionen

Abgesehen von Verbesserungen der Genauigkeit von numerischen Funktionen ändert sich die Funktionsweise von Arbeitsblattfunktionen normalerweise nicht von einer Version zur nächsten. In Bezug auf die nur für die C-API gültigen Funktionen funktionieren die folgenden drei jedoch in Excel 2007 anders als in früheren Versionen.

xlStack

Diese Funktion gibt nun entweder den tatsächlichen Stackspeicher oder 64 KB zurück, je nachdem, welcher der beiden Werte kleiner ist. Im folgenden Codebeispiel wird veranschaulicht, wie Sie in einer beliebigen Version den Stackspeicher abrufen.

C++

            double __stdcall get_stack(void)
            {
            if(gExcelVersion12plus)
            {
            xloper12 retval;
            if(xlretSuccess != Excel12(xlStack, &retval, 0))
            return -1.0;

            if(retval.xltype == xltypeInt)
            return (double)retval.val.w; // returns min(64Kb, actual space)
            // This is not the returned type, but was returned in
            // an Excel 12 beta, so the code is left here.
            if(retval.xltype == xltypeNum)
            return retval.val.num;
            }
            else
            {
            xloper retval;
            if(xlretSuccess != Excel4(xlStack, &retval, 0))
            return -1.0;

            if(retval.xltype == xltypeInt)
            return (double)(unsigned short)retval.val.w;
            }
            return -1.0;
            }
          
xlGetHwnd

In Excel 2003 gibt xlGetHwnd eine XLOPER-Datenstruktur vom Typ xltypeInt zurück, die einen 2-Byte-Short-Wert enthält, den unteren Teil des vollständigen Windows-HWND-Handles für Excel. Das vollständige Handle muss mithilfe der Windows-API EnumWindows abgerufen werden (siehe unten). Wenn die zurückgegebene XLOPER12-Datenstruktur vom Typ xltypeInt in Excel 2007 mit Excel12 aufgerufen wird, enthält sie eine 4-Byte-Ganzzahl mit Vorzeichen, die das vollständige Handle darstellt. Beachten Sie, dass Excel4 auch bei einem Aufruf in Excel 2007 stets nur den unteren Teil des Handles zurückgibt.

C++

            HWND get_xl_main_handle(void)
            {
            if(gExcelVersion12plus) // xlGetHwnd returns full handle
            {
            xloper12 main_xl_handle;
            if(Excel12(xlGetHwnd, &main_xl_handle, 0) != xlretSuccess)
            return 0;
            return (HWND)main_xl_handle.val.w;
            }
            else // xlGetHwnd returns low handle only
            {
            xloper main_xl_handle;
            if(Excel4(xlGetHwnd, &main_xl_handle, 0) != xlretSuccess)
            return 0;
            get_hwnd_enum_struct eproc_param = {main_xl_handle.val.w, 0};
            EnumWindows((WNDENUMPROC)get_hwnd_enum_proc, (LPARAM)&eproc_param);
            return eproc_param.full_handle;
            }
            }

            #define CLASS_NAME_BUFFER_SIZE    50

            typedef struct
            {
            short main_xl_handle;
            HWND full_handle;
            }
            get_hwnd_struct;

            // The callback function called by Windows for every top-level window
            BOOL __stdcall get_hwnd_enum_proc(HWND hwnd, get_hwnd_struct *p_enum)
            {
            // Check if the low word of the handle matches Excel's
            if(LOWORD((DWORD)hwnd) != p_enum->main_xl_handle)
            return TRUE; // keep iterating

            char class_name[CLASS_NAME_BUFFER_SIZE + 1];
            // Ensure that class_name is always null terminated
            class_name[CLASS_NAME_BUFFER_SIZE] = 0;
            GetClassName(hwnd, class_name, CLASS_NAME_BUFFER_SIZE);
            // Do a case-insensitive comparison for the Excel main window class name
            if(_stricmp(class_name, "xlmain") == 0)
            {
            p_enum->full_handle = hwnd;
            return FALSE; // Tells Windows to stop iterating
            }
            return TRUE; // Tells Windows to continue iterating
            }
          
xlGetInst

Wie bei xlGetHwnd gilt, dass bei einem Aufruf mit Excel12 die zurückgegebene XLOPER12-Datenstruktur vom Typ xltypeInt das vollständige Ausführungshandle enthält, während Excel4 eine XLOPER-Datenstruktur vom Typ xltypeInt mit dem unteren Teil des Handles zurückgibt.

Anpassen der Benutzeroberfläche

Mit Befehlen wie xlcAddBar, xlcAddMenu, xlcAddCommand, xlcShowBar, xlcAddToolbar, xlcAddTool und xlcShowToolbar konnten Sie in früheren Versionen von Excel Menüleisten, Menüs, Befehlsleisten oder Symbolleisten mithilfe von XLL-Code anpassen. Diese Befehle werden zwar weiterhin unterstützt, doch da die alten Menüleisten- und Befehlsleistenstrukturen ersetzt wurden, bieten sie Zugriff auf die XLL-Befehle in der Add-In-Gruppe der Multifunktionsleiste und stellen den Benutzern somit möglicherweise nicht die beabsichtigte Oberfläche bereit.

In Version 2007 von Microsoft Office können Sie die Benutzeroberfläche nur mithilfe von verwaltetem Code anpassen. Ein Ansatz zur Anpassung der Benutzeroberfläche in Excel 2007 besteht in einer separaten Ressource für verwalteten Code oder einem Add-In, die bzw. das die Funktionen zur Anpassung der Benutzeroberfläche enthält. Diese Ressource bzw. dieses Add-In können Sie dann eng mit Ihrer XLL verkoppeln und die enthaltenen Befehle und Funktionen aus dem XLL-Code aufrufen.

Erstellen von versionsübergreifenden XLLs

Die folgenden Abschnitte behandeln das Erstellen von XLLs, die mit mehreren Versionen von Excel kompatibel sind.

Einige nützliche Konstantendefinitionen

Es empfiehlt sich, in den XLL-Projektcode Definitionen wie die folgenden einzufügen und alle Instanzen von in diesem Kontext verwendeten Literalzahlen zu ersetzen. Dadurch wird versionsspezifischer Code weitgehend bereinigt und die Wahrscheinlichkeit von versionsbezogenen Fehlern in Form von harmlos wirkenden Zahlen reduziert.

C++

          #define MAX_XL11_ROWS        65536
          #define MAX_XL11_COLS        256
          #define MAX_XL12_ROWS        1048576
          #define MAX_XL12_COLS        16384
          #define MAX_XL11_UDF_ARGS    30
          #define MAX_XL12_UDF_ARGS    255
          #define MAX_XL4_STR_LEN        255u
          #define MAX_XL12_STR_LEN    32767u
        

Abrufen der ausgeführten Version

Ermitteln Sie mithilfe von Excel4(xlfGetWorkspace, &version, 1, &arg) die ausgeführte Version. Dabei entspricht arg einem numerischen XLOPER-Wert, der auf 2 gesetzt ist, und version ist eine XLOPER-Zeichenfolge, die in eine ganze Zahl umgewandelt werden kann. For Excel 2007 ist dies 12. Rufen Sie die Version in oder von xlAutoOpen aus ab. Sie können auch XLCallVer aufrufen, aber dadurch lässt sich nicht feststellen, welche der Versionen vor 2007 ausgeführt wird.

Herstellen von Verbindungen zur xlcall32-Bibliothek und zu den Funktionen der C-API

Auf der Basis des Excel 97 SDK- und Frameworkprojekts besteht die Standardmethode zum Herstellen einer Verbindung zur xlcall32-Bibliothek darin, in das Projekt einen Verweis auf die Importbibliothek xlcall32.lib aufzunehmen. (Alternativ können Sie die Verbindung zu xlcall32.dll mit LoadLibrary und GetProcAddress explizit zur Laufzeit herstellen.) Für in dieser Weise erstellte Projekte wird die Verbindung zur Bibliothek zur Kompilierzeit hergestellt, und ihre Exporte werden in der üblichen Weise als Prototypen erstellt. Beispiel:

C++

          #ifdef __cplusplus
          extern "C" {
          #endif
          int _cdecl Excel4(int xlfn, LPXLOPER operRes, int count,... );
          //...
          #ifdef __cplusplus
          } // extern "C" block end
          #endif

        

Wenn die XLL von Excel zur Laufzeit geladen wird, wird die Verbindung zu xlcall32.dll explizit hergestellt.

Add-Ins, die mit einer früheren Version der Importbibliothek erstellt (und zur Kompilierzeit verknüpft) wurden, können mit Excel 2007 ausgeführt werden, aber nicht auf die Rückrufe Excel12 und Excel12v zugreifen, da diese nicht definiert sind. Code, der die Excel 2007 SDK-Version von xlcall.h und die C++-Quelldatei [name?].cpp verwendet und der mit der Excel 2007-Version von xlcall32.lib verknüpft ist, kann diese Funktionen in allen früheren Versionen von Excel sicher aufrufen. Wenn sie von einer früheren Version als Excel 2007 aufgerufen werden, geben sie einfach xlretFailed zurück. Dies ist lediglich eine Fehlerabsicherung. Stellen Sie daher sicher, dass dem Code die ausgeführte Version bekannt ist und der korrekte Rückruf aufgerufen wird.

Erstellen von Add-Ins, die duale Schnittstellen exportieren

Angenommen sei eine XLL-Funktion, die eine Zeichenfolge annimmt und ein einzelnes Argument zurückgibt. Dabei kann es sich um einen beliebigen der Datentypen des Arbeitsblatts oder einen Bereich handeln. In Excel 2003 und Excel 2007 können Sie eine Funktion, die als Typ RD registriert ist und deren Prototypen erstellt wurden, wie folgt exportieren, wobei die Zeichenfolge als Byte-Zeichenfolge vom Typ „length-counted“ übergeben wird:

C++

          xloper * __stdcall my_xll_fn(unsigned char *arg);
        

Erstens: Dies funktioniert in allen jüngeren Versionen von Excel korrekt, unterliegt aber den Zeichenfolgenbeschränkungen der alten C-API. Zweitens: Excel 2007 kann XLOPER-Datenstrukturen zwar übergeben und annehmen, konvertiert sie jedoch intern in XLOPER12-Datenstrukturen. In Excel 2007 liegt also ein impliziter Konvertierungsoverhead vor, der bei Ausführung des Codes in Excel 2003 nicht besteht. Drittens: Diese Funktion kann unter Umständen threadsicher gemacht werden. Wenn jedoch die Typzeichenfolge in RD$ geändert wird, schlägt die Registrierung in Excel 2003 fehl. Aus diesen Gründen sollten Sie im Idealfall eine als UD%$ registrierte Funktion, deren Prototypen erstellt wurden, für Excel 2007-Benutzer wie folgt exportieren:

C++

xloper12 * __stdcall my_xll_fn_v12(wchar_t *arg);

Ein weiterer Grund, aus dem bei Ausführung von Excel 2007 die Registrierung einer anderen Funktion empfohlen wird, besteht darin, dass XLL-Funktionen bis zu 255 Argumente annehmen können. (Das vorherige Maximum betrug 30.) Glücklicherweise können Sie die Vorteile von beidem nutzen, indem Sie beide Versionen aus Ihrem Projekt exportieren. Anschließend können Sie die ausgeführte Excel-Version ermitteln und in Abhängigkeit von der festgestellten Version die jeweils geeignetere Funktion registrieren.

Wenn Sie die Exporte der XLL in einem Projekt registrieren, gibt es viele verschiedene Möglichkeiten zur Verwaltung der übergebenen Daten.

Ein einfacher Ansatz besteht darin, eine Datenstruktur zu definieren, beispielsweise mit dem Namen ws_func_export_data wie im nachfolgenden Code, und dann ein Array von ws_func_export_data zu deklarieren und zu initialisieren. Mit diesem Array kann der XLL-Code anschließend die an xlfRegister übergebenen XLOPER- oder XLOPER12-Datenstrukturen initialisieren. Beispiel:

C++

          #define XL12_UDF_ARG_LIMIT    255
          typedef struct
          {
          // REQUIRED (if v12+ strings undefined use v11- strings):
          char *name_in_code;    // RegArg2: Function name as in code (v11-)
          char *types;        // RegArg3: Return type and argument types (v11-)
          char *name_in_code12;        // RegArg2: Function name as in code (v12+)
          char *types12;        // RegArg3: Return type and argument types (v12+)
          char *ws_name;        // RegArg4: Fn name as it appears on worksheet
          char *arg_names;        // RegArg5: Argument names (Excel 11-: max 30)
          char *arg_names12;        // RegArg5: Argument names (Excel 12+: max 64)
          // OPTIONAL:
          char *fn_category;        // RegArg7: Function category for Function Wizard
          char *help_file;        // RegArg9: Help file (optional)
          char *fn_description;    // RegArg10: Function description text (optional)
          char *arg_help[MAX_XL12_UDF_ARGS - 11]; // RegArg11...: Arg help text
          }
          ws_func_export_data;
        

Unabhängig davon, wie die Registrierungsfunktion diese Daten verarbeitet, wird nur ein Name einer Arbeitsblattfunktion bereitgestellt, sodass Arbeitsblättern nicht bekannt ist, welche Funktion aufgerufen wird. (Sie benötigen diese Information auch nicht.) Es folgt ein Beispiel für eine Funktion, die Bibliotheksstandardfunktionen zur Umkehrung einer Arbeitsblattzeichenfolge aufruft:

C++

          // Excel 11-:  Register as type "1F"
          void __stdcall reverse_text_xl4(char *text) {strrev(text);}

          // Excel 12+:  Register as type "1F%$" (if linking with thread-safe library)
          void __stdcall reverse_text_xl12(wchar_t *text) {wcsrev(text);}
        

Anschließend können Sie die Struktur für diese Funktion wie folgt initialisieren:

C++

          ws_func_export_data WsFuncExports[1] =
          {
          {
          "reverse_text_xl4",
          "1F",
          "reverse_text_xl12",
          "1F%$",
          "Reverse",
          "Text",
          "", // arg_names12
          "Text", // function category
          "", // help file
          "Reverse text",
          "Text ",
          },
          };
        

Die obigen Zeichenfolgen sind NULL-terminierte Byte-Zeichenfolgen. Bevor sie in Code zum Initialisieren von XLOPER-Datenstrukturen verwendet werden können, müssen sie zunächst in Zeichenfolgen vom Typ „length-counted“ konvertiert werden. Zusätzlich müssen sie von Bytes in Unicode konvertiert werden, wenn sie zum Initialisieren von XLOPER12-Datenstrukturen verwendet werden. Alternativ können Sie diese Zeichenfolgen mit einem führenden Leerzeichen initialisieren, das von anderem Code mit den Längen der Zeichenfolgen überschrieben werden kann. Dies kann jedoch zu Problemen mit einigen im Debugmodus ausgeführten Compilern führen. Sie können die obige Strukturdefinition problemlos so modifizieren, dass bei Ausführung von Excel 2007 Unicode-Zeichenfolgen an Excel übergeben werden. In diesem Fall müssten Sie auch den Code ändern, in dem die Struktur verwendet wird.

Diese Methode führt zu der Möglichkeit, dass die Ergebnisse, die ein Arbeitsblatt liefert, wenn es in Excel 2003 ausgeführt wird, von den Ergebnissen abweichen, die dasselbe Arbeitsblatt liefert, wenn es in Excel 2007 ausgeführt wird. So ordnet Excel 2003 einer Unicode-Zeichenfolge in der Zelle eines Excel 2003-Arbeitsblatts eine ASCII-Byte-Zeichenfolge zu und schneidet diese ab, bevor sie an den Aufruf einer XLL-Funktion übergeben wird. Excel 2007 übergibt an eine korrekt registrierte XLL-Funktion ein unkonvertiertes Unicode-Zeichen vom Typ „strong“. Dies kann zu einem anderen Rückgabeergebnis führen. Sie sollten sich dieser Möglichkeit und ihrer Folgen für den Benutzer bewusst sein, und zwar nicht nur bei der Aktualisierung auf Excel 2007. So wurden einige integrierte numerische Funktionen zwischen Excel 2000 und Excel 2003 verbessert.

Einbinden von Datentypen in eine gemeinsame Containerklasser oder Struktur

Im Allgemeinen führt eine XLL-Arbeitsblattfunktion die folgenden Aktionen durch:

  • Sie überprüft die Eingaben auf ihre Gültigkeit.

  • Sie interpretiert die Eingaben im gegenseitigen Kontext.

  • Sie gibt spezifische Fehler zurück, wenn die Eingaben nicht den Erwartungen entsprechen.

  • Sie füllt Datenstrukturen

  • Sie ruft tieferen Kerncode auf.

  • Sie verarbeitet die Rückgabewerte vom Kerncode und gibt adäquate Werte an Excel zurück.

Wenn Sie, wie oben beschrieben, zwei exportierte Schnittstellen bereitstellen, ist es alles andere als ideal, die gesamte Logik doppelt zu verwalten. Aber wenn die Argumentdatentypen alle verschieden sind, was bleibt Ihnen dann schon anderes übrig? Die Antwort lautet: Binden Sie Excel-Datentypen in einen gemeinsamen Container ein. Es gibt viele verschiedene Ansätze. Für den Moment beschränken wir die Erörterung dieses Themas auf Container für XLOPER- und XLOPER12-Datenstrukturen. Die hier beschriebene Lösung besteht darin, eine C++-Klasse zu erstellen, die XLOPER- und XLOPER12-Datenstrukturen erkennt (und in diesem Beispiel jeweils eine Instanz dieser Strukturen enthält). In den folgenden Beispielen wird die C++-Klasse cpp_xloper verwendet. Eine vollständige Beschreibung dieser Klasse finden Sie in der zweiten Ausgabe des Buchs des Autors, das in der Liste am Ende des Artikels aufgeführt ist.

Im Idealfall besitzt die Klasse einen Konstruktor, der standardmäßig eine flache Kopie eines bereitgestellten XLOPER- oder XLOPER12-Initialisierers erstellt. (Kopien sind flach, um die Interpretation von schreibgeschützten UDF-Argumenten zu beschleunigen.) Darüber hinaus sollte sie Accessorfunktionen bereitstellen, um die Extraktion und Modifikation von Typ und Wert zu ermöglichen. Die exportierten Funktionen brauchen dann nur ihre XLOPER- oder XLOPER12-Argumente in diesen gemeinsamen Typ zu konvertieren, eine gemeinsame Funktion aufzurufen, die die tatsächliche Aufgabe ausführt, und die Rückgabewerte dieser Funktion zu verarbeiten. Es folgt ein Beispiel, in dem die Klasse cpp_xlope verwendet wird:

C++

          xloper * __stdcall my_function_xl4(xloper *arg)
          {
          cpp_xloper Arg(arg); // constructor makes shallow copy
          cpp_xloper RetVal; // passed by ref to retrieve return value
          my_core_function(RetVal, Arg);
          return RetVal.ExtractXloper();
          }

          xloper12 * __stdcall my_function_xl12(xloper12 *arg)
          {
          cpp_xloper Arg(arg); // constructor makes shallow copy
          cpp_xloper RetVal; // passed by ref to retrieve return value
          my_core_function(RetVal, Arg);
          return RetVal.ExtractXloper12();
          }

          void my_core_function(cpp_xloper &RetVal, cpp_xloper &Arg)
          {
          double d;
          if(Arg.IsMissing() || Arg.IsNil())
          RetVal.SetToError(xlerrValue);
          else if(Arg.IsNum() && (d = (double)Arg) >= 0.0)
          RetVal = sqrt(d); // overloaded assignment operator
          else
          RetVal.SetToError(xlerrNum);
          }
        

Es wird angenommen, dass die Methoden ExtractXloper und ExtractXloper12 Zeiger auf eine threadsichere, statische XLOPER- und XLOPER12-Datenstruktur zurückgeben, gegebenenfalls mit den geeigneten Speicher-frei-Bits.

Um den Overhead des gesamten Einbindungsvorgangs zu minimieren, muss der Konstruktor nicht nur flache Kopien erstellen, sondern auch intern die ausgeführte Version erkennen und XLOPER-Datenstrukturen in XLOPER12-Datenstrukturen konvertieren sowie bei Ausführung von Excel 2007 Excel12 anstelle von Excel4 aufrufen. Denn wenn Excel4 in Excel 2007 aufgerufen wird, werden die XLOPER-Argumente in XLOPER12 aufwärts konvertiert und die Rückgabewerte wieder zurück in XLOPER konvertiert. Wenn Sie die Klasse abrufen, um den korrekten Typ und Rückruf zu verwenden, können Sie es vermeiden, diese Konvertierung bei jedem Aufruf durchzuführen.

Einschließen der Funktionen der C-API in einen Wrapper

Anknüpfend an den vorherigen Abschnitt, wenn my_core_function einen Rückruf in Excel über die C-API ausführen muss, muss cpp_xloperwieder in eine XLOPER- oder XLOPER12-Datenstruktur konvertiert werden. Anschließend muss je nach ausgeführter Version entweder Excel4 oder Excel12 aufgerufen werden. Eine Lösung hierfür besteht darin, die Funktionen Excel4, Excel4v, Excel12 und Excel12v in cpp_xloper als Klassenmemberfunktion einzubinden. In diesem Fall können Sie my_core_function wie folgt umschreiben:

C++

          void my_core_function(cpp_xloper &RetVal, cpp_xloper &Arg)
          {
          if(!Arg.IsNum() || Arg.Excel(xlfSqrt, 1, &Arg) != xlretSuccess)
          RetVal.SetToError(xlerrValue);
          }

        

Hierbei fügt cpp_xloper::Excel den Rückgabewert direkt in Arg ein. Um diese Möglichkeit nutzen zu können und dabei weiterhin die Flexibilität zu besitzen, diese Funktion mit den Argumenten XLOPER, XLOPER12 oder cpp_xloper aufrufen zu können, erstellen Sie eine Reihe von überladenen Memberfunktionen:

C++

          int Excel(int xlfn); // not strictly necessary, but simplifies the others
          int Excel(int xlfn, int count, const xloper *p_op1, ...);
          int Excel(int xlfn, int count, const xloper12 *p_op1, ...);
          int Excel(int xlfn, int count, const cpp_xloper *p_op1, ...);
          int Excel(int xlfn, int count, const xloper *p_array[]);
          int Excel(int xlfn, int count, const xloper12 *p_array[]);
          int Excel(int xlfn, int count, const cpp_xloper *p_array[]);
        

Beachten Sie, dass vorausgesetzt wird, dass der Aufrufer der Variablenargumentversionen dieser Funktionen die Argumenttypen nicht vertauscht. Beachten Sie des Weiteren, dass die Verwendung von const an dieser Stelle es erforderlich macht, const den Definitionen von Excel4v und Excel12v hinzuzufügen.

Nach der Implementierung eines solchen Wrappers dürfen Sie die Funktionen der C-API nicht direkt aufrufen. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass Sie die Klasse als Container für die Speicherverwaltung des zurückgegebenen Werts verwenden können. Falls Excel eine Zeichenfolge zurückgibt, kann die Klasse ein Flag setzen, das sie auffordert, vor dem Überschreiben oder Löschen dieser Instanz xlFree aufzurufen. Außerdem können Sie in diese Wrapper zusätzliche Prüfungen integrieren. So können Sie beispielsweise prüfen, ob die Anzahl kleiner als null oder größer als der versionsspezifische Grenzwert ist. In diesem Fall können Sie einen zusätzlichen Fehler definieren und zurückgeben:

C++

          #define xlretNotCalled -1 // C API not called
        

Das folgende Beispiel veranschaulicht die Implementierung einer diese Funktionen, wobei Variablen mit dem Präfix m_ Klassenmembervariablen sind, die Flags m_XLtoFree12 und m_XLtoFree angeben, dass die Klasse xlFree aufrufen muss, um Speicher freizugeben, und m_Op und m_Op12 die internen Kopien der Klasse der Datenstrukturen XLOPER und XLOPER12 sind:

C++

          int cpp_xloper::Excel(int xlfn, int count, const cpp_xloper *p_op1, ...)
          {
          if(xlfn < 0 || count < 0 || count > (gExcelVersion12plus ?
          MAX_XL12_UDF_ARGS : MAX_XL11_UDF_ARGS))
          return xlretNotCalled;

          if(count == 0 || !p_op1) // default to 0 and NULL if omitted
          return Excel(xlfn); // Call a simpler version of this function

          va_list arg_ptr;
          va_start(arg_ptr, p_op1); // Initialize

          if(gExcelVersion12plus)
          {
          const xloper12 *xloper12_ptr_array[MAX_XL12_UDF_ARGS];
          xloper12_ptr_array[0] = &(p_op1->m_Op12);
          cpp_xloper *p_cpp_op;

          for(int i = 1; i < count; i++) // retrieve as ptrs to cpp_xlopers
          {
          p_cpp_op = va_arg(arg_ptr, cpp_xloper *);
          xloper12_ptr_array[i] = &(p_cpp_op->m_Op12);
          }
          va_end(arg_ptr); // Reset

          xloper12 temp;
          m_ExcelRtnCode = Excel12v(xlfn, &temp, count, xloper12_ptr_array);
          Free();

          if(m_ExcelRtnCode == xlretSuccess)
          {
          m_Op12 = temp; // shallow copy
          m_XLtoFree12 = true;
          }
          }
          else // gExcelVersion < 12
          {
          const xloper *xloper_ptr_array[MAX_XL11_UDF_ARGS];
          xloper_ptr_array[0] = &(p_op1->m_Op);
          cpp_xloper *p_cpp_op;

          for(int i = 1; i < count; i++) // retrieve as ptrs to cpp_xlopers
          {
          p_cpp_op = va_arg(arg_ptr, cpp_xloper *);
          xloper_ptr_array[i] = &(p_cpp_op->m_Op);
          }
          va_end(arg_ptr); // Reset

          xloper temp;
          m_ExcelRtnCode = Excel4v(xlfn, &temp, count, xloper_ptr_array);
          Free();

          if(m_ExcelRtnCode == xlretSuccess)
          {
          m_Op = temp; // shallow copy
          m_XLtoFree = true;
          }
          }
          return m_ExcelRtnCode;
          }
        

Neue Arbeitsblattfunktionen und Analysis Toolpak-Funktionen

Im Unterschied zu früheren Versionen von Excel wurden in Excel 2007 ATP-Funktionen (Analysis Toolpak) integriert. Bislang konnte eine XLL eine ATP-Funktion wie folgt mithilfe von xlUDF aufrufen:

C++

          double call_ATP_example(void)
          {
          xloper settlement, maturity, coupon, yield,
          redepmtion_value, num_coupons, rate_basis, price;

          // Initialise the data types to be passed to the ATP function PRICE
          settlement.xltype = maturity.xltype = coupon.xltype =
          yield.xltype = redepmtion_value.xltype =
          num_coupons.xltype = rate_basis.xltype = xltypeNum;

          // Set the values to be passed to the ATP function PRICE
          settlement.val.num = 39084.0; // 2-Jan-2007
          maturity.val.num = 46706.0; // 15-Nov-2027
          coupon.val.num = 0.04;
          yield.val.num = 0.05;
          redepmtion_value.val.num = 1.0; // 100% of face value
          num_coupons.val.num = 1.0; // Annual coupons
          rate_basis.val.num = 1.0; // Act/Act

          xloper fn;
          fn.xltype = xltypeStr;
          fn.val.str = "\005" "PRICE";
          if(Excel4(xlUDF, &price, 8, &fn, &settlement, &maturity,
          &coupon, &yield, &redepmtion_value, &num_coupons,
          &rate_basis) != xlretSuccess || price.xltype != xltypeNum)
          return -1.0; // an error value
          return price.val.num;
          }
        

In Excel 2007 müssen Sie den Aufruf von Excel mit Code wie dem folgenden ersetzen, wobei gExcelVersion eine ganzzahlige Variable mit globalem Gültigkeitsbereich in Ihrem Projekt ist, die während des Aufrufs von xlAutoOpen initialisiert wird.

C++

          int xl_ret_val;
          if(gExcelVersion12plus)
          {
          xl_ret_val = Excel4(xlfPrice, &price, 7, &settlement,
          &maturity, &coupon, &yield, &redepmtion_value,
          &num_coupons, &rate_basis);
          }
          else // gExcelVersion < 12
          {
          xloper fn;
          fn.xltype = xltypeStr;
          fn.val.str = "\005" "PRICE";
          xl_ret_val = Excel4(xlUDF, &price, 8, &fn, &settlement,
          &maturity, &coupon, &yield, &redepmtion_value,
          &num_coupons, &rate_basis);
          }
          if(xl_ret_val != xlretSuccess || price.xltype != xltypeNum)
          return -1.0; // an error value
          return price.val.num;
        

Durch die Verwendung eines Containers wie cpp_xloper können Sie die Versionsunabhängigkeit und Effizienz der Funktion in Excel 2003 und Excel 2007 erhöhen. Beispiel:

C++

          double call_ATP_example_3(void)
          {
          cpp_xloper Settlement(39084.0); // 2-Jan-2007
          cpp_xloper Maturity(46706.0); // 15-Nov-2027
          cpp_xloper Price, Coupon(0.04), YTM(0.05);
          cpp_xloper RedepmtionValue(1.0); // 100% of face
          cpp_xloper NumCoupons(1.0); // Annual coupons
          cpp_xloper RateBasis(1.0); // Act/Act
          int xl_ret_val;

          if(gExcelVersion12plus)
          {
          xl_ret_val = Price.Excel(xlfPrice, 7, &Settlement,
          &Maturity, &Coupon, &YTM, &RedepmtionValue,
          &NumCoupons, &RateBasis);
          }
          else
          {
          cpp_xloper Fn("PRICE");
          xl_ret_val = Price.Excel(xlUDF, 8, &Fn, &Settlement,
          &Maturity, &Coupon, &YTM, &RedepmtionValue,
          &NumCoupons, &RateBasis);
          }
          if(xl_ret_val != xlretSuccess || !Price.IsNum())
          return -1.0; // an error value
          return (double)Price;
          }
        

Wenn Sie versuchen, Arbeitsblattfunktionen der neuen C-API in früheren Versionen aufzurufen, wird der Fehler xlretInvXlfn zurückgegeben.

Erstellen von threadsicheren XLLs und Arbeitsblattfunktionen

In früheren Versionen von Excel wurde für alle Arbeitsblattberechnungen ein einzelner Thread verwendet. Auf einem Mehrprozessorcomputer oder einem Computer mit einem Prozessor, der explizit zur Verwendung von mehreren Threads konfiguriert wurde, versucht Excel 2007, die Last auf den Hauptthread und einen oder mehrere zusätzliche Threads zu verteilen, die das Betriebssystem über alle Prozesse zuweist. Auf einem Dualprozessorcomputer (oder Dual-Core-Computer) kann dies die Neuberechnungszeit je nach Topologie der Abhängigkeitsstruktur innerhalb der Arbeitsmappe(n) und je nach Anteil der threadsicheren Funktionen an der Gesamtzahl der beteiligten Funktionen maximal um den Faktor 2 verkürzen.

Excel 2007 verwendet einen Thread (den Hauptthread) zum Aufrufen aller Befehle, threadunsicheren Funktionen, xlAuto-Funktionen (mit Ausnahme von xlAutoFree) sowie COM- und VBA-Funktionen.

Wenn XLL-Entwickler einige einfache Regeln beachten, können sie threadsichere Funktionen erstellen:

  • Rufen Sie keine Ressourcen in anderen DLLs auf, die möglicherweise nicht threadsicher sind.

  • Führen Sie keine threadunsicheren Aufrufe über die C-API oder über COM durch.

  • Schützen Sie Ressourcen, die möglicherweise von mehreren Threads gleichzeitig genutzt werden, mithilfe von kritischen Abschnitten.

  • Verwenden Sie für threadspezifische Speicheranforderungen threadlokalen Speicher, und ersetzen Sie statische Variablen in Funktionen durch threadlokale Variablen.

Wenn eine XLOPER- oder XLOPER12-Datenstruktur mit xlbitDllFree an Excel zurückgegeben wird, wird vor dem Aufruf einer anderen Funktion auf demselben Thread xlAutoFree aufgerufen. Dies gilt für alle Funktionen, unabhängig davon, ob sie threadsicher sind oder nicht. Dadurch wird ausgeschlossen, dass eine threadlokale XLOPER-Datenstruktur wieder verwendet wird, bevor der ihr zugewiesene Speicher freigegeben wurde.

Aufrufen von Funktionen der C-API aus threadsicheren Funktionen

Funktionen aus VBA- und COM-Add-Ins werden als nicht threadsicher betrachtet. Threadsichere Funktionen können nicht nur keine Befehle der C-API aufrufen (wie xlcDefineName, ein Befehl, den keine Arbeitsblattfunktion aufrufen kann), sondern auch nicht auf XLM-Informationsfunktionen zugreifen. Außerdem können Sie threadsichere XLL-Funktionen nicht als Makrovorlagenäquivalente registrieren, indem Sie ein Rautezeichen (#) an die Typzeichenfolge anhängen. Demzufolge ist eine threadsichere Funktion nicht dazu in der Lage:

  • den Wert einer nicht berechneten Zelle zu lesen (einschließlich des Werts der aufrufenden Zelle).

  • Funktionen wie xlfGetCell, xlfGetWindow, xlfGetWorkbook und xlfGetWorkspace sowie weitere Informationsfunktionen aufzurufen.

  • XLL-interne Namen mit von xlfSetName zu definieren.

Die einzige XLM-Ausnahme ist die threadsichere Funktion xlfCaller. Wenn es sich beim Aufrufer um eine Arbeitsblattzelle oder einen Arbeitsblattbereich handelt, gibt xlfCaller einen Bezug zurück. Sie können diesen resultierenden Bezug jedoch in einer threadsicheren Funktion nicht mit xlCoerce sicher in einen Wert umwandeln, da xlretUncalced zurückgegeben würde. Die Registrierung der Funktion mit # löst zwar das Problem, aber in dem Fall ist die Funktion ein Makrovorlagenäquivalent und wird daher als nicht threadsicher angesehen. Dadurch wird verhindert, dass Funktionen threadsicher sind, die den vorherigen Wert zurückgeben, etwa wenn eine bestimmte Fehlerbedingung vorliegt.

Beachten Sie, dass alle nur für die C-API gültigen Funktionen threadsicher sind:

  • xlCoerce (Allerdings schlägt die Umwandlung von nicht berechneten Zellbezügen fehl.)

  • xlFree

  • xlStack

  • xlSheetId

  • xlSheetNm

  • xlAbort (Ausnahme: Kann nicht dazu verwendet werden, eine Abbruchbedingung zu bereinigen)

  • xlGetInst

  • xlGetHwnd

  • xlGetBinaryName

  • xlDefineBinaryName

Die einzige Ausnahme ist xlSet, ein Befehlsäquivalent, das als solches von keiner Arbeitsblattfunktion aufgerufen werden kann.

Alle integrierten Arbeitsblattfunktionen von Excel 2007 und ihre C-API-Äquivalente sind threadsicher mit Ausnahme der folgenden:

  • PHONETIC

  • CELL bei Verwendung der Argumente „format“ oder „address“

  • INDIRECT

  • GETPIVOTDATA

  • CUBEMEMBER

  • CUBEVALUE

  • CUBEMEMBERPROPERTY

  • CUBESET

  • CUBERANKEDMEMBER

  • CUBEKPIMEMBER

  • CUBESETCOUNT

  • ADDRESS, wenn der fünfte Parameter (sheet_name) angegeben ist

  • Alle Datenbankfunktionen (wie DSUM oder DAVERAGE), die auf eine Excel-PivotTable verweisen

  • ERROR.TYPE

  • HYPERLINK

Von mehreren Threads genutzte Ressourcen

Lese-/Schreibspeicher, auf den von mehr als einem Thread aus zugegriffen werden kann, muss mithilfe von kritischen Abschnitten geschützt werden. Für jeden Speicherblock, den Sie schützen möchten, benötigen Sie einen benannten kritischen Abschnitt. Diese können Sie während des Aufrufs von xlAutoOpen initialisieren und während des Aufrufs von xlAutoClose freigeben und auf NULL setzen. Jeder Zugriff auf den geschützten Block muss in Aufrufe von EnterCriticalSection und LeaveCriticalSection eingebunden werden. Zu jeden Zeitpunkt ist es immer nur einem Thread gestattet, sich in einem kritischen Abschnitt aufzuhalten. Es folgt ein Beispiel für die Initialisierung, Deinitialisierung und Verwendung eines Abschnitts mit dem Namen g_csSharedTable:

C++

          CRITICAL_SECTION g_csSharedTable; // global scope (if required)
          bool xll_initialised = false; // module scope

          int __stdcall xlAutoOpen(void)
          {
          if(xll_initialised)
          return 1;
          // Other initialisation omitted
          InitializeCriticalSection(&g_csSharedTable);
          xll_initialised = true;
          return 1;
          }

          int __stdcall xlAutoClose(void)
          {
          if(!xll_initialised)
          return 1;
          // Other cleaning up omitted
          DeleteCriticalSection(&g_csSharedTable);
          xll_initialised = false;
          return 1;
          }

          bool read_shared_table_element(unsigned int index, double &d)
          {
          if(index >= SHARED_TABLE_SIZE) return false;
          EnterCriticalSection(&g_csSharedTable);
          d = shared_table[index];
          LeaveCriticalSection(&g_csSharedTable);
          return true;
          }
          bool set_shared_table_element(unsigned int index, double d)
          {
          if(index >= SHARED_TABLE_SIZE) return false;
          EnterCriticalSection(&g_csSharedTable);
          shared_table[index] = d;
          LeaveCriticalSection(&g_csSharedTable);
          return true;
          }
        

Ein weiterer, vielleicht sichererer Ansatz zum Schützen eines Speicherblocks besteht darin, eine Klasse mit eigenem kritischen Abschnitt (CRITICAL_SECTION) zu erstellen, deren Konstruktor-, Destruktor- und Accessormethoden diesen berücksichtigen können. Dieser Ansatz bietet den zusätzlichen Vorteil, dass Objekte geschützt werden, die schon vor der Ausführung von xlAutoOpen initialisiert werden oder nach dem Aufruf von xlAutoClose fortbestehen. Wägen Sie den Einsatz dieses Ansatzes jedoch genau ab, denn das Erstellen einer zu hohen Anzahl von kritischen Abschnitten bewirkt eine nicht erforderliche Verlangsamung des Betriebssystems.

Wenn Sie Code besitzen, der auf mehr als einen geschützten Speicherblock gleichzeitig zugreifen muss, müssen Sie die Reihenfolge, in der der Zugriff auf die und das Verlassen der kritischen Abschnitte erfolgt, sehr sorgfältig festlegen. So können die beiden folgenden Funktionen beispielsweise einen Deadlock verursachen:

C++

          bool copy_shared_table_element_A_to_B(unsigned int index)
          {
          if(index >= SHARED_TABLE_SIZE) return false;
          EnterCriticalSection(&g_csSharedTableA);
          EnterCriticalSection(&g_csSharedTableB);
          shared_table_B[index] = shared_table_A[index];
          LeaveCriticalSection(&g_csSharedTableA);
          LeaveCriticalSection(&g_csSharedTableB);
          return true;
          }
          bool copy_shared_table_element_B_to_A(unsigned int index)
          {
          if(index >= SHARED_TABLE_SIZE) return false;
          EnterCriticalSection(&g_csSharedTableB);
          EnterCriticalSection(&g_csSharedTableA);
          shared_table_A[index] = shared_table_B[index];
          LeaveCriticalSection(&g_csSharedTableA);
          LeaveCriticalSection(&g_csSharedTableB);
          return true;
          }
        

Wenn die erste Funktion in einem Thread auf g_csSharedTableA zugreift, während die zweite in einem anderen Thread auf g_csSharedTableB zugreift, hängen beide Threads. Der korrekte Ansatz besteht darin, wie im folgenden Beispiel in einer konsistenten Reihenfolge auf die Bereiche zuzugreifen und sie in umgekehrter Reihenfolge wieder zu verlassen:

C++

          EnterCriticalSection(&g_csSharedTableA);
          EnterCriticalSection(&g_csSharedTableB);
          // code that accesses both blocks
          LeaveCriticalSection(&g_csSharedTableB);
          LeaveCriticalSection(&g_csSharedTableA);
        

Falls möglich, ist es allerdings aus Gründen der Threadkooperation günstiger, wie im folgenden Beispiel den Zugriff in verschiedene Blöcke zu trennen:

C++

          bool copy_shared_table_element_A_to_B(unsigned int index)
          {
          if(index >= SHARED_TABLE_SIZE) return false;
          EnterCriticalSection(&g_csSharedTableA);
          double d = shared_table_A[index];
          LeaveCriticalSection(&g_csSharedTableA);
          EnterCriticalSection(&g_csSharedTableB);
          shared_table_B[index] = d;
          LeaveCriticalSection(&g_csSharedTableB);
          return true;
          }
        

Wenn zahlreiche Zugriffskonflikte für eine freigegebene Ressource auftreten, etwa häufige Zugriffsanforderungen von kurzer Dauer, sollten Sie in Betracht ziehen, die Spinningfähigkeit von kritischen Abschnitten zu nutzen. Mit dieser Technik gestaltet sich das Warten auf die Ressource weniger prozessorintensiv. Legen Sie hierzu die Häufigkeit fest, mit der der Thread eine Schleife durchläuft, bevor er auf das Verfügbarwerden einer Ressource wartet. Verwenden Sie dazu während der Initialisierung des kritischen Abschnitts InitializeCriticalSectionAndSpinCount oder nach seiner Initialisierung SetCriticalSectionSpinCount. Wartevorgänge sind sehr speicherintensiv. Durch das Spinning wird dies vermieden, wenn die Ressource in der Zwischenzeit freigegeben wird. Auf einem Rechner mit nur einem Prozessor wird die Spinninganzahl effektiv ignoriert. Sie können sie dennoch angeben, ohne dass sich dies negativ auswirkt. Der Speicherheap-Manager verwendet eine Spinninganzahl von 4000. Weitere Informationen zur Verwendung von kritischen Abschnitten finden Sie im Thema zu kritischen Abschnitten (möglicherweise in englischer Sprache) in der Plattform-SDK-Dokumentation.

Deklarieren und Verwenden von threadlokalem Speicher

Angenommen sei eine Funktion, die einen Zeiger auf eine XLOPER-Datenstruktur zurückgibt:

C++

          xloper * __stdcall mtr_unsafe_example(xloper *arg)
          {
          static xloper ret_val; // memory shared by all threads!!!
          // code sets ret_val to a function of arg ...
          return &ret_val;
          }

        

Diese Funktion ist nicht threadsicher, da es möglich ist, dass ein Thread die statische XLOPER-Datenstruktur zurückgibt und ein anderer Thread sie überschreibt. Die Wahrscheinlichkeit dafür, dass dies geschieht, ist noch größer, wenn die XLOPER-Datenstruktur an xlAutoFree übergeben werden muss. Eine Lösung besteht darin, Speicher für eine zurückgegebene XLOPER-Datenstruktur zu reservieren und xlAutoFree zu implementieren, sodass der XLOPER-Speicher selbst freigegeben wird:

C++

          xloper * __stdcall mtr_safe_example_1(xloper *arg)
          {
          xloper *p_ret_val = new xloper; // must be freed by xlAutoFree
          // code sets ret_val to a function of arg ...
          p_ret_val.xltype |= xlbitDLLFree; // Always needed regardless of type
          return p_ret_val; // xlAutoFree must free p_ret_val
          }
        

Dieser Ansatz ist einfacher als der folgende, der auf der TLS-API beruht, aber einige Nachteile besitzt. Zum einen muss Excel xlAutoFree unabhängig vom Typ der zurückgegebenen XLOPER-Datenstruktur aufrufen. Zum anderen gibt es, wenn die neu zugewiesene XLOPER-Datenstruktur eine Zeichenfolge ist, die in einem Aufruf von Excel4 gefüllt wird, kein unproblematisches Verfahren, um xlAutoFree darüber zu informieren, dass die Zeichenfolge mit xlFree freigegeben werden muss, bevor p_ret_val mithilfe von delete freigegeben wird. Aus diesem Grund ist es erforderlich, dass die Funktion eine der DLL zugeordnete Kopie erstellt.

Eine Lösung, die diese Einschränkungen umgeht, besteht darin, eine threadlokale XLOPER-Datenstruktur zu füllen und zurückzugeben. Mit diesem Ansatz kann xlAutoFree den XLOPER-Zeiger nicht selbst freigeben.

C++

          xloper *get_thread_local_xloper(void);

          xloper * __stdcall mtr_safe_example_2(xloper *arg)
          {
          xloper *p_ret_val = get_thread_local_xloper();
          // code sets ret_val to a function of arg setting xlbitDLLFree or
          // xlbitXLFree if required
          return p_ret_val; // xlAutoFree must not free this pointer!
          }
        

Die nächste Frage lautet, wie der threadlokale Speicher eingerichtet und abgerufen wird. Dies geschieht mit der TLS-API (Thread Local Storage). Der erste Schritt besteht darin, mithilfe von TlsAlloc einen TLS-Index zu erstellen, der schließlich noch mit TlsFree freigegeben werden muss. Für beides ist DllMain am besten geeignet:

C++

          // This implementation just calls a function to set up thread-local storage
          BOOL TLS_Action(DWORD Reason);

          __declspec(dllexport) BOOL __stdcall DllMain(HINSTANCE hDll, DWORD Reason, void *Reserved)
          {
          return TLS_Action(Reason);
          }
          DWORD TlsIndex; // only needs module scope if all TLS access in this module

          BOOL TLS_Action(DWORD DllMainCallReason)
          {
          switch (DllMainCallReason)
          {
          case DLL_PROCESS_ATTACH: // The DLL is being loaded
          if((TlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
          return FALSE;
          break;

          case DLL_PROCESS_DETACH: // The DLL is being unloaded
          TlsFree(TlsIndex); // Release the TLS index.
          break;
          }
          return TRUE;
          }
        

Nach der Indexerstellung ist der nächste Schritt die Zuweisung eines Speicherblocks für jeden Thread. DllMain in der Referenz zur Dynamic-Link Library empfiehlt, dies mit einem DLL_THREAD_ATTACH-Ereignis bei jedem Aufruf von DllMain durchzuführen und den Speicher bei jedem DLL_THREAD_DETACH-Ereignis freizugeben. Wenn Sie sich nach dieser Empfehlung richten, hat dies jedoch zur Folge, dass die DLL nicht erforderliche Zuweisungen für Threads durchführt, die Excel nicht zur Neuberechnung benötigt. Stattdessen ist es besser, eine Strategie anzuwenden, mit der die Zuweisung bei der ersten Verwendung erfolgt. Definieren Sie zunächst die Struktur, die Sie jedem Thread zuweisen möchten. Für das einfache Beispiel von oben reicht Folgendes aus:

C++

          struct TLS_data
          {
          xloper xloper_shared_ret_val;
          // Add other required thread-local data here...
          };
        

Die folgende Funktion ruft einen Zeiger auf die threadlokale Instanz ab oder weist einen solchen zu, wenn es sich um den ersten Aufruf handelt:

C++

          TLS_data *get_TLS_data(void)
          {
          // Get a pointer to this thread's static memory
          void *pTLS = TlsGetValue(TlsIndex);
          if(!pTLS) // No TLS memory for this thread yet
          {
          if((pTLS = calloc(1, sizeof(TLS_data))) == NULL)
          // Display some error message (omitted)
          return NULL;
          TlsSetValue(TlsIndex, pTLS); // Associate this this thread
          }
          return (TLS_data *)pTLS;
          }
        

Nun wird erkennbar, wie der threadlokale XLOPER-Speicher abgerufen wird: Zunächst rufen Sie einen Zeiger auf die Instanz des Threads von TLS_data ab, und dann geben Sie einen Zeiger auf die darin enthaltene XLOPER-Datenstruktur zurück:

C++

          xloper *get_thread_local_xloper(void)
          {
          TLS_data *pTLS = get_TLS_data();
          if(pTLS)
          return &(pTLS->xloper_shared_ret_val);
          return NULL;
          }
        

Indem Sie TLS_data eine XLOPER12-Datenstruktur und eine get_thread_local_xloper12-Zugriffsfunktion hinzufügen, können Sie XLOPER12-Versionen von mtr_safe_example schreiben.

Selbstverständlich sind mtr_safe_example_1 und mtr_safe_example_2 threadsichere Funktionen, die Sie bei Ausführung von Excel 2007 als „RP$“ und mit Excel 2003 als „RP“ registrieren können. Sie können eine Version dieser XLL-Funktion erstellen und registrieren, die bei Ausführung von Excel 2007 eine XLOPER12-Datenstruktur als „UQ$“ verwendet. In Excel 2003 können Sie sie hingegen gar nicht registrieren.

Schlussbemerkung

Das neue XLL-SDK wird kurze Zeit nach der Veröffentlichung von Office 2007 zur Verfügung stehen. Von dem Moment an werden Sie dazu in der Lage sein, von den in diesem Artikel beschriebenen neuen Funktionen zu profitieren.

Der Autor

Steve Dalton ist Gründer von Eigensys Ltd. in Großbritannien. Eigensys arbeitet auf dem Feld der Excel-Entwicklung, mit dem Schwerpunkt auf Anwendungen in der Finanzanalyse. Herr Dalton ist der Autor der Bücher Excel Add-In Development in C/C++: Applications in Finance (Wiley, 2004) und Financial Applications Using Excel Add-In Development in C/C++ (Wiley, 2007).

Dieser Artikel wurde in Zusammenarbeit mit A23 Consulting entwickelt.

Weitere Ressourcen

Weitere Informationen zur Entwicklung von Add-Ins in Excel 2007 finden Sie in den folgenden Ressourcen, die möglicherweise zum Teil in englischer Sprache vorliegen:


Anzeigen: