Разработка надстроек (XLL-модулей) в Excel 2007

Автор:

  • Стив Далтон (Steve Dalton), компания Eigensys Ltd.

Опубликовано: Октябрь 2006 года

Продукты и технологии: Microsoft 2007 Office Suites, Microsoft Office Excel 2007

Статья посвящена функциям Microsoft Office Excel 2007, касающимся надстроек XLL и предоставляющим новые возможности по созданию XLL-модулей; а также изменениям интерфейса C API для модуля XLL.

На этой странице…

 Разработка XLL-модулей в Excel 2007
Обзор функций Excel 2007, связанных с XLL-модулями Обзор функций Excel 2007, связанных с XLL-модулями
Обзор XLL-модулей Обзор XLL-модулей
Изменения, касающиеся XLL-модулей в Excel 2007 Изменения, касающиеся XLL-модулей в Excel 2007
Создание XLL-модулей, совместимых с разными версиями Excel Создание XLL-модулей, совместимых с разными версиями Excel
Создание поточно-ориентированных XLL-модулей и функций рабочего листа Создание поточно-ориентированных XLL-модулей и функций рабочего листа
Заключение Заключение

Разработка XLL-модулей в Excel 2007

Статья рассчитана на разработчиков, работающих с Microsoft Visual C и Microsoft Visual C++, уже имеющих опыт создания надстроек Microsoft Office Excel, или XLL-модулей. Данная статья не является введением в разработку XLL-модулей, хотя и содержит ее краткий обзор. Чтобы извлечь из этой статьи максимальную пользу, читатели должны быть знакомы с:

  • конструкциями и концепциями языков C и C++. Примеры кодов написаны на языке C++;
  • созданием динамических библиотек, экспортирующих функции;
  • структурой данных XLOPER и другими типами данных Excel, такими, как матричная структура с плавающей точкой (FP);
  • функциями интерфейса диспетчера надстроек, такими, как xlAutoOpen и xlAutoClose;
  • управлением памятью для структуры XLOPER(xlAutoFree, xlFree, а также с использованием xlbitDLLFree и xlbitXLFree).

Обзор функций Excel 2007, связанных с XLL-модулями

Наиболее очевидное изменение в Microsoft Office Excel 2007, связанное с XLL-модулями, состоит в том, что рабочий лист расширен с 224 до 234 ячеек, а точнее, с 256 столбцов x 65 536 строк (28 x 216), до 16 384 x 1 048 576 (214 x 220). Эти новые значения превосходят максимальные значения для целочисленных типов данных, к которым относятся размеры прежних структур - диапазона и массива ячеек. Это изменение требует создания новых структур данных, использующих более длинные целые числа для задания размера диапазонов и массивов.

Максимальное число аргументов функции увеличилось с 30 до 255. Кроме того, XLL-модули могут теперь обмениваться с Excel длинными строками в Юникоде, а не строками байтов ограниченной длины.

Многопоточный пересчет рабочей книги поддерживается как на однопроцессорных, так и на многопроцессорных компьютерах. В отличие от пользовательских функций (User-Defined Functions, UDFs) в Visual Basic для приложений (VBA), пользовательские функции XLL-модулей можно зарегистрировать как поточно-ориентированные. Их исполнение, как и исполнение большинства встроенных функций Excel, можно назначить параллельным потокам, что ускорит вычисления. За это преимущество приходится заплатить некоторыми ограничениями, а также необходимостью соблюдать определенные правила при использовании многопоточных привилегий.

Функции пакета анализа Analysis Toolpaсk теперь полностью встроены в Excel, хотя сама надстройка до сих пор требует применения инструментов анализа данных (Data Analysis tools). Это создает некоторую несовместимость с XLL-модулями, разработанными для более ранних версий, которые вызывают функции Analysis ToolPack с использованием пользовательских функций Excel (xlUDF).

Пользовательский интерфейс также сильно изменился. Настройка пользовательского интерфейса не является темой данной статьи, скажем только, что прежние пользовательские XLL-меню и их пункты действуют, но помещены они не там, где должны находиться функции прежнего интерфейса C API.

Обзор XLL-модулей

Связывание XLL-модулей с Excel поддерживается, начиная с Microsoft Excel 4.0. Кроме того, поддерживается также интерфейс C API, через который XLL-модули получают доступ к функциям и командам Excel. XLL - это название динамических библиотек (DLL), которые содержат ответные вызовы для диспетчера надстроек Excel, а также экспортируемые команды и функции XLL-модулей. Этот интерфейс изменился незначительно по сравнению с Excel 5.0. Многие новые функции Excel 2007 и некоторые функции из прежних версий, которые не поддерживались раньше, доступны в обновленном интерфейсе C API. В данной статье рассматриваются дополнения к новому интерфейсу C API и обсуждаются некоторые переходные моменты, с которыми сталкиваются разработчики.

Корпорация Майкрософт опубликовала пакет SDK для Excel 97 (software development kit, пакет разработчика программного обеспечения), являющийся обновлением прежней версии пакета, включающий следующие компоненты:

  • заголовочный файл C xlcall.h, содержащий определения структур данных, используемых Excel для обмена данными с XLL-модулями; зарегистрированные определения функций и команд, соответствующие встроенным функциям Excel, информационным функциям XML, а также многим командам; прототипы функций ответного вызова Excel Excel4, Excel4v и XLCallVer;
  • статическую библиотеку импорта xlcall32.lib. Из этой библиотеки экспортируются ответные вызовы Excel с использованием соглашения об именовании языка C;
  • проект XLL SDK Framework, включающий полный XLL-проект и ряд подпрограмм для работы с типами данных Excel и вызова функций Excel4 и Excel4v.

Простейший XLL-модуль экспортирует функцию, вызываемую Excel, при загрузке надстройки xlAutoOpen. Эта функция выполняет задачи инициализации и регистрирует все функции и команды, которые экспортирует XLL-модуль. Это приводит к тому, что надстройка вызывает функцию интерфейса C API, эквивалентную XML-функции REGISTER(). Библиотека Excel xlcall32.dll экспортирует функции, позволяющие XLL-модулям выполнить ответный вызов Excel: Excel4 и Excel4v (их названия отражают тот факт, что XLL-функции были впервые введены в четвертой версии), а теперь в Excel 2007 к ним добавлены Excel12 и Excel12v.

