Developpement ADD ins dans Excel 2007

Paru le 09 novembre 2006

Steve Dalton, Eigensys Ltd.

Lire cet article en anglais .

S'applique à : suites Microsoft Office 2007, Microsoft Office Excel 2007

Résumé : Découvrez les fonctions de Microsoft Office Excel 2007 qui affectent les compléments XLL et activent de nouvelles fonctionnalités XLL, ainsi que des modifications apportées à l'API C XLL elle-même (36 pages imprimées).

Sur cette page

Développement de XLL dans Excel 2007 Développement de XLL dans Excel 2007
Présentation des fonctions liées aux XLL dans Excel 2007 Présentation des fonctions liées aux XLL dans Excel 2007
Présentation des XLL Présentation des XLL
Modifications apportées aux XLL dans Excel 2007 Modifications apportées aux XLL dans Excel 2007
Écriture de XLL sécurisées au niveau des threads et de fonctions de feuille de calcul Écriture de XLL sécurisées au niveau des threads et de fonctions de feuille de calcul
Conclusion Conclusion
Ressources supplémentaires Ressources supplémentaires

Développement de XLL dans Excel 2007

Le public ciblé par cet article est composé de développeurs Microsoft Visual C et Microsoft Visual C++ qui connaissent déjà le développement de compléments Microsoft Office Excel, ou XLL. Cet article n'est pas une introduction au développement de XLL, même s'il comporte une brève présentation. Pour tirer le meilleur parti de cet article, les lecteurs doivent connaître :

  • Les concepts et structures des langages C et C++. Les exemples de code sont écrits en C++.

  • La création de DLL qui exportent des fonctions.

  • La structure de données XLOPER et les autres types de données Excel, comme la structure de la matrice en virgule flottante (FP).

  • Les fonctions d'interface du gestionnaire de compléments, comme xlAutoOpen et xlAutoClose.

  • La gestion de mémoire XLOPER (xlAutoFree, xlFree et l'utilisation de xlbitDLLFree et xlbitXLFree).

Présentation des fonctions liées aux XLL dans Excel 2007

Modification la plus évidente des fonctions liées aux XLL Microsoft Office Excel 2007, la feuille de calcul est étendue de 224 à 234 cellules ou, pour être plus précis, de 256 colonnes x 65 536 lignes (28 x 216) à 16 384 x 1 048 576 (214 x 220). Ces nouvelles limites dépassent les types entiers les contenant dans les anciennes structures de plage et de tableau. Cette modification implique de nouvelles structures de données avec des entiers plus grands, pour définir la taille des plages et des tableaux.

Le nombre maximal d'arguments qu'une fonction peut utiliser passe de 30 à 255. De plus, les XLL peuvent désormais échanger de longues chaînes Unicode avec Excel en lieu et place des chaînes d'octets limitées en longueur.

Le recalcul de classeurs multi-thread est pris en charge par les ordinateurs ayant un ou plusieurs processeurs. À la différence des fonctions définies par l'utilisateur (UDF) de Microsoft Visual Basic pour Applications (VBA), vous pouvez enregistrer des UDF XLL comme sécurisées au niveau des threads. Comme la majorité des fonctions intégrées de feuille de calcul dans Excel, vous pouvez les affecter à des threads simultanés pour accélérer le recalcul. Cet avantage compense certaines restrictions et la responsabilité de ne pas abuser des privilèges du multi-threading avec un comportement non fiable.

Les fonctions de l'Utilitaire d'analyse sont désormais totalement intégrées dans Excel, même si le complément est encore requis pour les outils d'analyse de données. Cela crée une zone d'incompatibilité pour les XLL développées pour les versions antérieures, qui appellent des fonctions ATP en utilisant xlUDF.

L'interface utilisateur a également été considérablement modifiée. La personnalisation de l'interface utilisateur n'entre pas dans le cadre de cet article, si ce n'est pour préciser que les anciens menus et éléments de menu personnalisés XLL sont toujours activés, mais qu'ils ne sont plus à l'emplacement prévu par l'ancienne interface de programmation d'application (API) C.

Présentation des XLL

Depuis Microsoft Excel 4.0, la liaison des XLL à Excel est prise en charge. De plus, une interface (appelée API C), grâce à laquelle la XLL peut accéder aux fonctions et commandes Excel, est également prise en charge. XLL est le nom d'une DLL qui contient les rappels requis par le Gestionnaire de compléments Excel, ainsi que les fonctions de commande et de feuille de calcul exportées de la XLL. L'interface n'avait pas été modifiée de façon significative depuis Excel 5.0. De nombreuses nouvelles fonctions d'Excel 2007, ainsi que certaines fonctions des versions antérieures qui n'étaient pas prises en charge précédemment, sont désormais disponibles dans une API C mise à jour. Cet article examine les ajouts à la nouvelle API C et évoque certaines difficultés de transition rencontrées par les développeurs.

Microsoft a publié un kit de développement logiciel (SDK) avec Excel 97, une mise à niveau d'une version d'un SDK antérieur, comprenant les composants suivants :

  • Un fichier d'en-tête C, xlcall.h, contenant des définitions des structures de données utilisées par Excel pour échanger des données avec les XLL ; des définitions de fonctions et de commandes énumérées correspondant aux fonctions intégrées de feuille de calcul Excel, des fonctions d'informations XLM et de nombreuses commandes ; des prototypes pour les fonctions de rappel d'Excel Excel4, Excel4v et XLCallVer.

  • Une bibliothèque d'importation statique, xlcall32.lib. Les rappels Excel sont exportés de cette bibliothèque avec l'ornement de nom C.

  • Un projet de structure SDK XLL, contenant un projet XLL complet et un certain nombre de routines pour gérer les types de données Excel et fournir de l'aide pour les appels vers Excel4 et Excel4v.

La XLL la plus simple est celle qui exporte une fonction appelée par Excel lorsque le complément est chargé : xlAutoOpen. Cette fonction effectue des tâches d'initialisation et enregistre toutes les fonctions et commandes exportées par la XLL. Elle suppose que le complément appelle l'équivalent API C de la fonction XLM REGISTER(). Une bibliothèque Excel, xlcall32.dll, exporte des fonctions qui activent des XLL pour le rappel dans Excel : Excel4 et Excel4v (indiquant que la fonctionnalité XLL a été introduite pour la première fois dans la version 4), désormais complétées par Excel12 et Excel12v dans Excel 2007.

Le Gestionnaire de compléments Excel charge et gère les XLL. Il recherche les exports XLL suivants :

  • xlAutoOpen : appelé lorsque la XLL est chargée. Emplacement idéal pour enregistrer des fonctions et commandes XLL, initialiser les structures de données et personnaliser l'interface utilisateur.

  • xlAutoClose : appelé lorsque la XLL est déchargée. Emplacement où supprimer l'enregistrement de fonctions et de commandes, libérer des ressources et annuler des personnalisations.

  • xlAutoAdd : appelé lorsque la XLL est activée ou chargée au cours d'une session.

  • xlAutoRemove : appelé lorsque la XLL est désactivée ou déchargée au cours d'une session.

  • xlAddInManagerInfo (xlAddInManagerInfo12) : appelé lorsque le gestionnaire de compléments est appelé pour la première fois. Si un argument passé est égal à 1, il renvoie une chaîne (le nom du complément), sinon il doit renvoyer #VALUE!

  • xlAutoRegister (xlAutoRegister12) : appelé lorsque REGISTER (XLM) ou xlfRegister (API C) est appelé sans les types d'argument et de renvoi de la fonction. Il recherche la XLL en interne pour enregistrer la fonction lorsque ces informations sont fournies.

  • xlAutoFree (xlAutoFree12) : appelé lorsqu'Excel reçoit un XLOPER marqué comme pointant vers la mémoire que la XLL doit libérer.

Les trois dernières fonctions acceptent ou renvoient des XLOPER. Dans Excel 2007, elles sont prises en charge par XLOPER et par XLOPER12.

La seule fonction requise est xlAutoOpen, sans laquelle le chargement du XLL est impossible. Lorsque vous allouez de la mémoire ou d'autres ressources à votre DLL, vous devez également implémenter xlFree (xlFree12) pour éviter les pertes de mémoire. Lorsque le XLL est déchargé, vous devez implémenter xlAutoClose pour procéder au nettoyage. Les autres fonctions peuvent être ignorées.

L'API C doit son nom au fait que : Excel échange des données en utilisant des types de données C standard ; les fonctions de la bibliothèque ont une décoration C de nom ; et les structures de données sont en C ANSI. Pour les utilisateurs expérimentés, C++ simplifie la gestion et offre davantage de lisibilité et de stabilité à un projet XLL. Par conséquent, le reste de l'article nécessite une compréhension de base des classes C++.

Les structures de données utilisées par Excel pour échanger des données avec les XLL sont récapitulées au tableau 1 qui indique également la lettre type utilisée dans le troisième argument de xlfRegister lors de l'enregistrement des UDF de feuille de calcul.

Tableau 1. Structures de données Excel utilisées pour échanger des données avec des XLL

Type de données

Passage par valeur

Passage par réf. (pointeur)

Commentaires

Booléen

A

L

abréviation (0=false ou 1=true)

Double

B

E

 

car *

 

C, F

Chaîne d'octets ASCII terminée par un caractère nul

car * non signé

 

D, G

Chaîne d'octets ASCII comptés

abréviation * non signée [v12+]

 

C%, F%

Chaîne Unicode à caractères étendus terminée par un caractère nul

abréviation * non signée [v12+]

 

D%, G%

Chaîne Unicode à caractères étendus comptés

abréviation non signée [int]

H

 

DWORD, size_t, wchar_t

abréviation [signée] [int]

I

M

16 bits

int [signé long]

J

N

32 bits

FP

 

K

Structure de tableau à virgule flottante

FP12 [v12+]

 

K%

Structure de tableau à virgule flottante et à grille étendue

XLOPER

 

P

Valeurs et tableaux de feuille de calcul de type variable

 

 

R

Valeurs, tableaux et références de plages

XLOPER12 [v12+]

 

Q

Valeurs et tableaux de feuille de calcul de type variable

 

 

U

Valeurs, tableaux et références de plages

Les types C%, F%, D%, G%, K%, Q et U sont des nouveautés d'Excel 2007 non pris en charge dans les versions précédentes. Les types de chaînes F, F%, G et G% sont utilisés pour les arguments modifiés sur place. Lorsque les arguments des fonctions UDF XLOPER ou XLOPER12 sont respectivement enregistrés en tant que types P ou Q, Excel convertit les références à cellule unique en valeurs simples et les références à cellules multiples en tableaux au moment de la préparation de ces arguments. Les types P et Q arrivent toujours dans votre fonction sous une des formes suivantes : xltypeNum, xltypeStr, xltypeBool, xltypeErr, xltypeMulti, xltypeMissing ou xltypeNil, mais pas xltypeRef ou xltypeSRef car ceux-ci sont toujours déréférencés.

L'argument 3 de xlfRegister, type_text, est une chaîne des codes ci-dessus. Cette chaîne peut aussi être suivie d'un signe dièse (#) pour indiquer que la fonction équivaut à une feuille macro. La chaîne peut également être suivie d'un point d'exclamation (!) pour indiquer que la fonction équivaut à une feuille macro et/ou qu'elle doit être considérée comme volatile. Le fait de déclarer qu'une fonction équivaut à une feuille macro lui permet d'obtenir la valeur des cellules non calculées (y compris la valeur actuelle de la (des) cellule(s) appelante(s)) et d'appeler les fonctions d'informations XLM. Par défaut, les fonctions enregistrées avec # et les arguments de type R ou U sont volatiles.

Excel 2007 vous permet également d'ajouter le symbole dollar ($) pour indiquer que la fonction est sécurisée au niveau thread. Les fonctions de feuille macro, quant à elles, ne sont pas jugées sécurisées au niveau thread. Par conséquent, vous ne pouvez pas ajouter à la fois un signe # et un signe $ à la chaîne type_text type d'une fonction. Si un XLL essaie d'enregistrer une fonction comprenant les deux signes # et $, la tentative échoue.

Modifications apportées aux XLL dans Excel 2007

Excel 2007 charge et exécute les compléments créés pour les versions précédentes. Cela ne signifie pas que tous les XLL s'exécutent comme prévu dans Excel 2007. Avant de considérer un XLL comme totalement compatible avec Excel 2007, il convient d'éviter quelques pièges. Cette section récapitule quelques hypothèses implicites ou explicites qui peuvent ne plus être valables.

L'une des deux principales modifications proposées par les nouvelles structures est l'introduction de grilles plus grandes pour lesquelles les lignes et les colonnes sont comptées avec deux nouvelles typedefs de données :

C++

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

Utilisés dans les nouvelles structures XLOPER12 et FP12, ces entiers 32 bits signés remplacent les lignes WORD et les colonnes BYTE utilisées dans les plages XLOPER et les lignes WORD utilisées dans les tableaux XLOPER et la structure de données FP. Autre modification importante, les chaînes Unicode sont désormais prises en charge dans les XLL. La structure XLOPER12 correspond simplement à un XLOPER comprenant les types RW et COL et dans lequel la chaîne d'octets ASCII est remplacée par une chaîne Unicode.

Nouvelles fonctions de feuille de calcul

Les fonctions Analysis Toolpak (ATP) font désormais partie intégrante d'Excel 2007. Auparavant, un XLL appelait une fonction de complément ATP via xlUDF. Dans Excel 2007, vous devez remplacer cet appel par un appel vers xlfPrice, par exemple. Il existe également de nouvelles fonctions de feuille de calcul que vous pouvez uniquement appeler lorsque Excel 2007 est en cours d'exécution. Dans les versions précédentes, lorsque celles-ci sont appelées, l'API C renvoie les fonctions xlretInvXlfn. Pour plus d'informations, reportez-vous à Writing Cross-Version XLLs (Création de XLL compatibles avec différentes versions).

Chaînes

Pour la première fois, Excel 2007 permet aux XLL d'accéder directement aux chaînes Unicode à caractères étendus d'un maximum de 32 767 (215–1) caractères de long. Ces chaînes sont désormais prises en charge dans les feuilles de calcul de plusieurs versions. Il s'agit d'une amélioration considérable par rapport au précédent API C avec lequel les chaînes ne pouvaient pas dépasser 255 octets ASCII. Les chaînes d'octets sont toujours prises en charge, selon les mêmes limites de longueur, avec les types d'arguments C, D, F et G ainsi que la fonction XLOPER xltypeStr.

Dans Microsoft Windows, la conversion entre les chaînes d'octets et les chaînes Unicode dépend des paramètres régionaux. Ceci signifie que les caractères de 255 octets sont convertis en et à partir de caractères Unicode étendus en fonction des paramètres régionaux du système. La convention Unicode Standard attribue des caractères uniques à chaque code, ce qui n'est pas le cas pour les codes ASCII étendus. Souvenez-vous de cette conversion liée aux paramètres régionaux. Par exemple, deux chaînes Unicode inégales peuvent devenir égales après la conversion en chaînes d'octets.

Dans Excel 2007, toute chaîne vue par l'utilisateur est généralement représentée en interne en Unicode. Par conséquent, le moyen le plus efficace d'échanger des chaînes dans Excel 2007 est de les utiliser. Les précédentes versions ne vous permettent d'accéder aux chaînes d'octets que lors de l'interaction via l'API C, bien qu'il soit possible de travailler avec des chaînes Unicode étendues grâce à des Variantes de chaînes. Celles-ci peuvent être transmises à un DLL ou XLL depuis VBA ou en utilisant l'interface COM d'Excel 2007. Lorsque vous utilisez Excel 2007, veillez à travailler le plus possible avec des chaînes Unicode.

Types de chaînes disponibles dans l'API C Excel

Le tableau 2 montre les XLOPER xltypeStr de l'API C.

Tableau 2. XLOPER xltypeStr de l'API C

Chaînes d'octets : XLOPER

Chaînes à caractères étendus : XLOPER12

Toutes versions d'Excel

Excel 2007+ uniquement

Longueur max : 255 octets ASCII étendus

Longueur maximum de 32 767 caractères Unicode

Premier octet (non signé) = longueur

Premier caractère étendu = longueur

Important : Ne tenez pas compte du caractère nul de terminaison.

Le tableau 3 montre les chaînes C/C++.

Tableau 3. Chaînes C/C++

Chaînes d'octets

Chaînes à caractères étendus

« C » terminé par un caractère nul (car *) - Longueur max : 255 octets ASCII étendus

« C% » terminé par un caractère nul (wchar_t *) - Longueur maximum de 32 767 caractères Unicode

« D » - Longueur comptée (car * non signé)

« D% » - Longueur comptée (wchar_t *)

Conversion d'un type de chaîne en un autre type

Avec l'ajout de nouveaux types de chaînes aux XLL, vous pouvez être amené à convertir des chaînes d'octets en chaînes à caractères étendus ou inversement.

Lors de la copie des chaînes, vérifiez que la chaîne source n'est pas trop longue pour le tampon de la chaîne de destination. Dans ce cas, selon l'implémentation, la tentative échoue ou la chaîne est tronquée. Le tableau 4 montre la conversion et la copie de routines de la bibliothèque.

Tableau 4. Conversion et copie de routines de la bibliothèque
Conversion et copie de routines de la bibliothèque

Notez que toutes les fonctions de la bibliothèque présentées au tableau 4 nécessitent un argument de longueur de chaîne (maximum). Cet argument doit impérativement être fourni pour prévenir toute saturation des tampons limités à Excel.

Examinons l'hypothèse suivante :

  • Lorsque vous travaillez avec des chaînes d'octets à longueur comptée déclarées en tant que car * [signé], convertissez la longueur en BYTE (octet) afin de prévenir les résultats négatifs pour les chaînes supérieures à 127.

  • Lors de la copie d'une chaîne à longueur maximum terminée par un caractère nul vers un tampon de chaîne à longueur limitée, ne copiez pas le caractère nul de terminaison car celui-ci risquerait de saturer le tampon.

  • Lors de l'allocation de nouvelle mémoire pour les chaînes terminées par un caractère nul, allouez de l'espace pour le caractère nul de terminaison.

  • Définissez explicitement le caractère nul de terminaison lors de la copie des chaînes à longueur comptée vers les chaînes terminées par un caractère nul, sauf si vous utilisez une fonction API qui s'en charge toujours pour vous, comme lstrcpynA().

  • Lorsque la vitesse importe et que vous connaissez le nombre d'octets copiés, utilisez memcpy() au lieu de strcpy(), strncpy(), wcscpy() ou wcsncpy().

Les fonctions suivantes convertissent en toute sécurité une chaîne d'octets à longueur comptée en chaîne d'octets C terminée par un caractère nul et inversement. La première implique un tampon cible suffisamment grand et la seconde un tampon de 256 octets maximum (octet de longueur compris) :

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;
}

Implications de l'agrandissement des grilles dans le code

Dans Microsoft Office Excel 2003, la taille maximum d'un tableau d'un seul bloc est de 2 puissance 24 cellules, ce qui entre largement dans les limites d'un entier de 32 bits. Dans Excel 2007, la limite est de 234 cellules. La limite de mémoire de deux gigaoctets imposée à toutes les applications est atteinte avec un simple tableau de doubles à environ 228 cellules, de sorte que l'enregistrement de la taille d'un tableau xltypeMulti ou d'un FP ou FP12 avec un entier 32 bits signé ne pose pas de problème. Mais pour atteindre en toute sécurité la taille d'une plage immense, comme la grille entière d'Excel 2007, il vous faut un type d'entier 64 comme __int64 ou INT64.

Noms des plages

Les règles déterminant la validité d'un nom de plage changent avec Excel 2007. La colonne maximum est désormais XFD. Par exemple, la formule =RNG1 est désormais interprétée comme une référence pour la première cellule de la 371e colonne, sauf si le classeur est exécuté en mode de compatibilité, à savoir lorsqu'un classeur au format Excel 2003 est ouvert. Si un utilisateur enregistre un classeur 2003 au format Excel 2007, Excel redéfinit les noms tels que RNG1 en _RNG1 et prévient l'utilisateur de ce changement. Toutes les occurrences du classeur sont remplacées, à l'exception de celles en code VBA, ce qui modifie ce code ainsi que de possibles références externes dans d'autres classeurs. Vous devez vérifier tous les codes qui créent, valident ou recherchent ces noms. Pour modifier le code de sorte qu'il fonctionne dans les deux formats enregistrés, vous pouvez notamment rechercher _RNG1 si RNG1 n'est pas défini.

Gestion de grands tableaux et de conditions de type mémoire insuffisante

Dans Excel 2007, les tableaux XLOPER (xltypeMulti) correspondant à des types de plages XLOPER contraintes sont limités en taille par l'espace d'adressage 32 bits d'Excel plutôt que par le nombre de lignes et de colonnes ou par la largeur des entiers utilisés pour les compter. Avec ses grilles beaucoup plus grandes, Excel 2007 peut atteindre plus facilement cette limite de mémoire. Les tentatives de contrainte explicite d'une référence de plage dans le code avec xlCoerce ou de contrainte implicite en enregistrant les arguments de la fonction exportée XLOPER en tant que type P ou de XLOPER12 en tant que Q échouent si la plage est trop étendue pour la mémoire disponible. Dans le premier cas, un appel à Excel4 ou Excel12 échoue avec xlretFailed. Dans le dernier cas, Excel renvoie #VALUE! à la cellule appelante.

Pour des raisons de performances, sachant qu'il y a un risque important pour qu'un utilisateur transfère un tableau énorme vers votre complément, vous devez détecter la taille du tableau et le rejeter s'il dépasse une certaine limite, ou bien permettre les arrêts utilisateur des fonctions du classeur via xlAbort.

Gestion des références à très larges plages et recalcul

Dans Excel 2003, une référence peut désigner au plus les 2 puissance 24 cellules d'une feuille de calcul. Même en ne traitant qu'une fraction de ces cellules, vous pourriez réellement bloquer l'ordinateur du point de vue de l'utilisateur. Par conséquent, vous devez vérifier la taille de la plage pour déterminer si vous devez procéder à un traitement plus morcelé et, comme pour le traitement des grands tableaux, détecter les arrêts utilisateur via xlAbort. Avec Excel 2007, les plages peuvent être jusqu'à 1 000 fois plus étendues, d'où l'importance d'une vérification appropriée. Une commande capable de traiter l'intégralité d'une feuille de calcul en une seconde dans Excel 2003 peut, dans des conditions identiques, nécessiter plus de 15 minutes dans Excel 2007.

Autres arguments de fonctions

Excel 2007 permet aux XLL d'exporter des fonctions comprenant jusqu'à 255 arguments. En pratique, une fonction comprenant ce nombre d'arguments, chacun ayant une signification différente, est généralement trop complexe et doit être divisée. Ce nombre d'arguments a beaucoup plus de chances d'être utilisé par une fonction qui traite un nombre variable de types d'entrées similaires.

Recalcul multi-thread

Vous pouvez configurer Excel 2007 afin d'utiliser plusieurs threads pour recalculer les fonctions de feuille de calcul enregistrées comme sécurisées au niveau thread. Ceci peut permettre de réduire le nombre de calculs sur les multiprocesseurs, mais peut aussi être utile sur les ordinateurs à un seul processeur, notamment lorsque des UDF sont utilisés pour accéder à la fonctionnalité sur un serveur multi-thread. Par rapport aux compléments VBA, COM et C#, les XLL présentent l'avantage d'enregistrer leurs fonctions de façon sécurisée au niveau thread.

Accès aux nouvelles commandes et fonctions de feuille de calcul

Les définitions de fonctions énumérées ont été élargies pour inclure toutes les fonctions de feuille de calcul ajoutées depuis Excel 97 (version 9) ainsi qu'un certain nombre de nouvelles commandes. Nouvelles fonctions, comme présentées au tableau 5.

Tableau 5. Nouvelles fonctions

xlfAccrint

xlfCumprinc

xlfImlog10

xlfQuotient

xlfAccrintm

xlfDec2bin

xlfImlog2

xlfRandbetween

xlfAmordegrc

xlfDec2hex

xlfImpower

xlfReceived

xlfAmorlinc

xlfDec2oct

xlfImproduct

xlfRoundbahtdown

xlfAveragea

xlfDelta

xlfImreal

xlfRoundbahtup

xlfAverageif

xlfDisc

xlfImsin

xlfRtd

xlfAverageifs

xlfDollarde

xlfImsqrt

xlfSeriessum

xlfBahttext

xlfDollarfr

xlfImsub

xlfSqrtpi

xlfBesseli

xlfDuration

xlfImsum

xlfStdeva

xlfBesselj

xlfEdate

xlfIntrate

xlfStdevpa

xlfBesselk

xlfEffect

xlfIseven

xlfSumifs

xlfBessely

xlfEomonth

xlfIsodd

xlfTbilleq

xlfBin2dec

xlfErf

xlfIsthaidigit

xlfTbillprice

xlfBin2hex

xlfErfc

xlfLcm

xlfTbillyield

xlfBin2oct

xlfFactdouble

xlfMaxa

xlfThaidayofweek

xlfComplex

xlfFvschedule

xlfMduration

xlfThaidigit

xlfConvert

xlfGcd

xlfMina

xlfThaimonthofyear

xlfCountifs

xlfGestep

xlfMround

xlfThainumsound

xlfCoupdaybs

xlfGetpivotdata

xlfMultinomial

xlfThainumstring

xlfCoupdays

xlfHex2bin

xlfNetworkdays

xlfThaistringlength

xlfCoupdaysnc

xlfHex2dec

xlfNominal

xlfThaiyear

xlfCoupncd

xlfHex2oct

xlfOct2bin

xlfVara

xlfCoupnum

xlfHyperlink

xlfOct2dec

xlfVarpa

xlfCouppcd

xlfIferror

xlfOct2hex

xlfViewGet

xlfCubekpimember

xlfImabs

xlfOddfprice

xlfWeeknum

xlfCubemember

xlfImaginary

xlfOddfyield

xlfWorkday

xlfCubememberproperty

xlfImargument

xlfOddlprice

xlfXirr

xlfCuberankedmember

xlfImconjugate

xlfOddlyield

xlfXnpv

xlfCubeset

xlfImcos

xlfPhonetic

xlfYearfrac

xlfCubesetcount

xlfImdiv

xlfPrice

xlfYield

xlfCubevalue

xlfImexp

xlfPricedisc

xlfYielddisc

xlfCumipmt

xlfImln

xlfPricemat

xlfYieldmat

Nouvelles commandes, comme présentées au tableau 6.

Tableau 6. Nouvelles commandes

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

 

Les commandes présentées au tableau 7 ont été supprimées.

Tableau 7. Commandes supprimées

xlcStart

xlcVbaObjectBrowser

xlcVbaAddWatch

xlcVbaReferences

xlcVbaClearBreakpoints

xlcVbaReset

xlcVbaDebugWindow

xlcVbaStepInto

xlcVbaEditWatch

xlcVbaStepOver

xlcVbaEnd

xlcVbaToggleBreakpoint

xlcVbaInstantWatch

 

Fonctions API C : Excel4, Excel4v, Excel12, Excel12v

Les développeurs XLL expérimentés sont habitués aux anciennes fonctions API C :

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[]);

