Сбой кучи при доступе к буферу обмена через DLL с использованием OpenEdge

Мне нужно передать строку длиннее 32/64 КБ в буфер обмена из моей программы, и, поскольку встроенная функция CLIPBOARD в OpenEdge имеет ограничение, я должен прибегнуть к использованию вызовов DLL.

Странно то, что все работает нормально... один раз... но если я пытаюсь сделать это дважды в программе, то программа вылетает. Я использую OpenEdge 11.3.1, а также пробовал его в версии 10.2B, которая работает лучше, но выдает другое сообщение о сбое.

Я пытался перемещать вещи, не очищая буфер обмена (согласно MS, я не должен очищать, но без очистки он не работает), изменяя функцию OpenClipboard для использования CURRENT-WINDOW:HWND вместо 0, и ничего не меняется.

Как я уже сказал, все работает нормально один раз, и буфер обмена заполнен моим текстом ... но если я снова попытаюсь открыть ту же программу в OpenClipboard, она обязательно вылетит.

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

После вызова SetClipboardData система становится владельцем объекта, указанного параметром hMem. Приложение может читать данные, но не должно освобождать дескриптор или оставлять его заблокированным, пока не будет вызвана функция CloseClipboard. (Приложение может получить доступ к данным после вызова CloseClipboard). Если параметр hMem идентифицирует объект памяти, объект должен быть выделен с помощью функции GlobalAlloc с флагом GMEM_MOVEABLE.

Я не знаю, есть ли в OpenEdge способ выделить глобальную память, поэтому я в тупике. Если я просто не отпущу указатель памяти, то смогу снова открыть буфер обмена, но не смогу повторно использовать переменную, поскольку Progress не понимает, что переменная больше не принадлежит ему. Второй SET-SIZE не имеет никакого эффекта, хотя mRet является локальной переменной в функции, похоже, что она не сбрасывается при каждом вызове функции.

/* Clipboard Crash Test */
ROUTINE-LEVEL ON ERROR UNDO, THROW.
SESSION:ERROR-STACK-TRACE = TRUE.

PROCEDURE OpenClipboard EXTERNAL 'user32.dll':
    DEFINE INPUT  PARAMETER hWndNewOwner    AS LONG NO-UNDO.
    DEFINE RETURN PARAMETER lRet            AS LONG NO-UNDO.
END PROCEDURE.

PROCEDURE CloseClipboard EXTERNAL 'user32.dll':
    DEFINE RETURN PARAMETER lRet            AS LONG NO-UNDO.
END PROCEDURE.

PROCEDURE EmptyClipboard EXTERNAL 'user32.dll':
    DEFINE RETURN PARAMETER lRet            AS LONG NO-UNDO.
END PROCEDURE.

PROCEDURE SetClipboardData EXTERNAL 'user32.dll':
    DEFINE INPUT  PARAMETER uFormat     AS LONG      NO-UNDO.
    DEFINE INPUT  PARAMETER hMem        AS LONG      NO-UNDO.
    DEFINE RETURN PARAMETER uRet        AS LONG      NO-UNDO.
END PROCEDURE.

FUNCTION SetClipboardText RETURNS LOGICAL (cText AS LONGCHAR):
    DEFINE VARIABLE iRet AS INT64   NO-UNDO.
    DEFINE VARIABLE mRet AS MEMPTR  NO-UNDO.
    DEFINE VARIABLE lRet AS LOGICAL NO-UNDO.

    RUN OpenClipboard(0, OUTPUT iRet).
    IF iRet <> 0 THEN 
    DO:
        RUN EmptyClipboard(OUTPUT iRet) NO-ERROR. 
        SET-SIZE(mRet) = LENGTH(cText,'RAW') + 1.
        PUT-STRING(mRet,1) = cText.
        RUN SetClipboardData(1, GET-POINTER-VALUE(mRet), OUTPUT iRet).
        IF iRet <> 0 THEN lRet = TRUE.
/*      SET-SIZE(mRet) = 0.*/
        RUN CloseClipboard(OUTPUT iRet) NO-ERROR.
    END.
    RETURN lRet.
END FUNCTION.

DEFINE VARIABLE cText AS LONGCHAR NO-UNDO.

ASSIGN cText = 'Text'.

SetClipboardText(cText).

MESSAGE "Clipboard set once." VIEW-AS ALERT-BOX.

ASSIGN cText = 'Newt'.

SetClipboardText(cText).

MESSAGE "Clipboard set twice." VIEW-AS ALERT-BOX.

person matsf    schedule 04.02.2014    source источник
comment
В 11.2 тоже вылетает.   -  person Jensd    schedule 04.02.2014
comment
Обновленный ответ с фактическим обходным путем   -  person Jensd    schedule 04.02.2014


Ответы (2)


Да, есть некоторые ограничения в том, как Progress обрабатывает буфер обмена.

В онлайн-справке есть примечание:

Примечание. В Windows буфер обмена может хранить до 64 КБ данных.

Так что да, есть ограничение, которое заставит вас сделать это по-другому.