Диспетчер надстроек Excel загружает XLL-модули и управляет ими. Он ищет следующие экспортируемые функции:

  • xlAutoOpen, вызываемую при загрузке XLL-модуля. Это идеальное место для регистрации функций и команд XLL, инициализации структур данных и настройки пользовательского интерфейса;
  • xlAutoClose, вызываемую при выгрузке XLL-модуля. Здесь отменяется регистрация функций и команд, освобождаются ресурсы и отменяются настройки интерфейса;
  • xlAutoAdd, вызываемую при активизации или загрузке XLL-модуля во время сессии;
  • xlAutoRemove, вызываемую при деактивации или выгрузке XLL-модуля во время сессии;
  • xlAddInManagerInfo (xlAddInManagerInfo12), вызываемую при первом обращении к диспетчеру надстроек. Если функции передается аргумент = 1, то возвращается строка (имя надстройки), иначе функция должна возвратить “#VALUE!”;
  • xlAutoRegister (xlAutoRegister12), вызываемую, когда REGISTER (из XLM) или xlfRegister (из интерфейса C API) вызывается без указания типов возвращаемого значения функции и аргументов. Она производит поиск в XLL-модуле для регистрации функции с данной информацией;
  • xlAutoFree (xlAutoFree12), вызываемую, когда в Excel возвращается структура XLOPERс установленным флагом, означающим, что она указывает на область памяти, которая должна быть освобождена по запросу XLL-модуля.

Последние три функции получают и возвращают структуры XLOPER. В Excel 2007 они поддерживаются как XLOPER, так и XLOPER12.

Единственная обязательная функция - xlAutoOpen. Без нее XLL-модуль не загрузится. При резервировании памяти или других ресурсов в динамической библиотеке, нужно применить xlFree (xlFree12), чтобы избежать утечки памяти. Для возврата ресурсов системе при выгрузке XLL-модуля нужно применить xlAutoClose. Все остальные функции могут быть опущены.

Данный интерфейс прикладных программ называется C API по следующей причине: Excel обменивается данными, используя некоторые стандартные типы данных C; функции библиотеки используют соглашение об именованиях языка C; и структуры данных соответствуют ANSI C. Обладая соответствующим опытом и применяя язык C++, можно сделать XLL-проект более управляемым, легко читаемым и стабильным. Таким образом, оставшаяся часть статьи требует базового понимания классов языка C++.

Структуры данных, используемые Excel для обмена данными с XLL-модулями, обобщены в таблице 1. В ней также содержатся буквы, обозначающие типы данных и используемые в качестве третьего аргумента функции xlfRegister при регистрации пользовательских функций.

Табл. 1. Структуры данных Excel, используемые для обмена данными с XLL-модулями

Тип данных Передача по значению Передача по ссылке (указателю) Комментарии
Boolean A L короткий (0=”ложь” или 1=”истина”)
Double B E
char * C, F Строка байтов в кодировке ASCII, ограниченная нулем
unsigned char * D, G Строка байтов в кодировке ASCII со счетчиком
[v12+] unsigned short * C%, F% Строка двухбайтовых символов в кодировке Юникод, ограниченная нулем
[v12+] unsigned short * D%, G% Строка двухбайтных символов в кодировке Юникода со счетчиком
unsigned short [int] H DWORD, size_t, wchar_t
[signed] short [int] I M 16 бит
[signed long] int J N 32 бита
FP K Структура массива с плавающей точкой
[v12+] FP12 K% Структура массива с плавающей точкой с большей разрядной сеткой
XLOPER P Значения и массивы типа Variable
R Ссылки на значения, массивы и диапазоны
[v12+] XLOPER12 Q Значения и массивы типа Variable
U Ссылки на значения, массивы и диапазоны

Типы C%, F%, D%, G%, K%, Q и U новые, они появились только в Excel 2007 и не поддерживаются в более ранних версиях. Строковые типы F, F%, G и G% используются для аргументов, модифицируемых «на месте». Когда аргументы пользовательских функций B>XLOPER или XLOPER12 регистрируются как типы P или Q соответственно, Excel при подготовке этих аргументов преобразует ссылки на единичные ячейки в простые значения, а ссылки на множество ячеек - в массивы. Типы P и Q всегда попадают в функцию как один из следующих типов: xltypeNum, xltypeStr, xltypeBool, xltypeErr, xltypeMulti, xltypeMissing или xltypeNil, но не xltypeRef или xltypeSRef, потому что последние всегда преобразуются из ссылок в значения.

Третий аргумент функции xlfRegister type_text - это строка кодов, о которых говорилось выше. Эта строка может также заканчиваться знаком “#”, указывающим, что эта функция эквивалентна листу макросов. Эта строка может также заканчиваться восклицательным знаком “!”, указывающим, что эта функция эквивалентна листу макросов и/или с ней нужно обращаться как с принудительно вычисляемой функцией (volatile). Объявление функций как эквивалентов листа макросов позволяет им принимать значения непересчитываемых ячеек (включая текущие значения вызывающей ячейки или ячеек) и вызывать информационные функции XML. Функции, зарегистрированные как имеющие тип “#” и как принимающие аргументы типа R или U, являются по умолчанию принудительно вычисляемыми.

Excel 2007 позволяет добавлять знак доллара “$” для обозначения того, что функция является поточно-ориентированной. Однако функции листа макросов не считаются поточно-ориентированными. Поэтому к строке типа функции type_text нельзя добавлять одновременно символы “#” и “$”. Попытка XLL-модуля зарегистрировать функцию с символами “#” и “$” закончится неудачей.

Изменения, касающиеся XLL-модулей в Excel 2007

Excel 2007 загружает и выполняет любые надстройки, созданные для более ранних версий. Это не означает, что любой XLL-модуль работает в Excel 2007, как задумано. Нельзя считать XLL-модуль полностью совместимым с Excel 2007, не проверив выполнения некоторых условий. В этом разделе рассматриваются некоторые из явных или неявных допущений, которые могут более не выполняться.

Одно из двух значительных изменений, связанных с появлением новых структур - это введение более длинных разрядных сеток, в связи с чем для описания номеров строк и столбцов используются два новых определения типов данных:

C++

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

Эти 32-битные целые со знаком при использовании в новых структурах XLOPER12 и FP12 заменяют значения в формате WORD (для номеров строк) и в формате BYTE (для номеров столбцов), используемые в диапазонах ячеек XLOPER, а также значения в формате WORD (для номеров строк), используемые в массивах XLOPER и структуре данных FP. Другим значительным изменением является то, что в XLL-модулях теперь поддерживаются строки в кодировке Юникод. XLOPER12 - это попросту структура XLOPER, которая включает типы RW и COL, и в которой строки байтов в формате ASCII заменены на строки в кодировке Юникод.

