I need to pass a string longer than 32/64KiB to the clipboard from my program and since the built in CLIPBOARD function in OpenEdge has that as a limit I have to resort to using DLL calls.
The strange thing is that everything works fine... once.. but if I try to do it twice in a program then the program crashes. I'm using OpenEdge 11.3.1 and also tried it in 10.2B which seems to work better but gives a different crash message.
I have tried moving things around, not emptying the clipboard (according to MS I shouldn't empty, but without emptying it doesn't work), changing the OpenClipboard function to use CURRENT-WINDOW:HWND instead of 0 and nothing changes.
As I said everything works fine once and the clipboard is filled with my text.. but if I try to OpenClipboard again the same program then it crashes without fail.
After reading the API manual with a fine-toothed comb I think I have found the problem:
After SetClipboardData is called, the system owns the object identified
by the hMem parameter. The application can read the data, but must not
free the handle or leave it locked until the CloseClipboard function is
called. (The application can access the data after calling
CloseClipboard). If the hMem parameter identifies a memory object, the
object must have been allocated using the GlobalAlloc function with the
GMEM_MOVEABLE flag.
I don't know if there is any way in OpenEdge to allocate global memory so I'm stumped. If I just don't release the memory pointer then I can open the clipboard again but I can't reuse the variable since Progress doesn't understand the variable isn't its own any more. The second SET-SIZE has no effect, even though mRet is a local variable in the function it doesn't seem to get reset with every call to the function.
/* 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.
Yes, there is some limitation in the way Progress handles the clipboard.
There's a note in the online help:
Note: In Windows, the clipboard can store a maximum of 64K of data.
So yes, there's a limitation that will force you to do this another way.
This has most likely something to do with the raw variable being in use when you empty it.
If I remove
SET-SIZE(mRet) = 0.
I'm able to open the clipboard again.
Based on the entry from the Knowledge base (see below) I'm guessing that the dll has already deallocated the memptr and thus there's no need for you to do it (or rather - deallocating again leads to a crash). So simply removing the deallocation should really fix it.
Knowledgebase Entry.
Reading in MSDN you can see that the "system" owns the pointer once SetClipboardData is called. Thus one viable solution must be to create a new pointer each time. Store the pointers in an array, and free them up when quitting.
From MSDN:
If SetClipboardData succeeds, the system owns the object identified by the hMem parameter. The application may not write to or free the data once ownership has been transferred to the system, but it can lock and read from the data until the CloseClipboard function is called. (The memory must be unlocked before the Clipboard is closed.) If the hMem parameter identifies a memory object, the object must have been allocated using the function with the GMEM_MOVEABLE flag.
Full text here
After contacting Progress Support I got a version that works, mostly by bypassing the built in functions in Progress altogether.
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.
Related
If I create new project and I call this string predefined function or extern variables all works fine. But when I continuously modified or did some operation with those string function or variables then those functions are not working properly and shows some junk characters at the UART output.
It may because of the memory issue. please check the buzzer size if it is string array, make sure if are you using string then at end of the buzzer put the null ('\0') terminate.
If you are modifying variable in different process or function , please try to use "volatile " key word for the update.
Thank you.
Ketan
I can't find any explanation of how this call:
DeviceIoControl( aHANDLE,
IOCTL_STORAGE_QUERY_PROPERTY,
& aSTORAGE_PROPERTY_QUERY,
... etc.)
is supposed to work when aSTORAGE_PROPERTY_QUERY.QueryType
is set to PropertyExistsQuery.
This is somehow supposed to tell me whether or not the property,
specified by aSTORAGE_PROPERTY_QUERY.PropertyId,
is available from the object addressed by aHANDLE.
Also, it specifically DOES NOT return any information in the output buffer (where information is returned when QueryType
is set to PropertyStandardQuery).
I find with trial and error that the return value of DeviceIoControl() still indicates success/failure of the function call, and DOES NOT indicate the availability of the property.
So, how does this work?
Also, it specifically DOES NOT return any information in the output buffer (where information is returned when QueryType is set to PropertyStandardQuery).
That is clearly stated in the IOCTRL_STORAGE_QUERY_PROPERTY and STORAGE_PROPERTY_QUERY documentations:
The optional output buffer returned through the lpOutBuffer parameter can be one of several structures depending on the value of the PropertyId member of the STORAGE_PROPERTY_QUERY structure pointed to by the lpInBuffer parameter. These values are enumerated by the STORAGE_PROPERTY_ID enumeration. If the QueryType member of the STORAGE_PROPERTY_QUERY is set to PropertyExistsQuery then no structure is returned.
The optional output buffer returned through the lpOutBuffer parameter of the IOCTL_STORAGE_QUERY_PROPERTY control code can be one of several structures depending on the value of the PropertyId member. If the QueryType member is set to PropertyExistsQuery, then no structure is returned.
The IOCTRL_STORAGE_QUERY_PROPERTY documentation also clearly states the following:
nOutBufferSize
The size of the output buffer, in bytes. It can be zero to determine whether a property exists without retrieving its data. To do that, set this parameter to zero (0) and the QueryType member of the STORAGE_PROPERTY_QUERY input structure to PropertyExistsQuery (1). If the call to DeviceIoControl returns a nonzero value then the property exists.
This contradicts what you claim about the return value of DeviceIoControl() not telling you what you are looking for.
However, it seems that PropertyExistsQuery is not reliable on all systems and devices! It can report incorrect results at times (also, when retrieving a property's STORAGE_DESCRIPTOR_HEADER, that can also report incorrect Size values at times).
The author of the linked article goes on to show how he resorts to not only requesting a property's existence, but also queries its header and data, and then corroborates the results.
The code is in VB, but is can be translated to C/C++.
I'm using the Window's API, in particular the 'WaveIn' functions. I want to know the format that my laptop's input audio device supports.
Therefore, I used the function "waveInGetDevCaps." This function call , once called, fills in a WAVEINCAPS structure with the information about the audio device.
This is my code so far:
procedure TForm1.Button4Click(Sender: TObject);
var
wc : WAVEINCAPS; // structure to be filled with audio device info
begin
waveInGetDevCaps(WAVE_MAPPER, &wc, sizeof(WAVEINCAPS));
Showmessage (wc.dwFormats);
end;
However I keep getting an error:
"E2010 Incompatible types: 'PWaveInCapsA' and 'tagWAVEINCAPSA2"
I would appreciate any help please.
Information on "waveInGetDevCaps" and "WAVEINCAPS" can be found:
https://msdn.microsoft.com/en-us/library/dd743841%28v=vs.85%29.aspx
https://msdn.microsoft.com/en-us/library/dd743839%28v=vs.85%29.aspx
You are using the wrong operator to take the address. You use & in C and C++. In Delphi the operator is #. This operator is documented here: http://docwiki.embarcadero.com/RADStudio/en/Expressions_(Delphi)#The_.40_Operator
In Delphi, & is used to escape keywords. It has no effect here, because wc is not a keyword, and is essentially ignored, treated as whitespace.
Replace & with # and your code will compile. Don't forget to check the return value of the function call for errors, as described in the function documentation.
The Delphi header translations introduce Pascal-case type names so instead of the WAVEINCAPS type it would be idiomatic to use the TWaveInCaps type.
I am new to VB. I am testing some old VB code. The code is as follows -
Public GlobalCommArea() As Byte
...
...
'GlobalCommArea is set to some value
Now, I want to see the contents of this GlobalCommArea variable. (By the way, is it a variable?)
So I tried
outputBox.Text = GlobalCommArea
But the outputBox (which is a textbox) didn't show anything. What should I do to print the contents of GlobalCommArea into the textbox?
The Byte data type is an array of bytes actually. You need to convert it to a string.
Use this to convert it:
outputBox.Text = StrConv(GlobalCommArea, vbUnicode)
Depending on what's stored in GlobalCommArea you may have to change the vbUnicode parameter.
Hope this helps
Is it possible in Visual Basic 6 to make some variable to reference to another variable, so when one changes, so does the other?
I know it is possible to use Set operator for objects. But how to make this work for integer type variables? The only way I am aware of is to wrap the variable inside an object.
Not through the language itself. You could use a class as you mentioned, the other way is to use the Win32 API.
Specifically
HeapAlloc to allocate memory. You will store the returned address in a Long variable.
Then use RTLMoveMemory renamed as CopyMemory to transfer data in and out of the allocated memory.
Public Declare Sub CopyMemory Lib "kernel32" Alias _
"RtlMoveMemory" (Destination As Any, Source As Any, _
ByVal Length As Long)
This website has a more complete example of using pointers in VB6.
I wrote a custom Reference Object class which sounds like it would do exactly what you are looking for. You can read up on it and download it here: http://battaglia.homedns.org/vbguyny/development/visualbasic6/visualbasic6_20070218.htm
try putting variable A in a textbox then make an on change event on the text box., then put the value the text to variable B.
textbox1.text = A
onchgange textbox1
B= textbox1.text
its wat im using., the most easiest way for me
The method of assigning a variable to a text box to set a reference is wrong. It does not do what is stated. Assigning a variable to a text box or assigning a text box to a variable COPIES the content of the text box to the variable. It is not setting a reference to it!