堆使用OpenEdge通过DLL访问剪贴板

时间:2014-02-04 12:16:12

标签: clipboard progress-4gl openedge

我需要将一个长度超过32 / 64KiB的字符串从我的程序传递到剪贴板,因为OpenEdge中内置的CLIPBOARD函数将其作为限制,我不得不求助于使用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.

2 个答案:

答案 0 :(得分:0)

是的,Progress处理剪贴板的方式有一些限制。

在线帮助中有一个注释:

注意:在Windows中,剪贴板最多可以存储64K的数据。

所以是的,有一个限制会迫使你以另一种方式做到这一点。

这很可能与您清空时正在使用的原始变量有关。

如果我删除

SET-SIZE(mRet) = 0.

我可以再次打开剪贴板。

根据知识库中的条目(见下文),我猜测dll已经解除了memptr的分配,因此不需要你这样做(或者更确切地说 - 再次释放会导致崩溃)。所以简单地删除释放应该真正解决它。

Knowledgebase Entry.

在MSDN中读取,您可以看到一旦调用了SetClipboardData,“system”就拥有了指针。因此,一个可行的解决方案必须是每次都创建一个新的指针。将指针存储在一个数组中,并在退出时释放它们。

来自MSDN:

  

如果SetClipboardData成功,则系统拥有hMem参数标识的对象。一旦所有权转移到系统,应用程序可能不会写入或释放数据,但它可以锁定和读取数据,直到调用CloseClipboard函数。 (在剪贴板关闭之前必须解锁内存。)如果hMem参数标识了一个内存对象,则必须使用带有GMEM_MOVEABLE标志的函数分配该对象。

Full text here

答案 1 :(得分:0)

在联系Progress Support之后,我得到了一个有效的版本,主要是通过完全绕过正在进行的内置函数。

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.