Новые функции Excel

Функции Пакета анализа теперь входят в Excel 2007. Раньше XLL-модуль вызывал функции надстройки Пакета анализа, используя пользовательские функции ExcelxlUDF. В Excel 2007 нужно заменить такие вызовы, например, вызовом функции xlfPrice. Имеется также много новых функций Excel, которые можно вызвать только в Excel 2007. При их вызове в более ранних версиях интерфейс C API возвращает xlretInvXlfn. Дополнительные сведения см. в разделе Создание XLL-модулей, совместимых с разными версиями Excel.

Строки

Excel 2007 впервые дает XLL-модулям прямой доступ к строкам двухбайтовых символов в кодировке Юникод длиной до 32 767 (215–1) символов. Эти строки поддерживаются в таблицах Excel уже в течение нескольких версий. Это огромное улучшение по сравнению с предыдущим интерфейсом C API, где строки не могли быть длиннее 255 байтов. Строки байтов все еще поддерживаются с помощью типов аргументов C, D, F и G и функции xltypeStr структуры XLOPER и имеют все те же ограничения длины.

В Microsoft Windows преобразование строк байтов в строки Юникода и обратно зависит от языкового стандарта. Это означает, что 255-байтовые символы преобразуются в двухбайтные символы Юникода и обратно способом, который зависит от настроек языкового стандарта системы. В стандарте Юникод каждому коду присваивается уникальный символ, но это не так для расширенных кодов ASCII. Следует помнить об этой зависимости преобразования от языкового стандарта. Например, две несовпадающие строки в кодировке Юникод могут совпасть после преобразования в строки байтов.

В Excel 2007 любая видимая пользователю строка обычно имеет внутреннее представление в кодировке Юникод. Поэтому самый эффективный способ обмена строками в Excel 2007 – это использование строк Юникода. Более ранние версии дают доступ к строкам байтов при работе через интерфейс С API, хотя с двухбайтными строками Юникода можно работать через строки типа Variant. Их можно передать в DLL-модуль или XLL-модуль из VBA или используя COM-интерфейс Excel 2007. При работе в Excel 2007 следует стремиться по возможности использовать строки Юникода.

Строковые типы, доступные используемому в Excel интерфейсу C API

В таблице 2 показаны структуры XLOPER функции xltypeStr интерфейса C API.

Табл. 2. Структуры XLOPER функции xltypeStr интерфейса C API

Строки байтов XLOPER

Строки двухбайтных символов XLOPER12

Все версии Excel

Только Excel 2007 и более поздние

Максимальная длина 255 байтов расширенного ASCII-кода

Максимальная длина 32 767 символов Юникода

Первый байт (без знака) = длина

Первый двухбайтный символ = длина

Внимание: Строка необязательно ограничивается нулем.

В таблице 3 показаны строки C и C++

Табл. 3. Строки C и C++

Строки байтов Строки двухбайтных символов
Ограниченная нулем (char *), тип "C", макс. длина: 255 байтов расширенного ASCII-кода Ограниченная нулем (wchar_t *), тип "C%", макс. длина 32 767 символов Юникода
Со счетчиком длины (unsigned char *), тип "D" Со счетчиком длины (wchar_t *), тип "D%"

Преобразование одного строкового типа в другой

Добавление новых строковых типов в XLL-модули создает возможности для преобразования строк байтов в строки двухбайтных символов или строк двухбайтных символов в строки байтов.

При копировании строк нужно убедиться, что исходная строка не длиннее строки, в которую производится копирование. Возвратится ли ошибка, или строка будет обрезана – вопрос конкретной реализации. На рисунке 1 показаны библиотечные процедуры преобразования и копирования.

Рис. 1. Библиотечные процедуры преобразования и копирования

Следует отметить, что все показанные на рисунке 1 библиотечные функции принимают (максимальную) длину строки в качестве аргумента. Чтобы избежать переполнения буферов Excel, нужно всегда задавать этот аргумент

Примите во внимание следующее:

  • при работе со строками байтов со счетчиком длины, объявленными как [signed] char *, значение длины нужно преобразовать в тип BYTE, чтобы избежать отрицательного результата для строк длиннее 127 символов.
  • при копировании строк максимальной длины, ограниченных нулем, в строки со счетчиком длины не следует копировать последний нулевой символ, поскольку это может вызвать переполнение выделенной области.
  • при резервировании памяти для строк, ограниченных нулем, необходимо зарезервировать место для конечного нулевого символа.
  • при копировании строк со счетчиком длины в строки, ограниченные нулем, необходимо явным образом дописать нулевое окончание, если только не используются функции интерфейса API, такие как lstrcpynA(), которые делают это сами.
  • если важна скорость, и точно известно число копируемых байтов, следует использовать функцию memcpy() вместо strcpy(), strncpy(), wcscpy() или wcsncpy().

Следующие функции безопасно преобразуют строки байтов со счетчиком длины в строки байтов C, ограниченные нулем, и обратно. Первая функция предполагает, что передается достаточно большой буфер конечной строки, а вторая – что буфер имеет длину не более 256 байт (включая байт длины).

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

Применение более длинной разрядной сетки в коде

В Microsoft Office Excel 2003 максимальный размер одноблочного диапазона 224 ячеек, что вполне укладывается в максимальное значение 32-битового целого числа. В Excel 2007 максимальный размер 234 ячеек. Двухгигабайтный лимит памяти, которым ограничены все приложения, исчерпывается простым массивом значений типа double объемом примерно 228 ячеек, поэтому можно безопасно описать размер массива xltypeMulti, или FP, или FP12 32-битовым целочисленным типом со знаком. Однако для того, чтобы безопасно определить размер огромного диапазона, такого, как целая таблица Excel 2007, нужно использовать 64-битовый целочисленный тип, такой, как __int64 или INT64.

Названия диапазонов

В Excel 2007 изменились правила, определяющие, какое название диапазона является правильным, а какое нет. Последний столбец теперь называется XFD. Например, выражение “=RNG1” теперь интерпретируется как ссылка на первую ячейку 371 столбца, если только файл рабочей книги не открыт в режиме совместимости (Compatibility Mode), т.е. при открытии книги формата Excel 2003. Если пользователь сохранит рабочую книгу Excel 2003 в формате Excel 2007, то Excel переопределит имена, например, RNG1, как _RNG1, и предупредит пользователя об этих изменениях. Все названия диапазонов в рабочей книге заменяются, за исключением названий в коде Visual Basic, что нарушает работу кода и, возможно, внешних ссылок на другие файлы. Необходимо проверить все программы, создающие такие названия, проверяющие их правильность и осуществляющие их поиск. Один из способов изменить код так, чтобы он правильно работал в обоих сохраненных форматах, это отыскать названия _RNG1, если RNG1 не определены.

