Показать сообщение отдельно
Старый 26.06.2009, 10:48   #36  
AndyD is offline
AndyD
Участник
КОРУС Консалтинг
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
 
2,560 / 2476 (88) +++++++++
Регистрация: 20.08.2005
Проблемы со свойствами 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;
}
Несколько комментариев
  1. Первый параметр, передаваемый во все функции - интерфейс IDispatch требуемого объекта. Можно получить с помощью вызова com.Interface(). DLL работает только c объектом RANGE
  2. Пары функций Set_Formula() и Get_Formula() позволяет работать как со свойством Formula, так и FormulaR1C1
    Второй параметр - значение DispId требуемой функции (эти значения можно получить с помощью FormulaDispId() и FormulaR1C1DispId()), если передавать 0, то будет использоваться вызов свойства Formula.
    Если необходим массовый вызов функций, то рекомендую сначала получить DispID и сохранить его, а затем подставлять это значение в вызовы, что увеличит скорость работы (не будет постоянных поисков необходимого dispId)
  3. Третий параметр для Set_Formula() - строка, содержащая в себе необходимую формулу на английском языке. Функция возвращает 1 в случае успешного выполнения[/B]
  4. Третий параметр для Get_Formula() - ссылка на буфер, в который будет помещена строка с формулой (или другим значением, содержащимся в ячейке) на английском языке. Функция возвращает размер требуемого буфера (без учета нулевого символа в конце). Можно использовать двумя способами.
    Первый способ - сначала получить размер буфера, передав в третий параметр 0. После этого создать буфер требуемого размера и еще раз сделать вызов, передав уже его в вызов третьим параметром
    Второй способ - создать буфер большого размера и подставлять в вызов его. В этом случае возможно получение исключения, если данные все-таки будут больше
Вложения
Тип файла: zip ComCall.zip (35.3 Кб, 129 просмотров)
__________________
Axapta v.3.0 sp5 kr2
За это сообщение автора поблагодарили: Wamr (5), tolstjak (1), ZVV (2), Antoncheg (1).