Это, скорее всего, как-то связано с необработанной переменной, которая используется, когда вы ее очищаете.

если я удалю

SET-SIZE(mRet) = 0.

Я снова могу открыть буфер обмена.

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

Запись в базу знаний.

Читая в MSDN, вы можете видеть, что «система» владеет указателем после вызова SetClipboardData. Таким образом, одним из жизнеспособных решений должно быть создание нового указателя каждый раз. Сохраните указатели в массиве и освободите их при выходе.

Из MSDN:

Если SetClipboardData завершается успешно, система владеет объектом, идентифицированным параметром hMem. Приложение не может записывать или освобождать данные после того, как право собственности было передано системе, но оно может блокировать данные и считывать их до тех пор, пока не будет вызвана функция CloseClipboard. (Память должна быть разблокирована перед закрытием буфера обмена.) Если параметр hMem идентифицирует объект памяти, этот объект должен быть выделен с помощью функции с флагом GMEM_MOVEABLE.

Полный текст здесь

person Jensd    schedule 04.02.2014
comment
Я знаю об этом примечании, и в Windows действительно было максимальное хранилище данных 64 КБ.. 98. Они исправили это в Windows ME, и с тех пор каждая версия может иметь до 2 ГБ или около того. Однако встроенная функция буфера обмена по-прежнему имеет ограничения, поэтому мне пришлось столкнуться с проблемой использования DLL: s. И OpenEdge 11.3, и 10.2 являются 32-разрядными, так что этого быть не должно, но, поскольку я поражен, что это работает в 10.2, а не в 11.3, я не знаю, что это может быть. И сам код работает... до тех пор, пока вы открываете буфер обмена только один раз. - person matsf; 04.02.2014
comment
@user3270728 user3270728 Я изменил свой ответ, указав реальное исправление (вроде...). - person Jensd; 04.02.2014
comment
Хммм.. Я не думаю, что это действительно работающее решение, так как мне нужна одна глобальная переменная каждый раз, когда я хочу что-то скопировать в буфер обмена. И эта сумма будет переменной. - person matsf; 04.02.2014
comment
У вас может быть массив raws , каждый раз используя новый и освобождая их все в конце. Может работать, но это очень дрянное решение... - person Jensd; 04.02.2014
comment
Последний комментарий от меня. Скорее всего, нет необходимости (или совершенно неправильно) освобождать память, поскольку об этом уже позаботились. - person Jensd; 04.02.2014
comment
Хм... да, но если я не разыменую ее, я не смогу повторно использовать эту переменную, потому что Progress, похоже, не понимает, что это не их собственная переменная. Обновил мой вопрос новым кодом. - person matsf; 04.02.2014
comment
Странно, но я думаю, что это решаемо. Если я возьму ваш код и закомментирую второй вызов функции, я смогу запустить код несколько раз без сбоев. Что-то не так с выделением или освобождением. Сбой также регистрируется в средстве просмотра событий, возможно, эта информация будет полезна для тех, кто знаком с Windows API. - person Jensd; 04.02.2014

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

FUNCTION SetClipboardText RETURNS LOGICAL (cText AS LONGCHAR):
    DEFINE VARIABLE iRet AS INT64   NO-UNDO INIT 0.
    DEFINE VARIABLE mRet AS MEMPTR  NO-UNDO.
    DEFINE VARIABLE lRet AS LOGICAL NO-UNDO INIT FALSE.
    DEFINE VARIABLE iHnd AS INT64   NO-UNDO INIT 0.
    DEFINE VARIABLE iPtr AS INT64   NO-UNDO INIT 0.

    /* Open the clipboard for processing */    
    RUN OpenClipboard(0, OUTPUT iRet).
    IF iRet <> 0 THEN 
    DO:
        /* Tell the clipboard to clear itself */
        RUN EmptyClipboard(OUTPUT iRet) NO-ERROR. 
        /* Globally allocate memory for the clipboard data */
        RUN GlobalAlloc(2, LENGTH(cText,'RAW') + 1, OUTPUT iHnd).
        RUN GlobalLock(iHnd, OUTPUT iPtr).
        /* Assign the global memory to the memory pointer */
        SET-POINTER-VALUE(mRet) = iPtr.
        /* Copy the supplied value to the global memory region */
        PUT-STRING(mRet,1) = cText.
        /* Unlock the memory so that clipboard can read it */
        RUN GlobalUnlock(iHnd, OUTPUT iPtr).
        /* Tell the clipboard to copy the data */        
        RUN SetClipboardData(1, iHnd, OUTPUT iRet).
        IF iRet <> 0 THEN lRet = TRUE.
        /* Close the clipboard */
        RUN CloseClipboard(OUTPUT iRet) NO-ERROR.
        /* Free the memory once the clipboard is closed */
        IF iHnd <> 0 THEN
            RUN GlobalFree(iHnd, OUTPUT iRet).
    END.
    RETURN lRet.
END FUNCTION.
person matsf    schedule 26.02.2014