Обработка больших массивов и ситуаций переполнения памяти

В Excel 2007 массивы XLOPER (xltypeMulti), которые преобразованы в типы диапазона XLOPER, ограничены по размеру 32-битовым пространством адресов Excel, а не количеством строк и столбцов или разрядностью целочисленных значений, используемых для их нумерации. Имея увеличенный размер таблиц, Excel 2007 может с большей вероятностью столкнуться с этим ограничением памяти. Попытки явным образом преобразовать ссылку на диапазон внутри кода, используя функцию B>xlCoerce, или неявным образом, зарегистрировав аргументы экспортируемой функции, имеющие тип XLOPER, как аргументы типа P, или аргументы типа XLOPER12 как имеющие тип Q, не удаются, если диапазон слишком велик для имеющейся в распоряжении памяти. В первом случае вызов Excel4 или Excel12 оканчивается неудачей и возвращает xlretFailed. Во втором случае Excel возвращает “#VALUE!” в вызывающую ячейку.

Для обеспечения производительности в случаях, когда пользователь может послать слишком большой массив в надстройку Excel, нужно либо проверять размер массива и отвергать его, если он превосходит некоторое максимальное значение, либо предоставить пользователю возможность прервать выполнение функции, воспользовавшись xlAbort.

Обработка ссылок на большие диапазоны ячеек и их пересчета

В Excel 2003 ссылка могла указывать максимум на 224 ячеек рабочего листа. И даже если обрабатывалась только часть этого диапазона, компьютер мог практически “зависнуть” с точки зрения пользователя. Поэтому нужно было проверять размер диапазона, чтобы решить, обрабатывать ли его частями, или, как и в случае обработки больших массивов, обрабатывать инициированное пользователем прерывание с помощью xlAbort. В Excel 2007 диапазоны могут быть примерно в 1000 раз больше, поэтому тщательная проверка еще более важна. Команда, которая в Excel 2003 могла потребовать одну секунду для обработки целого листа, в Excel 2007 при прочих равных условиях может потребовать более 15 минут.

Увеличенное количество аргументов функций

Excel 2007 позволяет XLL-модулям экспортировать функции с количеством аргументов до 255. В практическом применении функции с таким количеством аргументов, каждый из которых имеет свой собственный смысл, вероятно, слишком сложны, и должны разбиваться на части. Такое количество аргументов, по всей вероятности, может использоваться функциями, обрабатывающими переменное число однотипных входных данных.

Многопоточный пересчет

Можно сконфигурировать Excel 2007 таким образом, чтобы для пересчета функций рабочего листа, зарегистрированных как поточно-ориентированные, использовались параллельные потоки. Это может сократить время пересчета на многопроцессорных компьютерах, но может быть также полезно и для однопроцессорных компьютеров, особенно в случаях применения пользовательских функций для доступа к возможностям многопоточного сервера. Одно из преимуществ XLL-модулей перед надстройками Visual Basic, COM, или C# состоит в том, что они могут регистрировать свои функции как поточно-ориентированные.

Доступ к новым командам и функциям рабочего листа

Объем зарегистрированных определений функций был расширен, и теперь они включают все функции рабочего листа, добавленные после Excel 97 (9 версия), и ряд новых команд. Новые функции показаны в таблице 4.

Табл. 4. Новые функции

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

Новые команды показаны в таблице 5.

Табл. 5. Новые команды

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

Команды, показанные в таблице 6, удалены.

Табл. 6. Удаленные команды

xlcStart xlcVbaObjectBrowser
xlcVbaAddWatch xlcVbaReferences
xlcVbaClearBreakpoints xlcVbaReset
xlcVbaDebugWindow xlcVbaStepInto
xlcVbaEditWatch xlcVbaStepOver
xlcVbaEnd xlcVbaToggleBreakpoint
xlcVbaInstantWatch

Функции интерфейса C API: Excel4, Excel4v, Excel12, Excel12v

Опытным разработчикам XLL-модулей должны быть хорошо знакомы старые функции интерфейса C API:

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

Новый пакет SDK для Excel 2007 включает также модуль исходных программ, содержащий определения еще двух функций интерфейса C API, работающих аналогичным образом, но использующих в качестве аргументов структуры XLOPER12. При вызове этих функций из более ранних версий Excel обе они возвращают 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[]);

Обе функции возвращают те же значения в случаях успеха и ошибки, что и Excel4 и Excel4v в более ранних версиях, но в Excel 2007 все четыре функции могут возвратить новую ошибку - XlretNotThreadSafe, определенную как 128. Она возвращается, когда функция, зарегистрированная как поточно-ориентированная, пытается вызвать функцию, которая не является поточно-ориентированной, обычно функцию листа макросов или небезопасную пользовательскую функцию.

Функции интерфейса диспетчера надстроек

Пара xlAuto-функций принимает или возвращает структуры XLOPER. В Excel 2007 они работают, как и раньше, но распознают теперь и структуру XLOPER12. Функции, о которых идет речь:

C++

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

Новые функции:

C++

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

Ни одна из этих четырех функций не является обязательной, и если они отсутствуют, Excel действует по умолчанию. Если в Excel 2007 присутствуют версии структуры XLOPER12, то при вызовах им отдается предпочтение перед версиями XLOPER. При создании XLL-модулей для разных версий необходимо убедиться, что оба варианта работают одинаково.

Матричные типы с плавающей точкой

Следующая структура, зарегистрированная как тип K, поддерживается в Excel в течение многих версий. Эта структура определена в заголовочном файле xlcall.h пакета SDK для Excel 2007:

C++

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

Приведем пример допущения, которое может вызвать проблемы. До появления Excel 2007 следующий код мог считаться безопасным, хотя и написанным в плохом стиле:

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

Несмотря на то, что тип FP не является одним из новых типов данных, в Excel 2007 данная структура размещает массивы, которые охватывают всю ширину новой сетки (214 столбцов), но имеют ограничение до 216 строк. В данном случае, решение проблемы очень простое: динамическое распределение буфера, всегда имеющего надлежащий размер:

C++

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

Для обеспечения более 216 строк для передачи, в Excel 2007 также поддерживается новый тип данных, зарегистрированный как K%:

C++

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

XLCallVer