Dans Excel 2007, le nouveau kit SDK comprend également un module de code source incluant les définitions de deux autres fonctions API C qui fonctionnent de la même manière mais avec des arguments XLOPER12. Lorsque ces fonctions sont appelées dans une version précédente d'Excel, toutes deux renvoient xlretFailed :

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[]);

Les deux fonctions renvoient les mêmes valeurs de succès ou d'erreur que Excel4 et Excel4v dans les versions précédentes, mais dans Excel 2007, les quatre peuvent renvoyer une nouvelle erreur : xlretNotThreadSafe, définie sur 128. Cette erreur est renvoyée lorsqu'une fonction enregistrée comme sécurisée au niveau thread tente d'appeler une fonction qui ne l'est pas, généralement une fonction de feuille macro ou un UDF non fiable.

Fonctions de l'interface du Gestionnaire de compléments

Quelques fonctions xlAuto- utilisent ou renvoient des XLOPER qui fonctionnent comme prévu dans Excel 2007, mais les versions XLOPER12 sont désormais également reconnues. Les fonctions concernées sont :

C++

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

Les nouvelles fonctions sont :

C++

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

Aucune de ces quatre fonctions n'est requise. En leur absence, Excel utilise le comportement par défaut. En présence des versions XLOPER12 dans Excel 2007, celles-ci sont appelées en priorité par rapport aux versions XLOPER. Lorsque vous créez des XLL multi-version, vous devez veiller à ce que les deux versions fonctionnent de manière équivalente.

