DSOFramer закрывает Excel документ в другом окне. Если несохраненные данные в файле, dsoframer не открывается с «Попытка получить доступ к неверному адресу» - PullRequest
4 голосов
/ 31 октября 2008

Я использую элемент управления Microsoft's DSOFramer, чтобы позволить мне встроить файл Excel в мой диалог, чтобы пользователь мог выбрать свой лист, а затем выбрать диапазон ячеек; он используется с кнопкой импорта в моем диалоговом окне.

Проблема в том, что когда я вызываю функцию DSOFramer's OPEN, если я открываю Excel в другом окне, он закрывает документ Excel (но оставляет его в рабочем состоянии). Если документ, который он пытается закрыть, содержит несохраненные данные, я получаю диалоговое окно, закрывающее документ Excel в другом окне. Если несохраненные данные в файле, dsoframer не открывается с окном сообщения: Attempt to access invalid address.

Я собрал исходный код и прошел через него, и он выполняет вызов в своей функции CDsoDocObject::CreateFromFile, вызывая BindToObject для объекта класса IMoniker. HR - это 0x8001010a The message filter indicated that the application is busy. В случае этой ошибки он пытается InstantiateDocObjectServer на classid из CLSID Microsoft Excel Worksheet ... это происходит с HRESULT из 0x80040154 Class not registered. InstantiateDocObjectServer просто вызывает CoCreateInstance на classid, сначала с CLSCTX_LOCAL_SERVER, затем (если это не удается) с CLSCTX_INPROC_SERVER.

Я знаю, DSOFramer - это популярный пример проекта для встраивания приложений Office в различные диалоги и формы. Я надеюсь, что у кого-то еще была эта проблема и, возможно, есть некоторое представление о том, как я могу решить эту проблему. Я действительно не хочу, чтобы он закрывал любые другие открытые документы Excel, и я действительно не хочу, чтобы он выводил ошибку, если он не может закрыть документ из-за несохраненных данных.

Обновление 1: я пытался изменить classid, который был передан, на Excel.Application (я знаю, что класс разрешится), но это не сработало. В CDsoDocObject он пытается открыть ключ HKEY_CLASSES_ROOT\CLSID\{00024500-0000-0000-C000-000000000046}\DocObject, но не удается. Я визуально подтвердил, что ключ отсутствует в моем реестре; Ключ указан для руководства, но здесь нет подключа DocObject. Затем он выдает сообщение об ошибке: The associated COM server does not support ActiveX document embedding. Я получаю похожие (разные ключи, конечно) результаты, когда пытаюсь использовать Excel.Workbook programid.

Обновление 2: я попытался запустить второй экземпляр Excel, надеясь, что моя автоматизация свяжется с ним (будучи последним вызванным) вместо проблемного экземпляра Excel, но, похоже, этого не произошло Результаты были одинаковыми. Кажется, моя проблема сводится к следующему: я вызываю BindToObject для объекта класса IMoniker и получаю 0x8001010A (RPC_E_SERVERCALL_RETRYLATER) The message filter indicated that the application is busy. Я пытался играть с флагами, переданными на BindToObject (через SetBindOptions), но, похоже, ничего не изменилось.

Обновление 3: Сначала он пытается выполнить привязку с помощью класса IMoniker. Если это не удается, он вызывает CoCreateInstance для clsid как fallback метод. Это может работать для других объектов MS Office, но когда это Excel, класс предназначен для рабочего листа. Я изменил образец на CoCreateInstance _Application, затем получил рабочие книги, затем назвал Workbooks::Open для целевого файла, который возвращает объект Worksheet. Затем я вернул этот указатель и слил обратно с исходным путем образца кода. Все работают сейчас.

Ответы [ 3 ]

0 голосов
/ 17 ноября 2008