Сигнатура для функции XLCallVer выглядит следующим образом:

C++

int pascal XLCallVer(void);

В Microsoft Excel, начиная с 97 и заканчивая 2003-й версиями, XLCallVer возвращает 1280 = 0x0500 = 5*256, что указывает на 5-ую версию Excel (последний раз, когда были внесены изменения в C API). В Excel 2007 возвращается 0x0C00, что аналогично указывает на версию 12 .

Несмотря на то, что это можно использовать для определения доступности нового C API во время выполнения, возможно, предпочтительнее будет определить используемую версию Excel с помощью Excel4(xlfGetWorkspace, &version, 1, &arg), гдеarg – числовая структура XLOPER, заданная как 2, а version - строковая структура XLOPER, которую впоследствии можно привести к целому числу. Это обусловлено различиями между Microsoft Excel 2000, Microsoft Excel 2002 и Excel 2003, определение которых, возможно, потребуется для вашей надстройки. Например, в точность некоторых статистических функций были внесены изменения, и, возможно, это потребуется определить.

Функции C API, которые работают по-разному в зависимости от версии

Как правило, принцип работы функций листа не меняется от версии к версии, хотя числовые функции могут улучшить точность. Однако, что касается функций, относящихся только к C API, следующие три функции в Excel 2007 работают по-другому, нежели в предыдущих версиях.

xlStack

Теперь данная функция возвращает либо фактическое стековое пространство, либо 64 КБайт, в зависимости от того, какое из значений меньше. В следующем образце кода показан способ получения стекового пространства в любой версии.

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

В Excel 2003, xlGetHwnd возвращает xltypeInt XLOPER с двухбайтовым числом (short), являющимся младшей частью полного дескриптора Windows HWND для Excel. Полный дескриптор необходимо получить с помощью Windows API EnumWindows, как показано ниже. В Excel 2007, при вызове с помощью Excel12, возвращенная xltypeInt XLOPER12 содержит четырехбайтовое целое число со знаком, являющееся полным дескриптором. Обратите внимание на то, что даже при вызове в Excel 2007, Excel4 возвращает только младшую часть дескриптора.

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

Как и в случае с xlGetHwnd, при вызове с помощью Excel12, возвращенная xltypeInt XLOPER12 содержит полный дескриптор запущенного экземпляра, тогда как Excel4 возвращает xltypeInt XLOPER, в которой содержится младшая часть дескриптора.

Настройка пользовательского интерфейса

В ранних версиях Excel можно было использовать код XLL для настройки строк меню, самих меню, панелей команд или панелей инструментов с помощью таких команд, как xlcAddBar, xlcAddMenu, xlcAddCommand, xlcShowBar, xlcAddToolbar, xlcAddTool и xlcShowToolbar. Данные команды до сих пор поддерживаются, однако из-за замены структур старых строк меню и панелей команд, они предоставляют доступ к вашим командам XLL в группе Add-in (Надстройки) в пункте Ribbon (Лента), и поэтому могут не обеспечивать пользователей необходимым интерфейсом.

Пользовательский интерфейс можно настраивать только в Microsoft Office версии 2007 с помощью управляемого кода. Одним из подходов к настройке пользовательского интерфейса в Excel 2007 является использование отдельного ресурса с управляемым кодом или надстройки, в которых постоянно хранятся функции для настройки пользовательского интерфейса. Его можно тесно связать с вашим XLL с обратным вызовом в код XLL для вызова команд и функций, содержащихся в нем.

Создание XLL-модулей, совместимых с разными версиями Excel

В следующих разделах приводится обзор способов создания XLL-модулей, совместимых с разными версиями Excel.

Несколько полезных определений постоянных

Необходимо продумать процедуру включения определений, например, определений в коде проекта XLL, и процедуру замены всех экземпляров алгебраических чисел, используемых в данном контексте. Это в значительной степени упростит код, зависящий от версии, и уменьшит вероятность появления ошибок, связанных с версией, в виде безобидно выглядящих чисел.

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

Определение используемой версии

Необходимо определить используемую версию с помощью Excel4(xlfGetWorkspace, &version, 1, &arg), где arg – числовая структура XLOPER, заданная как 2, а version – строковая структура XLOPER, которую впоследствии можно привести к целому числу. Для Excel 2007 – это 12. Это необходимо сделать в xlAutoOpen или из нее. Кроме того, можно вызвать XLCallVer, однако она не покажет, какие из версий, предшествующих 2007, вы используете.

Связь с библиотекой xlcall32 и функциями C API

Стандартным способом организации связи с библиотекой xlcall32, который основан на Excel 97 SDK и проекте Framework, является включение ссылки на библиотеку импорта xlcall32.lib в проект. (Во время выполнения также возможна явная связь с xlcall32.dll с помощью LoadLibrary и GetProcAddress.) В разработанных таким способом проектах связь с библиотекой организовывается во время компиляции, а создание прототипов экспорта библиотеки происходит обычным образом. Например:

C++

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

Во время выполнения, если XLL-модуль загружен приложением Excel, он косвенным образом связывается с xlcall32.dll.

Любая надстройка, которая была создана (связана во время компиляции) с использованием более ранней версией библиотеки импорта, работает с Excel 2007, но не может подключится к ответным вызовам Excel12 и Excel12v, поскольку они не определены. Код, в котором используется xlcall.h версии Excel 2007 SDK и исходный файл C++ [name?].cpp, и который связан с версией xlcall32.lib для Excel 2007, может безопасно вызывать эти функции во всех последних версиях Excel. При вызове из более ранних версий, чем Excel 2007, они просто возвращают xlretFailed. Это реализовано исключительно в целях безопасности, поэтому необходимо обеспечить, чтобы в коде присутствовала информация об используемой версии, и осуществлялись вызовы надлежащих ответных вызовов.

Создание надстроек для экспорта двойственных интерфейсов

Рассмотрим функцию XLL-модуля, которая принимает одну строку и возвращает одиночный аргумент, при этом данный аргумент может быть любым из типов данных на листе или диапазоном. В Excel 2003 и Excel 2007 можно экспортировать функцию, зарегистрированную как тип RD с созданным следующим образом прототипом, где строка передается как строка байтов со счетчиком длины:

C++

xloper * __stdcall my_xll_fn(unsigned char *arg);