Types de matrices à virgule flottante

La structure suivante, enregistrée en tant que type K, a été prise en charge dans différentes versions d'Excel. La structure est définie dans le fichier d'en-tête SDK xlcall.h d'Excel 2007 :

C++

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

Voici un exemple du type d'hypothèse susceptible d'entraîner des problèmes. Avant Excel 2007, on pouvait considérer le que le code suivant était fiable, en dépit de son style médiocre :

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;
}

Même si le type FP ne fait pas partie des nouveaux types de données, dans Excel 2007 cette structure accueille des tableaux pouvant occuper toute la largeur de la nouvelle grille (214 colonnes), mais tronqués à 216 lignes maximum. Dans ce cas, la solution est très simple : allouer de manière dynamique un tampon toujours de la bonne taille :

C++

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

Pour accepter plus de 2 puissance 16 lignes, Excel 2007 prend également en charge un nouveau type de données, enregistrées en tant que K% :

C++

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

XLCallVer

La signature de la fonction XLCallVer est la suivante :

C++

int pascal XLCallVer(void);

Dans Microsoft Excel versions 97 à 2003, XLCallVer renvoie 1280 = 0x0500 = 5*256, ce qui désigne la version 5 d'Excel (dernier changement apporté à l'API C). Dans Excel 2007, elle renvoie 0x0C00, ce qui désigne la version 12.

