Проблемы со свойствами Formula и FormulaR1C1 - в способе их вызова из Ax
Все функции из Com-объектов вызываются не напрямую, а через специальный интерфейс IDispatch, в котором реализуется пара методов IDispatch::GetIdOfNames и IDispatch::Invoke. Первый метод по имени функции возвращает ее идентификатор (DispId), а второй - вызывает по этому идентификатору функцию выполнения.
В обоих этих методах в качестве входного параметра присутствует языковый идентификатор - LCID. И Excel, как многоязыковая система, использует этот идентификатор для определения используемого языка. Т.е., если вызвать Formula() с параметром языка 0x0409 (en-Us), то функция внутри будет оперировать с передаваемыми данных, считая их англоязычными, если передать 0x0419 (ru), то, соответственно, русскоязычными.
Dax при вызове подставляет константу LOCALE_USER_DEFAULT, что соответствует языку интерфейса по умолчанию, что для русского интерфейса Windows аналогично вызову Invoke() с параметром 0x0419 (ru) - отсюда и проблемы с интерпретацией буфера. Excel требует вводить имена функций на русском.
В VBA при вызове подставляется другая константа LOCALE_NEUTRAL (0x0000). Эту константу Excel интерпретирует как код языка по умолчанию и требует вводить английские (точнее, американские) имена функций.
Фактически, свойства Formula и FormulaR1C1 позволяют использовать любой язык, поддерживаемый Excel
Свойства FormulaLocal и FormulaR1C1Local игнорируют передаваемый LCID и всегда используют LOCALE_USER_DEFAULT, что и приводит к одинаковым результатам при вызове из AX всех этих свойств
Ну и в заключение, я набросал небольшую DLL, с помощью которой можно делать языконезависимый вызов свойств Formula и FormulaR1C1 из Ax
X++:
static void ExcelFormula(Args _args)
{
COM xlApp;
COM wbk;
COM rng;
COMVariant cv;
int iDispatch;
DLL _DLL = new DLL("ComCall.dll");
//Получение DispId для свойства Formula
DllFunction FormulaDispId = new DllFunction(_dll, "FormulaDispId");
//Получение DispId для свойства FormulaR1C1
DllFunction FormulaR1C1DispId = new DllFunction(_dll, "FormulaR1C1DispId");
//Установка свойства Formula для указанного объекта Range
DllFunction SetFormula = new DllFunction(_dll, "Set_Formula");
//Получение свойства Formula для указанного объекта Range
//первый вызов - для получения размера используемого буфера результата
DllFunction GetFormulaLen = new DllFunction(_dll, "Get_Formula");
//второй вызов - для получения свойства
DllFunction GetFormula = new DllFunction(_dll, "Get_Formula");
int dispId;
int dispIdR1C1;
Binary buf;
int len;
ComVariant var;
;
FormulaDispId.returns(ExtTypes::DWord);
FormulaDispId.arg(ExtTypes::DWord);
FormulaR1C1DispId.returns(ExtTypes::DWord);
FormulaR1C1DispId.arg(ExtTypes::DWord);
SetFormula.returns(ExtTypes::DWord);
SetFormula.arg(ExtTypes::DWord, ExtTypes::DWord, ExtTypes::String);
//Для получения размера буфера
GetFormulaLen.returns(ExtTypes::DWord);
GetFormulaLen.arg(ExtTypes::DWord, ExtTypes::DWord, ExtTypes::Dword);
//Для получения результата буфера
GetFormula.returns(ExtTypes::DWord);
GetFormula.arg(ExtTypes::DWord, ExtTypes::DWord, ExtTypes::Pointer);
xlApp = new COM ('Excel.Application');
wbk = xlApp.Workbooks();
wbk = wbk.Add();
rng = xlApp.Range('A1');
iDispatch = rng.interface();
dispId = FormulaDispId.call(iDispatch);
dispIdR1C1 = FormulaR1C1DispId.call(iDispatch);
SetFormula.Call(iDispatch, dispId, '=sum(B1:D1)');
cv = rng.Text();
print cv.bStr();
len = GetFormulaLen.call(iDispatch, dispId, 0);
buf = New Binary(len+1);
GetFormula.call(iDispatch, dispId, buf);
print "dll " + buf.string(0);
len = GetFormulaLen.call(iDispatch, dispIdR1C1, 0);
buf = New Binary(len+1);
GetFormula.call(iDispatch, dispIdR1C1, buf);
print "dll R1C1 " + buf.string(0);
var = rng.formula();
print "Dax " + var.bStr();
rng = xlApp.Range('A2');
rng.Formula('=sum(B1:D1)');
cv = rng.Text();
print cv.bStr();
wbk.Close(False); // чтобы не оставался скрытый экземпляр Excel
xlApp.Quit();
pause;
}
Несколько комментариев
- Первый параметр, передаваемый во все функции - интерфейс IDispatch требуемого объекта. Можно получить с помощью вызова com.Interface(). DLL работает только c объектом RANGE
- Пары функций Set_Formula() и Get_Formula() позволяет работать как со свойством Formula, так и FormulaR1C1
Второй параметр - значение DispId требуемой функции (эти значения можно получить с помощью FormulaDispId() и FormulaR1C1DispId()), если передавать 0, то будет использоваться вызов свойства Formula.
Если необходим массовый вызов функций, то рекомендую сначала получить DispID и сохранить его, а затем подставлять это значение в вызовы, что увеличит скорость работы (не будет постоянных поисков необходимого dispId)
- Третий параметр для Set_Formula() - строка, содержащая в себе необходимую формулу на английском языке. Функция возвращает 1 в случае успешного выполнения[/B]
- Третий параметр для Get_Formula() - ссылка на буфер, в который будет помещена строка с формулой (или другим значением, содержащимся в ячейке) на английском языке. Функция возвращает размер требуемого буфера (без учета нулевого символа в конце). Можно использовать двумя способами.
Первый способ - сначала получить размер буфера, передав в третий параметр 0. После этого создать буфер требуемого размера и еще раз сделать вызов, передав уже его в вызов третьим параметром
Второй способ - создать буфер большого размера и подставлять в вызов его. В этом случае возможно получение исключения, если данные все-таки будут больше