Во-первых, это корректно работает во всех последних версиях Excel, но здесь применяются ограничения старых строк C API. Во-вторых, несмотря на то, что Excel 2007 может передавать и принимать XLOPER, он осуществляет их внутреннее преобразование в XLOPER12, в связи с этим в Excel 2007 возникает неявная дополнительная нагрузка по преобразованию, которая отсутствует при выполнении кода в версии Excel 2003. В-третьих, данную функцию можно сделать поточно-ориентированной, но если изменить тип строки на RD$, регистрации в Excel 2003 происходить не будет. Принимая во внимание все упомянутые причины, в идеальном варианте следует экспортировать функцию для пользователей Excel 2007, зарегистрированную как UD%$ с созданным следующим образом прототипом:

C++

xloper12 * __stdcall my_xll_fn_v12(wchar_t *arg);

Еще одной из причин регистрации другой функции при использовании Excel 2007 является то, что она позволяет функциям XLL принимать до 255 аргументов (старый лимит составлял 30 аргументов). К счастью, можно воспользоваться преимуществами обеих версий путем экспорта обеих версий из проекта. Затем можно определить версию используемого приложения Excel и условно зарегистрировать наиболее подходящую функцию.

Существует множество способов управления передаваемыми данными при регистрации экспортов XLL в проекте.

Одним из простых способов является определение структуры данных под названием ws_func_export_data, например, как в коде ниже, с последующим объявлением и инициализацией массива ws_func_export_data, который код XLL затем может использовать для инициализации XLOPER или XLOPER12, передаваемых в xlfRegister. Например:

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;

Обратите внимание на то, что, независимо от того, что функция регистрации делает с этими данными, предоставляется только одно имя функции листа, поэтому листы не знают (и им не нужно знать), какая функция вызывается. Вот пример функции, вызывающей стандартные функции библиотеки для представления строки листа в обратном порядке:

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

После этого можно инициализировать структуру для этой функции следующим образом:

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

Строки выше являются байтовыми строками, ограниченными нулем. В любом коде, в котором они используются для инициализации XLOPER, сначала необходимо преобразовать их в строки со счетчиком длины. Затем их нужно будет преобразовать из байтов в кодировку Unicode, если они используются для инициализации XLOPER12. Также можно инициализировать эти строки предшествующим пробелом, который другой код может переписать и вставить значения длины строк. Однако это может привести к проблемам при использовании некоторых компиляторов в режиме отладки. Можно легко изменить определение структуры выше для передачи строк в Excel при использовании Excel 2007. Для этого также понадобится изменить код, в котором использовалась данная структура.

Такой подход может привести к тому, что на листе, открытом в Excel 2003, могут отображаться результаты, отличающиеся от результатов на таком же листе, но открытом в Excel 2007. Например, Excel 2003 сопоставляет строку Unicode в ячейке Excel 2003 со строкой байтов ASCII и укорачивает ее перед передачей в вызов функции XLL. Excel 2007 передаст строку Unicode без преобразования в функцию XLL, зарегистрированную надлежащим образом. Это может привести к получению различных результатов. Необходимо помнить о такой возможности и ее последствиях для пользователей, а не только для обновления до Excel 2007. Например, некоторые числовые функции улучшались, начиная с версии Excel 2000 и заканчивая Excel 2003.

Объединение типов данных в общий контейнерный класс или структуру

В общем плане, функция листа XLL выполняет следующие действия:

  • Проверка достоверности вводимых данных.
  • Интерпретация вводимых данных в контексте друг друга.
  • Возврат конкретных ошибок, если вводимые данные не соответствуют определенным требованиям.
  • Заполнение структур данных.
  • Вызов более глубокого системного кода.
  • Обработка возвращаемого системным кодом значения и возврат соответствующих данных в Excel.

При предоставлении двух экспортированных интерфейсов, как указано выше, необходимость дублирования всей этой логики далека от совершенства, но, если типы данных аргументов отличаются, какой при этом есть выбор? Ответом является объединение типов данных Excel в общий контейнер. Существует множество возможных подходов, но давайте пока ограничимся объединением структур XLOPER и XLOPER12. Приведенное здесь решение заключается в создании класса C++, понимающего XLOPER и XLOPER12 (и в данном примере присутствуют оба варианта). В примерах ниже приводится обсуждение cpp_xloper класса C++, который подробно описан во втором издании авторской книги, указанной в конце данной статьи.

В идеальном варианте, в классе должен присутствовать конструктор, по умолчанию создающий поверхностную копию инициализатора передаваемой структуры XLOPER или XLOPER12. (Копии являются поверхностными для ускорения интерпретации аргументов пользовательских функций, предназначенных только для чтения.) Он также должен предусматривать функции средства доступа для обеспечения извлечения и изменения типа и значения. При этом экспортированным функциям необходимо только преобразовать свои аргументы XLOPER или XLOPER12 в данный общий тип, вызвать общую функцию, выполняющую фактическую задачу, и обработать возвращаемые данной функцией значения. Ниже представлен пример с использованием класса 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);
 
}

Предполагается, что методы ExtractXloper и ExtractXloper12 возвращают указатели в поточно-ориентированную статическую структуру XLOPER и XLOPER12 соответственно, где при необходимости заданы надлежащие биты освобождения памяти.

Для сведения к минимуму дополнительной нагрузки на всю данную оболочку, конструктор должен создавать не только поверхностные копии, но и на внутреннем уровне распознавать используемую версию, преобразовывать структуры XLOPER в XLOPER12 и вызывать Excel12 вместо Excel4 при использовании Excel 2007. Это обусловлено тем, что в Excel 2007, при вызове Excel4, аргументы XLOPER преобразуются до XLOPER12, а возвращаемое значение преобразуется обратно до XLOPER. Получение класса для использования надлежащего типа и ответного вызова позволяет избежать данного преобразования при каждом вызове.

Объединение функций C API

В продолжение предыдущего раздела, если для функции my_core_function необходим ответный вызов в Excel через C API, она должна преобразовать cpp_xloperобратно в XLOPER или XLOPER12, а затем вызвать либо Excel4, либо Excel12, в зависимости от используемой версии. Одним из решений является объединение функций Excel4, Excel4v, Excel12 и Excel12v в cpp_xloper в качестве компонентных функций класса. После этого функцию my_core_function можно переписать как:

C++

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

где cpp_xloper::Excel помещает возвращаемое значение непосредственно в Arg. Для того чтобы это сделать и обеспечить при этом гибкость для вызова данной функции с помощью аргументов XLOPER, XLOPER12 или cpp_xloper, необходимо создать ряд перегруженных компонентных функций:

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

Примите во внимание: предполагается, что вызывающая сторона версий переменного аргумента данных функций не смешивает типы аргументов. Также необходимо отметить, что использование в данном случае const вызывает необходимость в добавлении const к определениям Excel4v и Excel12v.