Ceci vous permet de déterminer si le nouvel API C est disponible lors de l'exécution, mais vous pouvez aussi choisir de détecter la version d'exécution d'Excel via Excel4(xlfGetWorkspace, &version, 1, &arg), sachant que arg est une fonction XLOPER numérique définie sur 2 et que version est une chaîne XLOPER qui peut alors être forcée à correspondre à un entier. Ceci s'explique par le fait qu'il existe entre Microsoft Excel 2000, Microsoft Excel 2002 et Excel 2003 des différences que votre complément peut également avoir besoin de détecter. Par exemple, des changements ont été apportés en termes de précision de certaines fonctions statistiques et vous pouvez avoir besoin de les détecter.

Fonctions API C au comportement variable selon les versions

Généralement, les fonctions de feuille de calcul fonctionnent de la même manière d'une version à l'autre, mais les fonctions numériques peuvent en améliorer la précision. Ceci dit, pour les fonctions uniquement API C, les trois fonctions suivantes fonctionnent différemment dans Excel 2007 et dans les versions précédentes.

xlStack

Cette fonction renvoie désormais l'espace de pile réel ou 64 Ko (le plus faible des deux). L'exemple de code suivant indique comment obtenir l'espace de pile, quelle que soit la version.

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

Dans Excel 2003, xlGetHwnd renvoie un XLOPER xltypeInt contenant une abréviation de 2 octets, partie basse du descripteur HWND complet de Windows pour Excel. Le descripteur complet s'obtient via l'API Windows EnumWindows, comme illustré ci-dessous. Dans Excel 2007, lorsque l'appel est effectué via Excel12, le XLOPER12 xltypeInt renvoyé contient un entier signé de 4 octets qui correspond au descripteur complet. Notez que même lorsque l'appel a lieu dans Excel 2007, Excel4 ne renvoie que la partie basse du descripteur.

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