@ Jinjin

  1. Вы можете использовать директиву #import для импорта файла Excel вашего OLB. это должно сгенерировать (и автоматически включить файл Excel .tlh, который содержит структуры для _Application (и все остальное, что вам нужно)). В идеале вы должны найти файл OLB, который соответствует самой ранней версии Excel, которую вы хотите поддерживать. Вероятно, файл в вашей локальной системе находится в папке c: \ Program Files \ Microsoft Office \ Office12 (предполагается, что у вас установлен Office 2007). Он может называться Excel.olb или XL5EN32.OLB (иначе, очевидно, если вы не установили версию Excel на английском языке для США.
    Итак, скопируйте файл .olb в исходный каталог вашего проекта, затем в верхней части исходного файла добавьте строку для #import "XL5EN32.olb".

  2. Да, открывает более старые версии. Лучший способ гарантировать, что это так и будет, - найти файл OLB (упомянутый в пункте 1 выше), который установлен при установке Excel, самой ранней версии, которую вы хотите поддерживать. Я использую Excel9.olb из Office 2000. Хорошо работает с моим тестированием версий Excel вплоть до последних версий Office 2007.

  3. Да, вы должны использовать dsoframer обычно после внесения этих изменений.

  4. Боюсь, я, вероятно, не смогу этого сделать из-за ограничений моего работодателя. Однако, если вы возьмете «стандартный» проект dsoframer, внесете изменения, описанные в части 1 этого поста, и изменения, которые я описал в моем предыдущем посте, вы в значительной степени воссоздали именно то, что у меня есть.

0 голосов
/ 18 ноября 2008

@ Jinjin: вы поместили оператор импорта (#import "XL5EN32.olb") в файл cpp, где вы используете приложение Excel :: _? Если нет, сделайте это ... не можете просто добавить его в проект. Если вы уже сделали это, попробуйте также добавить эту инструкцию в файл cpp, где вы используете эти сопоставления #import "Debug \ XL5EN32.tlh". Файл tlh - это заголовок, который генерируется запуском #import; вы должны найти его в своем каталоге Debug (при условии, что вы выполняете сборку Debug).

Переименование _Применение к приложению (и другим) - неправильный путь. Структура _Application - это та, которая имеет сопоставления. Вот почему вы не можете найти приложение-> get_Workbooks.

В каком файле вы находите приложение, но не _Application?

0 голосов
/ 15 ноября 2008

Если вы используете проект DSOFRAMER, вам нужно добавить этот код в dsofdocobj.cpp в функции CreateFromFile, примерно в строке 348:

CLSID clsidExcelWS;
hr = CLSIDFromProgID(OLESTR("Excel.Sheet"),clsidExcelWS);                   
if (FAILED(hr)) return hr;

if (clsid == clsidExcelWS)
{
    hr = InstantiateAndLoadExcel(pwszFile, &pole);
    if (FAILED(hr)) return hr;
}
else
{
    <the IMoniker::BindToObject call and it's failure handling from the "stock" sample goes here>
}

Затем определите следующую новую функцию-член в CDsoDocObject:

////////////////////////////////////////////////////////////////////////
// CDsoDocObject::InstantiateAndLoadExcel (protected)
//
//  Create an instance of Excel and load the target file into its worksheet
//
STDMETHODIMP CDsoDocObject::InstantiateAndLoadExcel(LPWSTR pwszFile, IOleObject **ppole)
{
    IUnknown *punkApp=NULL;
    Excel::_Application *app=NULL;
    Excel::Workbooks *wbList=NULL;
    Excel::_Workbook *wb;

    CLSID   clsidExcel;
    HRESULT hr = CLSIDFromProgID(OLESTR("Excel.Application"), &clsidExcel);
    if (FAILED(hr)) 
        return hr;

    hr = CoCreateInstance(clsidExcel, NULL, CLSCTX_LOCAL_SERVER,  IID_IUnknown, (void**)&punkApp);
    if (SUCCEEDED(hr)) 
    {
        hr = punkApp->QueryInterface(__uuidof(Excel::_Application),(LPVOID *)&app);
        if (SUCCEEDED(hr))
        {
            hr = app->get_Workbooks(&wbList);

            VARIANT vNoParam;
            VariantInit(&vNoParam);
            V_VT(&vNoParam) = VT_ERROR;
            V_ERROR(&vNoParam) = DISP_E_PARAMNOTFOUND;

            VARIANT vReadOnly;
            VariantInit(&vReadOnly);
            V_VT(&vReadOnly) = VT_BOOL;
            V_BOOL(&vReadOnly) = VARIANT_TRUE;

            BSTR bstrFilename = SysAllocString(pwszFile);

            hr = wbList->Open(bstrFilename, vNoParam,vNoParam,vNoParam,vNoParam,vReadOnly,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,0,&wb);
            if (SUCCEEDED(hr))
                hr = wb->QueryInterface(IID_IOleObject, (void**)ppole);

            VariantClear(&vReadOnly);
            VariantClear(&vNoParam);
            SysFreeString(bstrFilename);
        }
    }

    if (wb != NULL) wb->Release();
    if (wbList != NULL) wbList->Release();
    if (app != NULL) app->Release();
    if (punkApp != NULL) punkApp->Release();

    return hr;
}
...