После реализации подобной оболочки не следует вызывать функции C API напрямую. Другим преимуществом данного подхода является то, что имеется возможность ограничивать управление памятью возвращенного значения в пределах класса. Если Excel возвращает строку, класс может установить флаг, который обязывает ее вызвать xlFree перед перезаписью или удалением данного экземпляра. В данные оболочки также можно встроить дополнительные проверки. Например, можно проверить, что число больше нуля или выходит за пределы, установленные в версии. В данном случае, возможно, потребуется определить и возвратить дополнительную ошибку:

C++

#define xlretNotCalled      -1   // C API not called

Здесь представлен пример реализации одной и этих функций, где переменные с префиксом m_ являются переменными члена класса; флаги m_XLtoFree12 и m_XLtoFree указывают на класс для вызова xlFree для освобождения памяти; а m_Op и m_Op12 – это внутренние копии класса структур данных XLOPER и XLOPER12, соответственно:

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

Новые функции рабочего листа и функции надстройки Analysis Toolpak

В отличие от предыдущих версий Excel, функции Analysis Toolpak (ATP) встроены в Excel 2007. До этого XLL мог вызывать функцию ATP следующим образом с помощью xlUDF:

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

В Excel 2007 необходимо заменить вызов в Excel чем-нибудь наподобие этого, где gExcelVersion – это целая переменная с глобальным контекстом в пределах проекта и инициализацией при вызове 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;

Можно сделать функцию более независимой от версии и более эффективной как для Excel 2003, так и для Excel 2007 при помощи такого контейнера, как cpp_xloper. Например:

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

При попытке вызова новых функций рабочего листа C API в ранних версиях будет выведено сообщение об ошибке xlretInvXlfn.

Создание поточно-ориентированных XLL-модулей и функций рабочего листа

В предыдущих версиях Excel использовался один поток для всех расчетов на листе. При использовании многопроцессорного или однопроцессорного компьютера, конфигурацию которого пользователь явным образом настроил на использование нескольких потоков, Excel 2007 пытается уравнивать нагрузку между главным потоком и одним или несколькими дополнительными потоками, которые операционная система распределяет по всем процессорам. Если используется компьютер с двумя процессорами (или двухъядерный процессор), это поможет уменьшить время пересчета максимум в 2 раза, в зависимости от топологии дерева зависимостей на листе (листах) и количества используемых поточно-ориентированных функций.

В Excel 2007 используется один поток (главный поток) для вызова всех команд, не поточно-ориентированных функций, функций xlAuto (кроме xlAutoFree) и функций COM и VBA.

Разработчики XLL могут создавать поточно-ориентированные функции при условии соблюдения простых правил:

  • Не следует вызывать ресурсы в других DLL, которые могут быть не поточно-ориентированными.
  • Не следует осуществлять какие-либо не поточно-ориентированные вызовы через C API или COM.
  • Необходимо защищать ресурсы, которые могут одновременно использоваться несколькими потоками, с помощью критических секций.
  • Необходимо использовать локальную память потока для хранения конкретного потока, и заменить статические переменные в функциях локальными переменными потока.

Если для Excel возвращается XLOPER или XLOPER12 с набором xlbitDllFree, он возвращает xlAutoFree в том же самом потоке перед вызовом каких-либо других функций в данном потоке. Это справедливо для всех функций, не зависимо от того, поточно-ориентированные они или нет, и помогает избежать риска повторного использования локальной структуры XLOPER потока, перед тем, как можно будет освободить связанную с ней память.

Вызов функций C API из поточно-ориентированных функций

Функции надстроек VBA и COM не считаются поточно-ориентированными. В дополнение к командам C API (таким как xlcDefineName, которую запрещено вызывать функциям Excel), поточно-ориентированным функциям закрыт доступ к информационным функциям XLM. Также невозможно зарегистрировать поточно-ориентированные функций XLL в качестве эквивалентов листа макросов путем добавления знака «#» к типу строки. Последствия будут выражаться в том, что поточно-ориентированная функция не сможет:

  • Прочитать значение не вычисленной ячейки (включая вызывающую ячейку).
  • Вызвать такие функции, как xlfGetCell, xlfGetWindow, xlfGetWorkbook и xlfGetWorkspace и другие информационные функции.
  • Определить или удалить внутренние имена XLL-модулей с помощью xlfSetName.

Единственным исключением для XLM является поточно-ориентированная функция xlfCaller. Если вызывающей стороной была ячейка листа или диапазон ячеек, xlfCaller возвращает ссылку. Однако невозможно безопасно привести полученную в результате ссылку к значению с помощью xlCoerce в поточно-ориентированной функции, поскольку будет возвращена xlretUncalced. Регистрация функции со знаком «#» позволяет решить эту проблему, однако, в данном случае функция эквивалентна листу макросов, и поэтому не может считаться поточно-ориентированной. Это не позволяет функциям, возвращающим предыдущее значение, например, при наличии каких-либо условий для возникновения ошибки, считаться поточно-ориентированными.

Необходимо отметить, что поточно-ориентированными являются все функции C API:

  • xlCoerce (несмотря на то, что приведение ссылок не вычисленных ячеек заканчивается ошибкой)
  • xlFree
  • xlStack
  • xlSheetId
  • xlSheetNm
  • xlAbort (за исключением того, что ее невозможно использовать для удаления условия для точки прерывания)
  • xlGetInst
  • xlGetHwnd
  • xlGetBinaryName
  • xlDefineBinaryName

Одним из исключений является функция xlSet, которая представляет собой эквивалент команды и не может быть вызвана из какой-либо функции рабочего листа.

Все встроенные функции Excel 2007 и их C API-эквиваленты являются поточно-ориентированными, кроме следующих:

  • PHONETIC
  • CELL при использовании аргументов "format" либо "address"
  • INDIRECT
  • GETPIVOTDATA
  • CUBEMEMBER
  • CUBEVALUE
  • CUBEMEMBERPROPERTY
  • CUBESET
  • CUBERANKEDMEMBER
  • CUBEKPIMEMBER
  • CUBESETCOUNT
  • ADDRESS где указан пятый параметр (sheet_name)
  • Любая функция базы данных (например, DSUM или DAVERAGE), которая относится к Excel PivotTable
  • ERROR.TYPE
  • HYPERLINK

Ресурсы, используемые несколькими потоками