Comme avec xlGetHwnd, lorsque l'appel est effectué via Excel12, le XLOPER12 xltypeInt renvoyé contient le descripteur complet de l'instance en cours d'exécution, tandis que Excel4 renvoie un XLOPER xltypeInt qui ne contient que la partie basse du descripteur.

Personnalisation de l'interface utilisateur

Dans les versions précédentes d'Excel, le code XLL pouvait être utilisé pour personnaliser les barres de menus, les menus, les barres de commande ou les barres d'outils grâce à des commandes telles que xlcAddBar, xlcAddMenu, xlcAddCommand, xlcShowBar, xlcAddToolbar, xlcAddTool et xlcShowToolbar. Ces commandes sont toujours prises en charge, mais les anciennes structures des barres de menus et des barres de commandes ayant été remplacées, elles placent l'accès à vos commandes XLL dans le groupe Complément du ruban et peuvent donc ne pas fournir à vos utilisateurs l'interface qu'ils attendent.

Dans la version Microsoft Office 2007, vous ne pouvez personnaliser l'IU que via le code géré. Une des approches de la personnalisation de l'IU dans Excel 2007 consiste à avoir un complément ou une ressource de code géré distinct, au sein duquel résident les fonctions qui personnalisent l'IU. Vous pouvez alors le coupler étroitement à votre XLL, en rappelant dans votre code XLL d'appeler les commandes et fonctions qu'il contient.

Écriture de XLL inter-version

Les sections suivantes récapitulent comment créer des XLL compatibles sur différentes versions d'Excel.

Définitions utiles

Vous devez envisager d'inclure des définitions comme celles comprises dans le code de votre projet XLL et de remplacer toutes les instances de valeurs littérales utilisées dans ce contexte. Ceci clarifiera nettement le code spécifique à la version et réduira les risques de bogues sous forme de chiffres apparemment inoffensifs.

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

Accès à la version en cours d'exécution

Vous devez détecter quelle version est en cours d'exécution via Excel4(xlfGetWorkspace, &version, 1, &arg), sachant que arg est un XLOPER numérique défini sur 2 et que version est une chaîne XLOPER qui peut alors être forcée à correspondre à un entier. Pour Excel 2007, il s'agit de 12. Vous devez procéder dans, ou à partir de, xlAutoOpen. Vous pouvez également appeler XLCallVer, mais cela n'indique pas quelle version préalable à la 2007 vous utilisez.

Liaison à la bibliothèque xlcall32 et aux fonctions de l'API C

Conformément au projet SDK et Framework Excel 97, la méthode standard de liaison à la bibliothèque xlcall32 consiste à inclure une référence du projet à la bibliothèque d'importation xlcall32.lib. (Il est également possible d'effectuer une liaison explicite à xlcall32.dll lors de l'exécution via LoadLibrary et GetProcAddress). Pour les projets conçus de cette façon, la bibliothèque est liée au moment de la compilation et ses exportations sont prototypées de la manière habituelle. Par exemple :

C++

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

au moment de l'exécution, lorsque le XLL est chargé par Excel, il est implicitement lié à xlcall32.dll.

Tout complément conçu (lié au moment de la compilation) avec une version précédente de la bibliothèque d'importation s'exécute avec Excel 2007, mais n'a pas accès aux rappels Excel12 et Excel12v car ils ne sont pas définis. Le code qui utilise la version SDK Excel 2007 de xlcall.h ainsi que le fichier source C++ [name?].cpp et qui est lié à la version Excel 2007 de xlcall32.lib peut appeler ces fonctions dans toutes les versions récentes d'Excel. Si l'appel est effectué à partir d'une version inférieure à Excel 2007, xlretFailed est renvoyé. Il s'agit simplement d'une valeur à sécurité intégrée, aussi mieux vaut vous assurer que votre code connaît la version en cours pour solliciter le rappel approprié.

Création de compléments exportant des interfaces doubles

Prenons une fonction XLL qui prend une chaîne et renvoie un argument simple pouvant correspondre à n'importe quel type de données de la feuille de calcul ou à une plage. Dans Excel 2003 et Excel 2007, vous pouvez exporter une fonction enregistrée en tant que type RD et prototypée comme suit, sachant que la chaîne est transmise comme une chaîne d'octets à longueur comptée :

C++

xloper * __stdcall my_xll_fn(unsigned char *arg);

Premièrement, ceci fonctionne correctement dans toutes les versions récentes d'Excel, mais conformément aux limites des chaînes de l'ancien API C. Deuxièmement, bien qu'Excel 2007 puisse transmettre et accepter des XLOPER, en interne il les convertit en XLOPER12 ; la surcharge de conversion implicite qui en découle dans Excel 2007 n'existe pas lorsque le code s'exécute dans Excel 2003. Troisièmement, il se peut que cette fonction soit sécurisée au niveau thread, mais si la chaîne type est remplacée par RD$, l'enregistrement échoue dans Excel 2003. Compte tenu de toutes ces raisons, pour satisfaire au mieux vos utilisateurs Excel 2007, vous devez exporter une fonction qui a été enregistrée en tant que UD%$ et prototypée comme suit :

