18 June 2009

Patching the GfaWin23.Ocx DLL

It is not a good idea to patch the GfaWin23.Ocx DLL. For instance, recently someone wanted to use the properties and methods of GFA-BASIC's RichEdit Ocx for the newer RichEdit control version 4.0 from MsftEdit.DLL. To accomplish this task the runtime DLL was patched by replacing some ASCII text using a HEX-file-editor. However the GfaWIn23.Ocx is updated regularly and patching each new update isn't an ideal solution to add new features to the BASIC. This got me thinking about adding new functionality to the runtime. In case of the newer RichEdit control I considered three options.

1 Use the WrapRichEd command

Consider the purpose of the patch. By forcing GFA-BASIC 32 to load a different richedit DLL and changing the class name used to create the RichEdit Ocx control, GFA-BASIC 32 will create the newer control when it invokes the statement Ocx RichEdit. Then, the properties and methods of the RichEdit COM interface can be used to manage the control. (Note most properties and methods are simply wrappers for the SendMessage(hwndEd, EM_xxx, wParam, lParam) function. These wrapper functions use an internal structure with state information GFA-BASIC uses to optimize the performance of the COM functions.)

Ok, back in 1999 the first GFA-BASIC 32 beta didn't contain the Ocx command. To wrap a control up in a COM interface GFA-BASIC 32 support(ed) the WrapCtrlName statements, like WrapRichEd. This command assigns a COM interface to a child control. The child control might have been created using a GFA-BASIC control command, like the general Control creation command, or simply by using an API function like CreateWindowEx(). You can still find examples of the use of the Wrap-commands in the \Samples directory. In general it is used as follows:

Dim rtb1 As RichEdit
RichEditCtrl "", 101, x, y, w, h
WrapRichEd 101, V:rtb1, "rtb1"

This encapsulates a RichEdit control with ID = 101 in an Ocx variable rtb1 and sets it event name to "rtb1".

Back to the issue at hand. The window class name of the newer RichEdit control is "RICHEDIT50W'. The current implementation of the Ocx RichEdit (or RichEditCtrl) statement is to load RichEd32.DLL and to create a control with the classname "RichEdit20A". To get a new control we need to load the MsftEdit.DLL first and then create a control of this class using the general command Control. Like this

Global Handle hMsftEdit = LoadLibrary("msftedit.dll")
Assert hMsftEdit
Global Dim rtb As RichEdit
Control "", 3, "RichEdit50w", WS_BORDER, 100, 70, 100, 284
WrapRichEd 3, V:rtb, "rtbev"

The Ocx is now accessible by its variable name rtb and the event subs have the format Sub rtbev_Event(). Great!? Well, this process continues to work until you invoke a window-style property for the RichEdit Ocx, like rtb.BorderStyle=. In this case the control is destroyed and recreated, a process completely hidden for the developer. When the richedit control is recreated, the 'normal' GFA-BASIC 32 DLL code for the creation of a RichEdit Ocx is invoked, which then creates a "RichEdit20A" class control. Implicitly, the version 4.0 richedit control is destroyed and a richedit control of an older version is created. (The properties that cause a recreation of the RichEdit control are .HideSelection, .MultiLine, .ScrollBars, .BorderStyle, .TabStop ). Unless you give up these properties, this doesn't seem an ideal technique.

2 Poke the DLL (Advanced)

Under conditions this is a valid way to go. Only the loaded DLL is affected. The code is modified at runtime, for instance in the initialization process of your program. Such a modification isn't permanent, once your program has finished, the DLL is unloaded and the next time the original GfaWin23.Ocx is loaded again. When you insist on patching the DLL poking at runtime is definitely worth a shot. The API functions to use are VirtualProtextEx() and WriteProcessMemory(). VirtualProtextEx() is required before poking, because in this particular case the data section – which is read-only - of the DLL has to be patched.

In general you can use the addresses as they are showed in a HEX-file editor and poke those addresses. However, the system doesn't guarantee that a DLL is loaded at the preferred base address. In case multiple DLLs require the same base-address Windows may relocate one of the DLLs giving it a new address in (virtual) memory. You can invoke an additional LoadLibray("GfaWin23.Ocx") or GetModuleHandle() to obtain the base-address, of course you must decrement the DLL count immediately by invoking FreeLibrary(). Maybe I come back on this in some other post.

3 Send messages on your own (Preferred method)

When a GFA-BASIC 32 program requires a new or custom control it should proceed the API way. Point. Load the DLL and create a control using Control or CreateWindowEx(). Then send messages to manage the control and use the parent form's _Message sub event to respond to WM_COMMAND messages. When the custom control sends WM_NOTIFY messages use _MessageProc. More on _Message(Proc) see http://gfabasic32.googlepages.com/faqforms

To utilize GFA-BASIC 32 to the most, you can write wrapper functions and put them in a $Library. Most C/C++ include files for custom controls also present C/C++ macros that invoke the SendMessage() API to manage the custom control. You can easily convert them to Function calls and put them in a library.

No comments:

Post a Comment