Как вызвать xlcOnTime из xll
В основном, пытаясь реплицировать следующий код VBA в C++, я не хочу, чтобы моя надстройка Excel возвращала массив (вместо этого мне нужен UDF для изменения других ячеек), но вызов Excel4(xlcOnTime, &timer2, 2, &now, cmd) в функции системного таймера имеет код возврата 2, т. е. не удалось установить таймер Excel. Любая идея? Я пытаюсь ограничить мои надстройки до 1 и в коде C++. Я могу решить эту проблему, имея две надстройки, одну на C++ и одну на VBA, но предпочитаю этого не делать.
namespace {
UINT timer1 = 0;
XLOPER timer2;
XLOPER now;
};
//
//this function is xlfRegister as ApplicationOnTime
//
int __stdcall application_on_time()
{
XLOPER ret;
XLOPER ref;
XLOPER value;
//define the destination cells to be changed
ref.xltype = xltypeSRef;
ref.val.sref.count = 1;
ref.val.sref.ref.rwFirst = 10;
ref.val.sref.ref.rwLast = 12;
ref.val.sref.ref.colFirst = 10;
ref.val.sref.ref.colLast = 20;
//the values
value.xltype = xltypeMulti;
value.val.array.columns = 2;
value.val.array.rows = 3;
value.val.array.lparray = new XLOPER[6];
(value.val.array.lparray[0]).xltype = xltypeNum;
(value.val.array.lparray[0]).val.num = 9991;
(value.val.array.lparray[1]).xltype = xltypeNum;
(value.val.array.lparray[1]).val.num = 9992;
(value.val.array.lparray[2]).xltype = xltypeNum;
(value.val.array.lparray[2]).val.num = 9993;
(value.val.array.lparray[3]).xltype = xltypeNum;
(value.val.array.lparray[3]).val.num = 9994;
(value.val.array.lparray[4]).xltype = xltypeNum;
(value.val.array.lparray[4]).val.num = 9995;
(value.val.array.lparray[5]).xltype = xltypeNum;
(value.val.array.lparray[5]).val.num = 9996;
//set the value to the cell
int xlret = Excel4(xlSet, &ret, 2, &ref, &value);
printf("xlret is %d", xlret);
return 0;
}
//xlfRegister as a command TestUserCommand, to be
//called from a button on Excel, with VBA code
//Application.run("TestUserCommand")
int __stdcall test_user_command()
{
LPXLOPER now = new XLOPER;
LPXLOPER cmd = new XLOPER;
cmd->xltype = xltypeStr;
cmd->val.str = XLUtil::MakeExcelString("ApplicationOnTime");
Excel4(xlfNow, now, 0, NULL);
timer2.xltype = xltypeInt;
timer2.val.num = 0;
int xlret = Excel4(xlcOnTime, &timer3, 2, now, cmd);
//xlret is 0, the ApplicationOnTime command would be triggered
//and the cells K11:U13 value get set as expected
printf("xlret is %d", xlret);
return 0;
}
//xlfRegister as a function TestUserFunction, to be
//called from a cell formula on Excel
//=TestUserFunction(1)
char * __stdcall test_user_function(xloper *pxl)
{
static bool first = true;
if (first) {
timer2.xltype = xltypeInt;
timer2.val.num = 0;
first = false;
}
if (timer1 > 0)
{
UINT tmp = timer1;
timer1 = 0;
KillTimer(NULL, tmp);
}
Excel4(xlfNow, &now, 0, NULL);
//set the system timer to triggers the MyTimerProc,
//and MyTimerProc is triggered successfully but no luck
//in setting the second timer that is excel
//inside MyTimerProc
timer1 = SetTimer(NULL, 0, 1, (TIMERPROC)MyTimerProc);
return 0;
}
//this is the procedure referred to in the SetTimer call
void CALLBACK MyTimerProc(HWND hwnd, UINT message, UINT idTimer, DWORD dwTime)
{
if (timer1 > 0)
{
UINT tmp = timer1;
timer1 = 0;
KillTimer(NULL, tmp);
}
LPXLOPER12 cmd = new XLOPER12;
cmd->xltype = xltypeStr;
cmd->val.str = (XCHAR *)XLUtil::MakeExcelString("ApplicationOnTime");
now.val.num = now.val.num + 10.0 / (60.0 * 60.0 * 24.0);
int xlret = Excel4(xlcOnTime, &timer2, 2, &now, cmd);
//xlret is 2, indicates invalid function, no luck. the ApplicationOnTime
//command won't be triggered
//try to call the application_on_time function to set the values to cell
//to no effect
application_on_time();
}
2 ответа
Проблема в том, что ваш код C++ не является точным эквивалентом кода VBA из другого вопроса. В VBA запустить команду по таймеру Excel это
Application.OnTime
который использует вызов Excel OnTime Automation. В C++ у вас есть
Excel4(xlcOnTime, &timer2, 2, &now, cmd);
которая является интерфейсной функцией XLL xlcOnTime
, Excel очень ограничен в вызовах функций XLL; они могут быть вызваны только в определенных контекстах. Например xlcOnTime
может вызываться из определенной пользователем команды Excel, но не может вызываться из обратного вызова таймера Windows.
Что вам нужно сделать, это использовать точный эквивалент кода VBA в C++, что означает использование автоматизации в C++. Есть несколько примеров использования Automation с Excel на C++, доступных в MSDN и других источниках. Они очень часто используют AutoWrap
функция для вызова автоматизации, так что вам нужно что-то вроде
AutoWrap(DISPATCH_METHOD, &result, pXlApp, L"OnTime", 1, COleVariant("ApplicationOnTime"));
Автоматические вызовы не имеют тех же ограничений, что и функции xlc, поэтому такой вызов из обратного вызова таймера Windows будет принят Excel и вашим пользователем. ApplicationOnTime
команда будет выполнена.
Я уверен, что причина в том, что вы не можете вызывать какие-либо функции Excel4 из потока, отличного от вызывающего потока. Поскольку вы перезваниваете изнутри TIMERPROC, вы, вероятно, находитесь в потоке событий или в каком-либо другом потоке времени. Это по общему признанию огромная боль, но это так.