C++

xloper12 * __stdcall my_xll_fn_v12(wchar_t *arg);

Vous pouvez aussi choisir d'enregistrer une autre fonction lors de l'exécution d'Excel 2007 afin de permettre aux fonctions XLL d'accepter jusqu'à 255 arguments (l'ancienne limite est de 30). Par chance, vous pouvez bénéficier des avantages des deux en exportant les deux versions depuis votre projet. Vous détectez alors la version d'Excel en cours d'exécution et, conformément à celle-ci, enregistrez la fonction la plus appropriée.

Il existe différents moyens de gérer les données transmises lors de l'enregistrement des exportations du XLL dans le cadre d'un projet.

Une des méthodes les plus simples consiste à définir une structure de données, appelée ws_func_export_data par exemple, comme dans le code ci-dessous, puis à déclarer et initialiser un tableau de ws_func_export_data qui peut ensuite être utilisé par votre code XLL pour initialiser les XLOPER ou XLOPER12 transmis à xlfRegister. Par exemple :

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;

Notez que quoi que fasse la fonction d'enregistrement avec ces données, seul le nom de la fonction de la feuille de calcul est fourni de manière à ce que les feuilles de calcul ne sachent pas (et n'aient pas à savoir) quelle fonction est appelée. Voici un exemple de fonction appelant des fonctions de bibliothèque standard pour inverser une chaîne de feuille de calcul :

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);}

Vous pouvez ensuite initialiser la structure de cette fonction comme suit :

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 ",
    },
};

Les chaînes ci-dessus sont des chaînes d'octets terminées par un caractère nul. Tout code utilisant ces chaînes pour initialiser les XLOPER doit d'abord les convertir en chaînes à longueur comptée. Les chaînes d'octets doivent également être converties en Unicode si elles sont utilisées pour initialiser les XLOPER12. Vous pouvez aussi initialiser ces chaînes avec un espace de début qu'un autre code peut écraser avec les longueurs des chaînes. Mais ceci peut entraîner des problèmes avec certains compilateurs qui fonctionnent en mode de débogage. Vous pouvez facilement modifier la définition de la structure ci-dessus pour transmettre les chaînes Unicode à Excel lorsque vous exécutez Excel 2007. Ceci oblige également à modifier le code qui a utilisé la structure.

Avec cette approche, une même feuille de calcul exécutée sous Excel 2003 et sous Excel 2007 peut afficher des résultats différents. Par exemple, Excel 2003 établit une correspondance entre la chaîne Unicode d'une cellule de feuille de calcul Excel 2003 et une chaîne d'octets ASCII, puis la tronque avant de la transmettre à l'appel d'une fonction XLL. Excel 2007 transmettra une chaîne Unicode non convertie à une fonction XLL correctement enregistrée. D'où la possibilité de résultat différent. Outre la mise à niveau vers Excel 2007, vous devez être conscient de cette possibilité et de ses conséquences pour vos utilisateurs. Par exemple, certaines fonctions numériques intégrées ont été améliorées entre Excel 2000 et Excel 2003.

Habillage des types de données sous une structure ou classe de conteneur commune

En général, une fonction de feuille de calcul XLL effectue les actions suivantes :

  • Vérification de la validité des entrées.

  • Interprétation des entrées dans leurs contextes mutuels.

  • Renvoi d'erreurs spécifiques lorsque les entrées ne sont pas ce qu'elles devraient être.

  • Remplissage des structures de données.

  • Appel d'un code principal plus approfondi.

  • Traitement de la valeur de retour du code principal et renvoi de quelque chose d'approprié à Excel.

Lorsque vous fournissez deux interfaces exportées, comme indiqué ci-dessus, le fait de devoir dupliquer toute cette logique est loin d'être idéal, mais si les types de données des arguments sont tous différents, quel autre choix avez-vous ? La solution consiste à présenter les types de données Excel dans un conteneur commun. Il existe de nombreuses approches possibles, mais concentrons-nous pour le moment sur les XLOPER et XLOPER12 contenants. La solution présentée ici consiste à créer une classe C++ qui comprenne les XLOPER et les XLOPER12 (avec, dans cet exemple, une instance de chaque). Les exemples suivants présentent une classe C++ cpp_xloper, entièrement décrite dans la deuxième édition du « author's book » cité à la fin de l'article.

La classe doit idéalement disposer d'un constructeur qui, par défaut, crée une copie superficielle d'un initialiseur XLOPER ou XLOPER12 fourni. (Les copies sont superficielles pour accélérer l'interprétation des arguments UDF en lecture seule). Cela doit également offrir des fonctions d'accès pour permettre l'extraction et la modification de type et de valeur. Les fonctions exportées doivent alors convertir leurs arguments XLOPER ou XLOPER12 conformément à ce type commun, appeler une fonction commune qui exécute la véritable tâche et traiter les valeurs de retour de cette fonction. Voici un exemple utilisant la classe cpp_xloper :

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);
}

On suppose que les méthodes ExtractXloper et ExtractXloper12 renvoient respectivement des pointeurs vers un XLOPER et un XLOPER12 statiques sécurisés au niveau thread, avec le nombre approprié d'octets disponibles en mémoire définis aux emplacements requis.

Pour minimiser la surcharge de tout cet habillage, le constructeur doit non seulement effectuer des copies superficielles mais, en interne, il doit reconnaître la version en cours et convertir les XLOPER et XLOPER12 ainsi qu'appeler Excel12 au lieu de Excel4 lors de l'exécution d'Excel 2007. Ceci s'explique par le fait que l'appel de Excel4 dans Excel 2007 entraîne la conversion des arguments XLOPER vers le haut jusqu'aux XLOPER12 ainsi que la conversion de la valeur de retour vers le bas jusqu'à un XLOPER. Le fait d'inciter la classe à utiliser le type et le rappel appropriés évite cette conversion à chaque appel.

Habillage des fonctions API C

Dans la continuité de l'exemple de la section précédente, si my_core_function doit effectuer un rappel dans Excel via l'API C, il doit reconvertir le cpp_xloper en XLOPER ou XLOPER12 puis appeler Excel4 ou Excel12 en fonction de la version en cours d'exécution. Une des solutions possibles consiste à envelopper les fonctions Excel4, Excel4v, Excel12 et Excel12v en cpp_xloper en tant que fonctions de membres de classe. Vous pouvez ensuite réécrire my_core_function comme suit :

C++

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

sachant que cpp_xloper::Excel place directement la valeur de retour dans Arg. Pour ce faire, et pour conserver une certaine flexibilité afin de pouvoir appeler cette fonction avec les arguments XLOPER, XLOPER12 ou cpp_xloper, vous devez créer un certain nombre de fonctions de membres surchargées :

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[]);

Notez que l'on considère que l'appelant des versions à arguments variables de ces fonctions ne mélange pas les types d'arguments. Notez également que l'utilisation de const nécessite ici l'ajout de const aux définitions de Excel4v et Excel12v.