Необходимо защитить оперативную память, доступ к которой могут получать несколько потоков, с помощью критических секций. Для каждого защищаемого блока памяти необходима критическая секция с именем. Такие секции можно инициализировать при вызове xlAutoOpen, и освободить их и задать им нулевое значение при вызове xlAutoClose. Необходимо ограничивать доступ к каждому защищенному блоку с помощью вызова EnterCriticalSection и LeaveCriticalSection. Только одному потоку разрешается находиться в критической секции в любой момент времени. Ниже представлен пример инициализации, деинициализации и использования секции с именем 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;
 
}

Другим, возможно, более безопасным способом защиты блока памяти, является создание класса, содержащего свою собственную CRITICAL_SECTION, при этом методы конструктора, деструктора и средства доступа данного класса будут обеспечивать ее использование. Данный подход имеет дополнительное преимущество в защите объектов, которые могут быть инициализированы до запуска xlAutoOpen или выживать после вызова xlAutoClose, однако следует соблюдать осторожность и не создавать слишком большое число критических секций, что может привести к ненужному замедлению работы операционной системы.

При наличии кода, для которого необходим одновременный доступ к нескольким блокам защищенной памяти, нужно соблюдать осторожность в отношении порядка входа и выхода из критических секций. Например, следующие две функции могут заблокировать друг друга:

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

Если первая функция в одном потоке войдет в g_csSharedTableA в то время, как вторая функция в другом потоке войдет в g_csSharedTableB, оба потока зависнут. Правильным способом будет вход в последовательном порядке и выход – в обратном, например:

C++

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

По возможности, лучше обособить доступ к раздельным блокам, с точки зрения взаимодействия потоков, следующим образом:

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

При значительной конкуренции за общий ресурс, например, при частых запросах кратковременного доступа, необходимо ограничить возможность «прокручивания» критической секции. Такой метод снижает загрузку процессора при ожидании доступа к ресурсу. Для этого можно использовать либо InitializeCriticalSectionAndSpinCount при инициализации секции, либо SetCriticalSectionSpinCount после окончания инициализации секции, чтобы задать количество циклов, выполняемых потоком, до ожидания доступности ресурса. Операция ожидания ресурсоемкая, поэтому прокрутка позволяет избежать таких затрат, пока ресурс освобождается. В однопроцессорных системах число прокруток фактически игнорируется, однако его все же можно указывать без риска возникновения нежелательных последствий. В программе управления динамической областью памяти число прокруток задано как 4000. Дополнительные сведения по использованию критических секций см. в теме Объект критических секций в документации по платформе SDK.

Объявление и использование локальной памяти потока

Например, рассмотрим функцию, возвращающую указатель в 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;
 
}

Данная функция не является поточно-ориентированной, так как существует вероятность того, что один поток вернет статическую XLOPER, в то время как другой перезаписывает ее. Вероятность такого события еще выше, если структуру XLOPER необходимо передать в xlAutoFree. Одним из решений является выделение памяти для возвращаемой XLOPER и реализация xlAutoFree таким образом, чтобы память XLOPER освобождалась сама:

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
 
}

Данный подход проще по сравнению с методом, приведенным ниже, который основывается на TLS API, однако и у него есть несколько недостатков. Во-первых, Excel должен вызывать xlAutoFree независимо от типа возвращаемой XLOPER. Во-вторых, если заново распределенная XLOPER является строкой, внесенной в вызов Excel4, то нет простого способа сообщить xlAutoFree о необходимости освободить строку с помощью xlFree до начала удаления для освобождения p_ret_val, что требует создания функцией выделенной для DLL копии.

Решение, позволяющее обойти данные ограничения, заключается в заполнении и возврате локальной структуры XLOPER потока, причем для данного подхода необходимо, чтобы функция xlAutoFree не освобождала сам указатель XLOPER.

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

Следующий вопрос заключается в том, как настраивать и извлекать локальную память потока. Это осуществляется с помощью API хранилища локального потока (TLS). Сначала необходимо получить индекс TLS с помощью TlsAlloc, который, в конечном итоге, должен быть освобожден посредством TlsFree. Обе задачи лучше всего выполняются из 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;
 
}

После получения индекса необходимо выделить блок памяти для каждого потока. DllMain в справочнике библиотеки динамических ссылок рекомендует выполнять это каждый раз, когда DllMain вызывается событием DLL_THREAD_ATTACH, и освобождать память при каждом DLL_THREAD_DETACH, однако следование данной рекомендации приведет к тому, что DLL будет выполнять ненужное выделение для потоков, которые Excel не использует для пересчета. Вместо этого лучше применять стратегию выделения памяти при первом использовании. Сначала необходимо определить структуру, которую нужно выделить для каждого потока. В качестве простого примера достаточно привести следующий:

C++

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

Следующая функция получает указатель на локальный экземпляр потока или выделяет указатель, если это первый вызов:

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

Теперь можно рассмотреть получение локальной памяти XLOPER потока: Сначала мы получаем указатель на экземпляр потока TLS_data, а затем возвращаем указатель на содержащуюся в нем структуру XLOPER:

C++

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

Добавляя XLOPER12 в TLS_data и функцию доступа get_thread_local_xloper12, можно записать версии XLOPER12 mtr_safe_example.

Как должно быть понятно, mtr_safe_example_1 и mtr_safe_example_2 являются поточно-ориентированными функциями, которые можно зарегистрировать как "RP$" при использовании Excel 2007 и как "RP" при использовании Excel 2003. Можно создать и зарегистрировать версию данной функции XLL, использующей XLOPER12, как "UQ$" при использовании Excel 2007, однако ее невозможно зарегистрировать в Excel 2003.

Заключение

Новый пакет XLL SDK появится вскоре после выхода Office 2007, и тогда можно будет воспользоваться новыми функциональными возможностями, описанными в данной статье.

Об авторе

Стив Далтон является основателем компании Eigensys Ltd., которая находится в Соединенном Королевстве. Eigensys работает в области расширения возможностей Excel, в частности, в сфере финансовой аналитики. Г-н Далтон является автором книг Разработка надстроек для Excel на C/C++ (Excel Add-in Development in C/C++): Приложения в сфере финансов (Applications in Finance) (Wiley, 2004 г.) и Разработка надстроек для Excel с использованием финансовых приложений на C/C++ (Financial Applications Using Excel Add-in Development in C/C++) (Wiley, 2007 г.).

Данная статья была написана совместно с A23 Consulting (на английском языке).

Дополнительные источники

Для получения дополнительных сведений о разработке надстроек в Excel 2007 см. следующие источники (на английском языке):