![]() |
![]() |
|
![]() |
Impossible ObjectPAL Techniques Multitasking, Hard to Call DLLs and Status Messages © 1994 - 2002 Mark Pauker Important Note From The Editor Recently, this article was graciously forwarded to the community (via Vladimir Menkin) by Mark Pauker. However, the article was written back in 1994 and the download includes files dated from 1993 through 1996. It's quite possible that the things you read below will not work under later versions of Paradox and/or with later versions of the BDE and Windows. Please test the concepts below in test environments and not on real/live data or code! Thanks Mark for everything you've contributed to the Paradox community! Multitasking Windows is a non-preemptive multitasking environment. This means that, as long as all programs are well behaved, multiple programs can be running at the same time. The reason why programs need to be well behaved is that it is up to them to return control to the operating system, not vice-versa. In general, while your ObjectPAL code is running, the computer is not able to do any other processing. This is usually not a problem, as most methods execute in small fractions of a second. Consider, however, the situation where you are scanning through a large table. In this case, all resources are tied up until the scan is completed. Enabling Timer Events While Another Method is Executing Let's assume that you want a form to display the time. This can be done easily using a timer method. But the "clock" will stop while the following code is executing, because the code will not give control back to Windows until it has completed: method myMethod() for si from 1 to 5000 loopBody() endFor endMethodTo remedy this situation, all we need to do is to give Windows temporary control somewhere within the loop. This is known as a "yield", and is implemented using the sleep method as follows: method myMethod() for si from 1 to 5000 loopBody() sleep() endFor endMethodEach time the sleep is encountered, Paradox will yield to Windows for an instant, and the timer event (as well as any other program that needs to be serviced) will be called. This should not have significant performance ramifications, and is thus highly recommended within methods that can take a long time to complete. Aborting Within a Loop Although a yield will enable timer events and processing by other applications while a method is running, it will not allow "external" events to be processed by the form. (Thus a button cannot receive a mouse event while a loop is executing.) In this section we will discuss how to enable a "Cancel" button while an ObjectPAL loop is running. The way to accomplish this is to break up the loop into separate methods and to use a timer to iterate the loop. This involves adding the loop counter to the Var window, and placing most of myMethod in the timer: Var siLoopCount smallInt endVar method myMethod() siLoopCount = 1 self.setTimer(1) endmethod method timer(var eventInfo TimerEvent) if siLoopCount > 5000 or cancelButton'value then self.killTimer() else loopBody() siLoopCount = siLoopCount + 1 endIf endmethodUsing this technique, the timer is called as quickly as possible, and will only stop after it has executed 5000 times, or if the user presses the button on the form named cancelButton. The only problem with this approach is that the mechanics of it are significantly slower than the mechanics of the for loop alone. This shouldn't be a problem because the code in loopBody is likely to dwarf the overhead imposed by this technique, but nonetheless, you might consider substituting the timer method presented below for efficiency sake. In the below method, rather than "pausing" to allow the cancel button to be pushed after each iteration, it lets the loop iterate 50 times before allowing the system to handle other events. This code will execute much more efficiently, but will be a bit less responsive to external events. If each iteration of loopBody takes .01 seconds, then the user will have to wait as much as a half second for the routine to be canceled. Note that there is nothing magical about the number 50. This number can be tuned to best serve your needs. method timer(var eventInfo TimerEvent) var si smallInt endVar si = 0 while siLoopCount <= 5000 and not cancelButton'value si = si + 1 if si >= 50 then return endIf loopBody() siLoopCount = siLoopCount + 1 endWhile self.killTimer() endmethod Hard to Call DLLs With DLL support, Paradox applications can have virtually unlimited capabilities; allowing us to seemlessly integrate functionality that is not native to Paradox. This opens up areas such as multimedia and communications, allows us to interface with specialized hardware, etc. The only problem with DLLs is that Paradox can only provide a limited degree of protection against our using them improperly. If we tell Paradox to pass an integer to a DLL, that's exactly what it will do. If the DLL expects a floating point number, a system crash would likely be the result. Because of the severity of this potential danger, Borland attempted to simplify the interface between Paradox and external DLLs. The following table (which can be found in the docs and in the on-line help system) describes the data types that Paradox can pass to DLLs, and their counterparts in C and Pascal:
This table tells us that if our DLL expects a long integer, we should use the keyword CLONG in our Uses statement. If we want to pass a string, we use CPTR. But what if want to pass something not listed in this chart? There is no provision for passing an array or a structure, nor is there a way to pass a character. For that matter, there doesn't seem to be a way to pass a pointer to anything other than a string. Does this mean that we can't pass a variable to a DLL by reference? Passing Pointers While technically accurate, the above table is actually a bit misleading. There really isn't a one-to-one relationship between the ObjectPAL keywords and their C and Pascal counterparts as the table suggests. The ObjectPAL keyword determines what will be sent to the DLL, but it doesn't necessarily restrict the data type of the argument on the Paradox side. For example, you can pass a smallInt to a DLL whose argument was declared as CLONGDOUBLE. Paradox will simply convert the smallInt before calling the DLL. Likewise, you can pass any "simple" variable to a DLL whose argument is defined as CPTR, and Paradox will pass a pointer to that variable. Thus the C counterpart of CPTR is probably better thought of as void far *. The following code illustrates how to pass a smallInt to a DLL by reference: uses myDLL myFunction(siArg CPTR) endUses method myMethod(var si smallInt) myFunction(si) endMethodThere is one minor additional issue that you should be aware of when passing pointers to DLLs. Paradox requires that all variables being passed to a DLL be initialized, even if they are being passed as pointers and their values will not be examined. Thus in the above example, the value of si must have been set before the call to myFunction, or the call will fail. Passing Unsupported Data Types DLLs whose arguments are not supported by Paradox (such as those utilizing structures and arrays) can still be called, but this requires special handling. The most efficient way to call such a DLL is to use a "wrapper". A wrapper is a DLL that Paradox can call, which in turn calls the DLL that Paradox can't call. The benefit of using a wrapper is that it is very efficient. The drawback is that the developer needs to know a low-level language such as C or Pascal, and must also know how to create a DLL. If you do not wish to create a wrapper, there is another alternative. Recognizing that a structure or an array is simply an organized sequence of bytes, we can create a string to be used as a buffer to hold these bytes, pass that string to the DLL, and manipulate the bytes within the string to translate the values into the appropriate data types. In the example below, we want to obtain the size of Paradox's client area (the area where Paradox places child windows such as forms and reports): uses USER getClientRect(hWnd CWORD, lpRect CPTR) endUses method appSize() point var app application stStruct string endVar stStruct = "00000000" getClientRect(app.windowClientHandle(), stStruct) return pixelsToTwips(point(ansiCode(stStruct.substr(5)) + ansiCode(stStruct.substr(6)) * 256, ansiCode(stStruct.substr(7)) + ansiCode(stStruct.substr(8)) * 256)) endMethodThe problem is that the second argument of the Windows API function getClientRect is a pointer to a structure (which contains four 2-byte integers, the latter two of which we are interested in). To call this function we use a buffer variable, stStruct, which is a string that is large enough to hold the 8-byte structure that getClientRect returns. We then use the appropriate string manipulation methods to reconstitute the values back into 2-byte integers. This technique is not particularly fast, and would be much more complex if the structure contained floating point numbers that we needed to translate, but it works reasonably well in this case. Status Messages The last topic we will cover in this paper is status messages. More specifically, people typically want to know how to send messages to any of the status windows, and how to cause messages to remain in these windows until they remove them. The solution presented here will accomplish this for a single form. We will elaborate on this during the technical session to create a solution that will work across all forms within your application. The technique employed here allows you to send "message commands", as well as simple messages, to the status window. A message command, as defined by the method included below, is a message that begins with either an "M" (for message) or an "F" (for filter), is followed by the number of the status window and a colon, and then by a textual argument. To send the message "Hello" to the middle of the three small status windows, simply use the following: message("M2:Hello")You can set a filter using the same format. Rather than placing a message, the filter stops messages from being displayed unless they satisfy the given condition (as defined in advMatch). For example, the "message command": message("F0:^[0-9]+ of [0-9]+")will set a filter for the main status window that will allow Paradox's record number message (an example of which might be: "1 of 42 [TABLE.DB]") to be displayed. Messages not conforming to the format will be discarded, leaving the existing message in place. All messages are compared against the appropriate filter unless they are send via message commands, in which case they will be placed in the given window regardless of whether or not they satisfy the filter condition for that window. The following code is attached to the form's status method. The array arstMessageMask is defined as a 4-element array of strings in the form's Var window, and all elements are initialized to blank in the form's open method: method status(var eventInfo statusEvent) var stCmd, stWin, stText string endVar if not eventInfo.isPreFilter() then if advMatch(eventInfo.statusValue(), "^([MmFf])([0123]):(..)$", stCmd, stWin, stText) then if stCmd.upper() = "F" then arstMessageMask[smallInt(stWin) + 1] = stText disableDefault else eventInfo.setStatusValue(stText) eventInfo.setReason(smallInt(stWin)) endIf else if not advMatch(eventInfo.statusValue(), arstMessageMask[eventInfo.reason() + 1]) then disableDefault endIf endIf endIf endmethod Discussion of this article |
![]() Feedback | Paradox Day | Who Uses Paradox | I Use Paradox | Downloads ![]() |
|
![]() The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation. Paradox is a registered trademark of Corel Corporation. ![]() |
|
![]() Modified: 15 May 2003 Terms of Use / Legal Disclaimer ![]() |
![]() Copyright © 2001- 2003 Paradox Community. All rights reserved. Company and product names are trademarks or registered trademarks of their respective companies. Authors hold the copyrights to their own works. Please contact the author of any article for details. ![]() |
![]() |
|