Après l'implémentation d'un tel wrapper, vous n'avez pas à appeler directement les fonctions de l'API C. Cette approche présente également l'avantage de vous permettre de contenir la gestion de mémoire de la valeur renvoyée dans la classe. Si Excel renvoie une chaîne, la classe peut définir un indicateur lui mentionnant d'appeler xlFree avant d'écraser ou de détruire cette instance. Vous pouvez aussi créer des vérifications supplémentaires dans ces wrappers. Par exemple, vous pouvez vérifier que le compte n'est pas inférieur à zéro ou supérieur à la limite spécifique à la version. Dans ce cas, vous avez la possibilité de définir et de renvoyer une erreur supplémentaire :

C++

#define xlretNotCalled      -1   // C API not called

Voici un exemple d'implémentation d'une de ces fonctions, sachant que les variables précédées de m_ correspondent à des variables de membres de classes ; que les indicateurs m_XLtoFree12 et m_XLtoFree indiquent à la classe d'appeler xlFree pour libérer de la mémoire ; et que m_Op et m_Op12 sont respectivement les copies internes des structures de données XLOPER et XLOPER12 de la classe :

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;
}

Nouvelles fonctions de feuille de calcul et fonctions Analysis Toolpak

Contrairement aux versions précédentes d'Excel, Excel 2007 comporte à présent des fonctions ATP (Analysis Toolpak). Avant, un XLL pouvait appeler une fonction ATP à l'aide de xlUDF, de la manière suivante :

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;
}

Dans Excel 2007, vous devez remplacer l'appel dans Excel par un élément de ce type, où gExcelVersion est une variable de type nombre entier dotée d'une portée globale au sein de votre projet et initialisée au cours de l'appel vers xlAutoOpen.

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;

Vous pouvez rendre cette fonction encore plus indépendante de la version et accroître son efficacité dans Excel 2003 et Excel 2007 à l'aide d'un conteneur tel que cpp_xloper. Par exemple :

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;
}

Si vous tentez d'appeler de nouvelles fonctions de feuille de calcul d'API C dans des versions antérieures, vous obtiendrez une erreur de type xlretInvXlfn.

Écriture de XLL sécurisées au niveau des threads et de fonctions de feuille de calcul

Les précédentes versions d'Excel utilisent un même thread pour tous les calculs de feuille de calcul. Dans le cas d'un ordinateur multiprocesseur ou d'un ordinateur à processeur unique que l'utilisateur a explicitement configuré pour qu'il utilise des threads multiples, Excel 2007 tente d'équilibrer la charge entre le thread principal et un ou plusieurs threads supplémentaires que le système d'exploitation répartit sur l'ensemble des processeurs. Dans le cas d'un ordinateur à deux processeurs (ou double cœur), ce phénomène peut au maximum doubler la vitesse des calculs, selon la topologie de l'arborescence de dépendances au sein du ou des classeurs et selon le nombre de fonctions sécurisées au niveau des threads parmi celles impliquées.

Excel 2007 utilise un seul thread (son thread principal) pour appeler l'ensemble des commandes, des fonctions non sécurisées au niveau des threads, des fonctions xlAuto (à l'exception de xlAutoFree) et des fonctions COM et VBA.

Les développeurs XLL peuvent créer des fonctions sécurisées au niveau des threads, à condition qu'ils respectent quelques règles simples :

  • Ne pas appeler les ressources dans d'autres DLL susceptibles de ne pas être sécurisées au niveau des threads.

  • Ne pas effectuer d'appels non sécurisés au niveau des threads via l'API C ou COM.

  • Protéger les ressources susceptibles d'être utilisées simultanément par plusieurs threads utilisant des sections critiques.

  • Utiliser la mémoire locale de thread pour le stockage spécifique des threads et remplacer les variables statiques par des fonctions comportant des variables locales de thread.

Lorsqu'Excel reçoit un élément XLOPER ou XLOPER12 avec xlbitDllFree défini, il appelle xlAutoFree sur le même thread avant d'appeler toute autre fonction sur ce thread. Cela s'applique à toutes les fonctions, sécurisées ou non au niveau des threads, et permet d'éviter le risque qu'une variable XLOPER locale de thread ne soit réutilisée avant que la mémoire qui lui est associée ne puisse être libérée.

Appel de fonctions API C à partir de fonctions sécurisées au niveau des threads

Les fonctions en complément VBA et COM ne sont pas considérées comme étant sécurisées au niveau des threads. Outre les commandes API C (telles que xlcDefineName, qu'aucune fonction de feuille ne calcul n'est autorisée à appeler), les fonctions sécurisées au niveau des threads ne peuvent pas accéder aux fonctions d'information XML. De même, vous ne pouvez pas enregistrer de fonctions XLL sécurisées au niveau des threads en tant qu'équivalents de feuille macro en ajoutant un signe dièse (#) au type « chaîne ». De ce fait, une fonction sécurisée au niveau des threads ne peut pas effectuer les opérations suivantes :

  • Lire la valeur d'une cellule non calculée (y compris la cellule à l'origine de l'appel).

  • Appeler des fonctions telles que xlfGetCell, xlfGetWindow, xlfGetWorkbook et xlfGetWorkspace et autres fonctions d'information.

  • Définir ou supprimer des noms internes à XLL à l'aide de xlfSetName.

La seule exception XLM est xlfCaller, laquelle est sécurisée au niveau de threads. Si l'appelant était une cellule ou une ligne de feuille de calcul, xlfCaller renvoie une référence. Cependant, vous ne pouvez pas forcer cette valeur de référence à l'aide de xlCoerce dans une fonction sécurisée au niveau des threads ; xlretUncalced vous serait retourné. L'enregistrement de la fonction à l'aide de # permet de traiter ce problème, mais dans ce cas, cette fonction est un équivalent de feuille macro, donc non considérée comme étant sécurisée au niveau des threads. En conséquence, les fonctions qui renvoient une valeur précédente (par exemple, en cas de condition d'erreur) ne peuvent pas être sécurisées au niveau des threads.

Notez que toutes les fonctions uniques à l'API C sont toutes sécurisées au niveau des threads :

  • xlCoerce (même si la contrainte des références de cellules non calculées échoue)

  • xlFree

  • xlStack

  • xlSheetId

  • xlSheetNm

  • xlAbort (mis à part qu'elle ne peut pas être utilisée pour supprimer une condition de rupture)

  • xlGetInst

  • xlGetHwnd

  • xlGetBinaryName

  • xlDefineBinaryName

La seule exception est xlSet ; étant un équivalent de commande, elle ne peut pas être appelée par une fonction de feuille de calcul.

Toutes les fonctions de feuille de calcul intégrées à Excel 2007 et leurs équivalents API C sont sécurisés au niveau des threads, à l'exception des suivantes :

  • PHONETIC

  • CELL lorsque les arguments « format » ou « address » sont utilisés

  • INDIRECT

  • GETPIVOTDATA

  • CUBEMEMBER

  • CUBEVALUE

  • CUBEMEMBERPROPERTY

  • CUBESET

  • CUBERANKEDMEMBER

  • CUBEKPIMEMBER

  • CUBESETCOUNT

  • ADDRESS, lorsque le cinquième paramètre (sheet_name) est renseigné

  • Toute fonction de base de données (telles que DSUM ou DAVERAGE) faisant référence à un tableau croisé Excel

  • ERROR.TYPE

  • HYPERLINK

Ressources utilisées par des threads multiples

Vous devez protéger en lecture/écriture la mémoire à laquelle peuvent accéder plusieurs threads à l'aide de sections critiques. Vous devez disposer d'une section critique nommé pour chaque bloc de mémoire que vous souhaitez protéger. Vous pouvez les initialiser au cours de l'appel vers xlAutoOpen, puis les libérer et les définir sur « null » au cours de l'appel vers xlAutoClose. Vous devez contenir chaque accès au bloc protégé à l'aide d'appels vers EnterCriticalSection et LeaveCriticalSection. Un seul thread est autorisé dans la section critique, quel que soit le moment. Ci-après se trouve un exemple d'initialisation, de non initialisation et d'utilisation d'une section appelée 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; 
  } 

Il existe un autre moyen, peut-être plus sûr, de protéger un bloc de mémoire ; il s'agit de créer une classe contenant sa propre CRITICAL_SECTION (section critique) dont le constructeur, le destructeur et les méthodes d'accès sont chargés de son utilisation. Cette approche présente l'avantage de protéger les objets susceptibles d'être initialisés avant l'exécution de xlAutoOpen ou de perdurer suite à l'appel de xlAutoClose. Néanmoins, vous devez veiller à ne pas créer des sections critiques trop nombreuses, ce qui pourrait ralentir le système d'exploitation inutilement.

En cas de code nécessitant un accès simultané à plusieurs blocs de mémoire protégée, vous devez être très attentif quant à l'ordre dans lequel il pénètre et quitte les sections critiques et les quitte. Par exemple, les deux fonctions suivantes peuvent créer un verrou mortel :

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; 
  } 

Si la première fonction sur un thread entre dans g_csSharedTableA alors que la seconde sur un autre thread entre dans g_csSharedTableB, ces deux threads se bloquent. L'approche correcte consiste à entrer dans un ordre cohérent et à quitter dans l'ordre inverse, comme indiqué ci-après :

C++

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

Dans la mesure du possible, du point de vue de la coordination des threads, il est préférable d'isoler l'accès aux blocs distincts, comme indiqué ci-après :

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;
  } 

En cas de conflit majeur pour une ressource partagée, comme par exemple en cas de demandes fréquentes d'accès de courte durée, il est conseillé d'utiliser la faculté de rotation de la section critique. Cette technique permet de réduire la sollicitation du processeur lors de l'attente. Pour ce faire, vous pouvez utiliser InitializeCriticalSectionAndSpinCount (lors de l'initialisation de la section) ou SetCriticalSectionSpinCount (une fois que la section a été initialisée) afin de définir le nombre de boucles que doit effectuer le thread dans l'attente que les ressources soient disponibles. L'opération d'attente étant onéreuse, la rotation permet de l'éviter si la ressource concernée est libérée entre-temps. Dans le cas d'un système à processeur unique, le nombre de rotations est ignoré, mais vous pouvez malgré tout le préciser sans causer le moindre dommage. Le gestionnaire de segments de mémoire utilise un nombre de rotations égal à 4 000. Pour plus d'informations sur l'utilisation de sections critiques, reportez-vous à la rubrique relative aux objets de section critique dans la documentation du Kit de développement Platform SDK.

Déclarer et utiliser la mémoire locale de thread

Prenons l'exemple d'une fonction qui renvoie un pointeur vers un élément XLOPER :

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; 
  } 

Cette fonction n'est pas sécurisée au niveau des threads du fait qu'il est possible qu'un thread renvoie l'élément statique XLOPER alors qu'il est en cours d'écrasement par un autre thread. La probabilité de ce cas de figure est accrue si l'élément XLOPER doit être transmis à xlAutoFree. Pour éviter ce problème, il est possible d'allouer de la mémoire à un élément XLOPER renvoyé et d'implémenter xlAutoFree de sorte que la mémoire XLOPER en elle-même soit libérée :

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 
  } 

Cette approche est plus simple que celle décrite ci-dessous, laquelle se base sur l'API TLS, mais présente deux inconvénients. Premièrement, Excel doit appeler xlAutoFree quel que soit le type d'élément XLOPER renvoyé. Enfin, si l'élément XLOPER qui vient d'être alloué est une chaîne renseignée dans un appel vers Excel4, il n'existe aucun moyen simple d'informer xlAutoFree de la nécessité de libérer la chaîne utilisant xlFree avant d'utiliser la suppression pour libérer p_ret_val, ce qui nécessite que la fonction effectue une copie allouée à DLL.

Pour éviter ces contraintes, il est possible de renseigner et renvoyer un élément XLOPER local de thread, ce qui nécessite que xlAutoFree ne libère pas le pointeur XLOPER en lui-même.

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! 
  } 
  

La question suivante consiste à savoir de quelle manière configurer et récupérer la mémoire locale de thread. Cette opération est effectuée à l'aide de l'API TLS (Thread Local Storage). La première étape consiste à obtenir un index TLS à l'aide de TlsAlloc, lequel doit, au final, être libéré à l'aide de TlsFree. Ces deux opérations ont davantage de succès lorsqu'elles sont effectuées depuis DllMain :

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; 
  } 

Après avoir obtenu cet index, l'étape suivante consiste à allouer un bloc de mémoire à chaque thread. Dans la référence DLL, DllMain recommande d'effectuer cette opération à chaque fois que DllMain est appelé à l'aide d'un événement DLL_THREAD_ATTACH et de libérer la mémoire sur chaque DLL_THREAD_DETACH. Or, en suivant cette recommandation, votre DLL alloue inutilement de la mémoire aux threads qu'Excel n'utilise pas pour le recalcul. En lieu et place, il est préférable d'utiliser une stratégie d'allocation à la première utilisation. En premier lieu, vous devez définir une structure à allouer à chaque thread. Pour l'exemple simple ci-dessus, la structure suivante est suffisante :

C++

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

La fonction suivante obtient un pointeur vers l'instance locale de thread ou en alloue un s'il s'agit du premier appel :

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; 
  } 

À présent, nous pouvons voir de quelle manière la mémoire XLOPER locale de thread est obtenue : En premier lieu, vous obtenez un pointeur vers l'instance du thread de TLS_data, puis vous renvoyez un pointeur vers l'élément XLOPER qu'elle contient :

C++

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

En ajoutant un élément XLOPER12 à TLS_data et une fonction d'accès get_thread_local_xloper12, vous pouvez écrire des versions XLOPER12 de mtr_safe_example.

À l'évidence, mtr_safe_example_1 et mtr_safe_example_2 sont des fonctions sécurisées au niveau des threads que vous pouvez enregistrer en tant que « RP$ » lors de l'exécution d'Excel 2007 et en tant que « RP » lors de l'exécution d'Excel 2003. Vous pouvez créer et enregistrer une version de cette fonction XLL utilisant un élément XLOPER12 en tant que « UQ$ » lors de l'exécution d'Excel 2007, mais vous ne pouvez absolument pas l'enregistrer dans Excel 2003.

Conclusion

Le nouveau Kit de développement XLL sera disponible peu après le lancement de la version Office 2007, laquelle vous permettra de bénéficier de la nouvelle fonctionnalité décrite dans cet article.

À propos de l'auteur

Steve Dalton est le fondateur de Eigensys Ltd. au Royaume-Uni. La société Eigensys travaille dans le domaine du développement d'Excel, spécifiquement appliqué à l'analyse financière. M. Dalton est l'auteur des documents Excel Add-in Development in C/C++: Applications in Finance (Wiley, 2004) (en anglais) et Financial Applications Using Excel Add-in Development in C/C++ (Wiley, 2007) (en anglais).

Cet article a été rédigé en partenariat avec A23 Consulting.

Ressources supplémentaires

Pour en savoir plus sur le développement de compléments dans Excel 2007, reportez-vous aux ressources suivantes :