Project

General

Profile

Feature #4438

parsing and conversion of .wrx files

Added by Greg Shah over 4 years ago. Updated about 2 years ago.

Status:
WIP
Priority:
Normal
Assignee:
Marius Gligor
Target version:
-
Start date:
Due date:
% Done:

90%

billable:
No
vendor_id:
GCD

cfb-explorer.png (38.9 KB) Marius Gligor, 12/06/2019 09:57 AM

pbt2-wrx.png (165 KB) Marius Gligor, 12/06/2019 09:57 AM

pbt3-wrx.png (198 KB) Marius Gligor, 12/06/2019 09:57 AM

wmp.png (34.8 KB) Marius Gligor, 12/06/2019 09:57 AM

pbt-wrx.png (168 KB) Marius Gligor, 12/06/2019 09:57 AM

ps-timer-props.png (6.91 KB) Marius Gligor, 12/13/2019 10:03 AM

ps-timer-tlb.txt Magnifier (9 KB) Marius Gligor, 12/13/2019 10:18 AM

progress-bar-props.png (12 KB) Marius Gligor, 12/17/2019 09:16 AM

pb-custom-props.png (23.6 KB) Marius Gligor, 12/17/2019 09:16 AM

ms-common-60-tlb.txt Magnifier (195 KB) Marius Gligor, 12/17/2019 09:17 AM

ps-timer-tlb.txt Magnifier (1.4 KB) Marius Gligor, 12/17/2019 09:17 AM

psc_knowledge_base_000192702_what_do_wrx_files_contain_20191003.pdf (22.2 KB) Greg Shah, 12/08/2020 09:52 AM


Related issues

Related to User Interface - Feature #4174: implement calendar control/dtpicker OCX replacement Closed

History

#1 Updated by Greg Shah over 4 years ago

The following is copied from #3767.

From Marius:

A .wrx file is MS CFB file (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b) a general-purpose file format that provides a file-system-like structure within a file for the storage of arbitrary, application-specific streams of data. This low level format is used by other Microsoft documents like .doc (Word document), .msg (mail message), etc. For example you can try to rename a .wrx file as .doc file and you will see that Word and LibreOffice Writer are able to open the file.

The problem is that the content is in binary format specific for each application .wrx, .msg, .doc. .msg file structure is documented by MS but I'm afraid the .wrx binary format content is created by Progress Software and not documented. https://knowledgebase.progress.com/articles/Article/What-do-wrx-files-contain

Some time ago I created a Python script able to parse .msg files and extract mail properties and attachments.

From Greg:

A .wrx file is MS CFB file

This is an interesting detail. I had assumed it was something completely proprietary to the 4GL. Looking through the specification, I note there is this "OLE Property Sets" specification:

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oleps/bf7aeae8-c47a-4939-9f45-700158dac3bc

I wonder if Progress just implemented such an approach.

All settings I did at runtime are stored perhaps inside .wrx file and are loaded at runtime during initialization!

Yes, this is something we need to resolve. I agree that parsing the .wrx file is the best approach. I do prefer to convert any such design-time features into static code references. In other words, I want to eliminate the .wrx file itself after conversion. Since one cannot edit that file easily (or at all) and it is difficult (impossible?) to read the settings later, it is a terrible way to store settings. It is much better to leave this as code that can be edited and read.

The down side is the scenario where there are more than one .wrx conditionally used in the same LoadControls. I've never seen such an approach, though it should be possible. In such a case we would also have to write the settings code in a conditional manner. For now we can ignore this problem.

Please look into the .wrx format and see if you can decode it. Perhaps there is some generic tool for viewing/parsing these files that can at least be used to understand the format. I'm going to create a separate task for this so that the findings will be public.

#2 Updated by Greg Shah over 4 years ago

Any parsing we do would need to be using Java. Each of the properties set from the .wrx would be converted into OCX setter calls in the converted code. The .wrx would not be used at runtime.

#3 Updated by Vladimir Tsichevski over 4 years ago

Greg Shah wrote:

Any parsing we do would need to be using Java. Each of the properties set from the .wrx would be converted into OCX setter calls in the converted code. The .wrx would not be used at runtime.

I see a possible trouble here. I suspect, that the .wrx files contain setters for all the OCX properties, which were exposed by the original OCX at the moment the .wrx file was created, most of these being just default values, which need not be set.

At the moment, is is safe to not implement some of original OCX properties, if applications does not set them. Implementing the proposed .wrx file processing will broke these OCX implementations.

To solve this, the conversion program may statically check the implemented in FWD OCX interface and do not convert the missing properties, probably, just reporting them.

Moreover, I suppose most of the real .wrx files contains only the default values, so no code at all should be generated for these files.

To make it happen, we should let the conversion program to know all the default property values. These we can, for example, define in the ComProperty annotaions for property setters.

#5 Updated by Greg Shah over 4 years ago

To make it happen, we should let the conversion program to know all the default property values. These we can, for example, define in the ComProperty annotaions for property setters.

Good idea.

#6 Updated by Marius Gligor over 4 years ago

1. I created an OE application using Application Builder using a SmartWondow on which I dropped a Progress Timer Control OCX. I saved the application and the .wrx file content was captured in picture pbt-wrx.png Then I changed the OCX property Interval from 0 to 3 and I saved the changes. Looking in the .wrx file pbt2-wrx.png I saw that my change was saved, which means that any property changes at design time are saved in the wrx file. Next I added another OCX control, Windows Media Player to the same frame, I did no property settings and I saved my application. Looking in the wrx file pbt3-wrx.png I found data for WMP. WMP has a property uiMode of type string having a default value full As we can see the unicode string value is in the .wrx file without doing any changes at design time. Looks like that properties values at design time are saved in the .wrx file. Basically the state of OCX properties at design time are saved, touched and untouched properties as well.

From Progress documentation related to ActiveX I extracted:

Unlike 4GL widgets, which have a standard set of defaults for the common attributes, the
default settings for common ActiveX control properties vary from control to control. If you
observe unexpected run-time behavior from an ActiveX control, consider whether you have set
design time property values that are appropriate for your application.

At runtime, your application uses AppBuilder-generated code to instantiate any ActiveX
controls. This code (shown in following sections) uses the CREATE Widget statement to realize
a separate control-frame widget for each ActiveX control, initializing each control-frame with
the name specified at design time. It then invokes the LoadControls( ) method on each
control-frame COM object to instantiate the corresponding ActiveX control, loading all design
time definitions from the .wrx file.

which come to validate what I found.

2. Searching on internet I found a tool OpenMCDF (https://sourceforge.net/projects/openmcdf) which allow to see what's inside a wrx file. The tool is a .NET application written in C# and runs only on Windows. Using this tool I opened the wrx file created previous. As you can see in the picture the content looks like a file system structure which contains a root directory and sub folders for each defined ControlFrame. Because we have 2 OCX's we have 2 ContolFrame's.

From Progress documentation:

A single control-frame is capable of holding only a single ActiveX control. However, you can include many control-frames (and thus many ActiveX controls) in a Progress frame. This limitation incurs certain restrictions on the types of ActiveX controls that you can use. For instance, there is no internal support for container controls — that is, controls that allow multiple controls to reside inside of them.

We have also 2 files for each OCX, Frame and Control0, which store properties values. If you look at the OCX properties in an Application Builder, there are some properties which not belong to the OCX, but seems to be properties for Frame. Example: HonorProKeys and HonorRetunKey properties. Perhaps these properties are stored in the Frame file and all OCX properties in file Control0

3. So far so good, but there are also other issues:
  • Having settings in .wrx file for more than one OCX should not be a problem because we have the OCX name as an identifier. We already use the control name in .ext-hints files
  • Looking over properties data in .wrx file I didn't found any property name as a string! The problem is how we can associate a value to a property?
  • At a first view the binary format of data seems to be a custom format but I'm not really 100% sure.

#7 Updated by Greg Shah over 4 years ago

Looking over properties data in .wrx file I didn't found any property name as a string! The problem is how we can associate a value to a property?

Take a look at the OLE Property Sets specification. COM is based on OLE. Each property is represented by a property ID and the string name is optional. I guess that OLE has some way to map these IDs to the specific properties being referenced. Each control is just a DLL after all, with various function entry points (COM methods) and exported memory locations (COM properties) which are defined as the external API of the DLL. It is common in Windows DLLs to assign ordinal values to exported entries. Perhaps the property ID is somehow related to that ordinal.

#8 Updated by Vladimir Tsichevski over 4 years ago

Greg Shah wrote:

Looking over properties data in .wrx file I didn't found any property name as a string! The problem is how we can associate a value to a property?

Take a look at the OLE Property Sets specification. COM is based on OLE. Each property is represented by a property ID and the string name is optional. I guess that OLE has some way to map these IDs to the specific properties being referenced. Each control is just a DLL after all, with various function entry points (COM methods) and exported memory locations (COM properties) which are defined as the external API of the DLL. It is common in Windows DLLs to assign ordinal values to exported entries. Perhaps the property ID is somehow related to that ordinal.

If I remember this correctly, the individual COM methods and properties are not exported as DLL entries. Only a few entries are required:

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID )
STDAPI DllCanUnloadNow(void)
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
STDAPI DllRegisterServer(void)
STDAPI DllUnregisterServer(void)

And I suppose, the DllGetClassObject returns the interface descriptions we are looking for.

#9 Updated by Hynek Cihlar over 4 years ago

Greg Shah wrote:

Looking over properties data in .wrx file I didn't found any property name as a string! The problem is how we can associate a value to a property?

Take a look at the OLE Property Sets specification. COM is based on OLE. Each property is represented by a property ID and the string name is optional. I guess that OLE has some way to map these IDs to the specific properties being referenced. Each control is just a DLL after all, with various function entry points (COM methods) and exported memory locations (COM properties) which are defined as the external API of the DLL. It is common in Windows DLLs to assign ordinal values to exported entries. Perhaps the property ID is somehow related to that ordinal.

COM methods (and properties) can be invoked dynamically through the optional IDispatch interface. If you look at its Invoke method, it declares a DISPID parameter, which is an ordinal value of the method/prop being invoked, see https://docs.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-invoke. The dispid is defined in a type library AFAIK. Also there is another method IDispatch::GetIDsOfNames which can translate the ids to names, I guess it goes to the type library and reads the info there.

I wonder if this dispid is somehow stored in the wrx file.

#10 Updated by Marius Gligor over 4 years ago

1. I started to do a decoding on .wrx file content.
Using Application Builder and PSTimer OCX which is a very simple ActiveX I created a simple procedure to check PSTimer property values at design time. All PSTimer properties which are exposed at design time are show in the picture.

Then, at design time, I changed OCX properties, one by one, I saved the .wrx file and I tried to find where properties are saved.
After doing that, at this stage, I'm able to describe the structure of .wrx file binary content as follow:

Offset   Data
------------------------------------------------------------------------
0000:    00 00 00 00 00 00 00 00 1C 00 00 00 1C 00 00 00 // ??? 16 bytes Unknown header

0010:    26 7b 46 30 42 38 38 41 39 30 2d 46 35 44 41 2d 31 31 43 46 2d 42 35 34 35 2d 30 30 32 30 41 46 36 45 44 33 35 41 7d 
        (38) {F0B88A90-F5DA-11CF-B545-0020AF6ED35A} // OCX CLSID First byte 0x26 (38) is the length of the string

0037:    07 50 53 54 69 6d 65 72 // OCX name not to be confused with Name property. First byte 0x07 (7) is the length of the string
            PSTimer

003F:    48 00 00 00 - 34 00 45 00 39 00 33 00 44 00 39 00 37 00 31 00 2d 00 37 00 35 00 34 00 30 00 2d 00 31 00 31 00 62 00 31 00 
         (72)          2d 00 38 00 39 00 61 00 31 00 2d 00 30 00 30 00 43 00 30 00 34 00 46 00 44 00 34 00 41 00 38 00 33 00 36 00
                       4E93D971-7540-11B1-89A1-00C04FD4A836 // an unknown UTF-16LE UUID, First 4 bytes 0x00000048 (72) is the length of the string 

// BEG:OCX Here OCX properties are stored
008B:    15 00 00 00 // block size
0090     00 00 01 00  e5 02 00 00  e5 02 00 00  40 00 00 00 // ??? unknown 16 bytes. Could be something related to "About" property? I think About property should display a 
                                                            // copy rights message in a modal dialog box but does not work in Application Builder for all OCX I tested.
                                                            // When "About" button is pressed Application Builder hangs!

// OCX DATA STREAM - Here are OCX property values stored like a data stream.
009F:    01           // Enabled = True.    (000B = VT_Bool - type is from type library, not stored in .wrx)
00A0:    07 00 00 00  // Interval = 7.      (0003 = VT_I4   - type is from type library, not stored in .wrx)

// END:OCX    00A4 = 008B + 15 + 4
00A4:    08 00 00 00 // ??? unknown data

00A8:    07 50 53 54 69 6d 65 72 // Name = PSTimer.  First byte 0x07 (7) is the length of the string

00B0:    00 00 ff ff ff ff // ??? unknown data
00B6:    03 6D 61 67 // Tag = mag.
00BA:    01 00 00 00 // HonorProKeys = True.
00BE:    01 00 00 00 // HonorReturnKey = True.
...

I should analyze and compare another .wrx binary data for another OCX control, maybe we could find more regarding bynary data pattern.

2. Next, I found a small tool which allow us to dump a COM type library if exists: (http://theircorp.byethost11.com/index.php?vw=TypeLib)
According to MS specifications, Type Libraries are not mandatory to exists. If type library is missing we can query COM object to get information regarding exposed interfaces, properties an methods. For PSTimer properties from Type Library dump I found:

----------------------------------------
     Properties:
----------------------------------------
ID:            FFFFFDFE ==-514
Name:        00000030 ==> "Enabled" 
Reference:    00000018
Record size (low-byte):    0014
Property number?:        0001
Flags:                    800B = VT_Bool
DataType:                000B = VT_Bool
Variable kind:            000C = RequestEdit, Bindable
Variable desc size:        0000
Value/Offset:            00240003
Unknown:                00000000

----------------------------------------
ID:            00000001 == 1
Name:        00000044 ==> "Interval" 
Reference:    0000002C
Record size (low-byte):    0018
Property number?:        0002
Flags:                    8003 = VT_I4
DataType:                0003 = VT_I4
Variable kind:            0000 = 
Variable desc size:        0000
Value/Offset:            00240003
Unknown:                00000000
HelpContext:            000003EC

Basically inside TLB we have properties metadata. For each property we have ID, Name, Propery number, DataType, etc.

3. My first conclusions are:
- Binary format is a Progress Software custom structure and is undocumented.
- Offsets in .wrx file are variable not fixed.
- OCX properties are stored together with wrapper properties.
- HonorProKeys, HonorReturnKey, Name and Tag are not OCX properties, they are wrapper properties. These properties are present in the property page for all OCX's.
- Enabled and Interval are OCX properties and the values are stored like a stream of data, one after the other. Only these properties should be decoded in FWD.
- Unfortunately I found no metadata (index, order, type, name) for OCX properties inside .wrx file!
- Looks like Progress decode OCX properties from .wrx file by querying OCX for metadata and then decoding values from data stream one by one using the Property number and DataType.
- Property ID's are not in order, Enabled ID = -514 and Interval ID = 1, that's why I think Property number is used because Property numbers are in order 1,2,3,..
- Property ID or Name is probably used when property value is set.
- Some OCX properties could be read only. If values for those properties are in the data stream, they should be skipped.
- LE (little endian) format is used to store data.
- Another import issues is to check if the binary values can be decoded into Java types boolean, short, integer, long, float, double, string, etc My concern at this moment is about the double and float values. If they are standard IEEE 754 values should not be a problem.

4. In FWD we cannot depend on OCX instance nor in Type Library. IMO one possible solution is to keep OCX properties metadata in a XML structure like:

values
<?xml version="1.0" encoding="UTF-8"?>
<ocx clsid="{F0B88A90-F5DA-11CF-B545-0020AF6ED35A}" type="PSTimer">
   <property number="1" type="VT_Bool" skip="true">Enabled</property>
   <property number="2" type="VT_I4">Interval</property>
</ocx>

This file should be created once and used whenever we need to decode values for same OCX type (CLSID). The link between metadata from XML file and .wrx data is the OCX CLSID which is unique. Here we can keep among the property number and type attributes a skip attribute which specify if the property should be set or not. That is, we decode property value but we don't set the property value, we just skip over.

Obviously, any other ideas are always welcome.

#11 Updated by Greg Shah over 4 years ago

This is very good work!

Your point about the Appbuilder made me think about the fact that the original Appbuilder code is open source written in the 4GL. My idea was to check if the binary .wrx writing code was included. I looked in the Possenet code for references to .wrx files. In src/adeuib/_recreat.p we can see this code:

        /* Save the control to the wrx file using the control-frame name */
        f_U._COM-HANDLE:SaveControls(tempBinaryFile, f_U._NAME).

I believe that SaveControls is a 4GL language feature (probably written in C like the rest of the 4GL) which saves off the binary .wrx file. It seems to be the design-time mirror image to the LoadControls method that is also on the CONTROL-FRAME.

Anyway, it seems like you already have enough information to parse the parts of the file that we care about. That is great!

In FWD we cannot depend on OCX instance nor in Type Library. IMO one possible solution is to keep OCX properties metadata in a XML structure like:

...

This file should be created once and used whenever we need to decode values for same OCX type (CLSID). The link between metadata from XML file and .wrx data is the OCX CLSID which is unique. Here we can keep among the property number and type attributes a skip attribute which specify if the property should be set or not. That is, we decode property value but we don't set the property value, we just skip over.

I like this idea. We would have to write a tool (using native code, similar to src/native/get_text_metrics.c) which would capture this based on some list of OCX controls. Then we would store the result in a repo.

I don't quite understand the skip idea. I think it is more important to know the default value for each property. If the value encoded in the wrx is different from the default, then we would write the property setter out into the Java code, otherwise we ignore it.

#12 Updated by Marius Gligor over 4 years ago

Greg Shah wrote:

[...]

Anyway, it seems like you already have enough information to parse the parts of the file that we care about. That is great!
[...]

I like this idea. We would have to write a tool (using native code, similar to src/native/get_text_metrics.c) which would capture this based on some list of OCX controls. Then we would store the result in a repo.
[...]

I don't quite understand the skip idea. I think it is more important to know the default value for each property. If the value encoded in the wrx is different from the default, then we would write the property setter out into the Java code, otherwise we ignore it.

1. We are close to have enough information to decode OCX property values from .wrx files. The name of the OCX control is also important even though is not an OCX property. So far we have some conclusions as a result of analyzing data for PSTimer OCX only. This is not enough that's why I'm going to analyse data for other OCX as well, for example ProgressBar.

2. Reading data from (scanning) .wrx files is very easy to implement. Using Apache POI (https://poi.apache.org/) we can do that in just a few lines of code. Apache POI is an open source project available under APACHE LICENSE, VERSION 2.0

3. Yes, we have to create a tool to extract metadata from OCX TLB and build the XML file, otherwise we should do that by hand. This tool will work only in Windows OS, we cannot do that in Linux. Nowadays almost all in Windows are .NET based. I have no idea on how we can do that in C code! Also I saw some posts which claims that using Powershell it is possible to analyze COM objects. A Powershell script would be better because is easy to maintain and is ready to run, no compile is needed. The last Windows C program I wrote is the p2j winspawn.c, 5-6 years ago!!!

4. Once we have the XML metadata, parsing data should be a trivial implementation. I proposed the skip attribute for unimplemented properties. On the other hand for unimplemented properties, conversion rules will add a noop-com annotation which in turn will generate a no operation COM chain, so we don't need a skip attribute for this purpose.

#13 Updated by Greg Shah over 4 years ago

Using Apache POI (https://poi.apache.org/) we can do that in just a few lines of code. Apache POI is an open source project available under APACHE LICENSE, VERSION 2.0

This is acceptable. You will have to update the gradle dependencies to add this.

This tool will work only in Windows OS, we cannot do that in Linux. Nowadays almost all in Windows are .NET based. I have no idea on how we can do that in C code! Also I saw some posts which claims that using Powershell it is possible to analyze COM objects. A Powershell script would be better because is easy to maintain and is ready to run, no compile is needed. The last Windows C program I wrote is the p2j winspawn.c, 5-6 years ago!!!

We already have some native code that does this. For example, you can see in src/native/comauto_win.c, there are C functions like getComName(int id), getComProperty(int id, String name), getMethodType(int id). I'm hoping that with our existing code with uses the IDispatch API, that you can see how GetTypeInfo() and other such helpers work.

I prefer to extend this code with any needed inspection functions and then write Java code to do the rest.

#14 Updated by Marius Gligor over 4 years ago

1. Doing a decoding only on a simple OCX like PSTimer is not enough to conclude about .wrx file binary content format. So, I tried to do the same decoding for a more complex OCX. I decided to use a ProgressBar control from Microsoft Windows Common Controls 6.0 (SP6). Using Application Builder I dropped a ProgressBar OCX control on a SmartWindow, then I opened the Property Editor to see which properties are available for change at design time.
The picture bellow shows the Property Editor page having default values.

When we choose the Custom option, a dialog window is open allowing us to change the values for a set of properties.

Choosing About option or trying to change MouseIncon property, will made Application Builder to hangs! In general Application Builder is unstable, he often hangs!

Then, I used a type library tool (http://www.benf.org/other/olewoo/index.html) to dump the Microsoft Windows Common Controls 6.0 (SP6) OCX Type Library into a text file. (see attached ms-common-60-tlb.txt)
From ms-common-60-tlb.txt file, I selected IProgressBar properties having attributes propget, propput and not hidden as follow:

[id(0x00000001), propget, helpstring("Returns/sets a control's maximum value."), helpcontext(0x00033486)] 
HRESULT Max([out, retval] float* pfMax);
[id(0x00000001), propput, helpstring("Returns/sets a control's maximum value."), helpcontext(0x00033486)] 
HRESULT Max([in] float pfMax);

[id(0x00000002), propget, helpstring("Returns/sets a control's minimum value."), helpcontext(0x00033487)] 
HRESULT Min([out, retval] float* pfMin);
[id(0x00000002), propput, helpstring("Returns/sets a control's minimum value."), helpcontext(0x00033487)] 
HRESULT Min([in] float pfMin);

[id(0x00000003), propget, helpstring("Returns/sets the type of mouse pointer displayed when over part of an object."), helpcontext(0x00033488)] 
HRESULT MousePointer([out, retval] MousePointerConstants* pMousePointers);
[id(0x00000003), propput, helpstring("Returns/sets the type of mouse pointer displayed when over part of an object."), helpcontext(0x00033488)] 
HRESULT MousePointer([in] MousePointerConstants pMousePointers);

[id(0x00000004), propget, helpstring("Sets a custom mouse icon."), helpcontext(0x00033489)] 
HRESULT MouseIcon([out, retval] IPictureDisp** ppPictureDisp);
[id(0x00000004), propputref, helpstring("Sets a custom mouse icon."), helpcontext(0x00033489)] 
HRESULT MouseIcon([in] IPictureDisp* ppPictureDisp);
[id(0x00000004), propput, helpstring("Sets a custom mouse icon."), helpcontext(0x00033489)] 
HRESULT MouseIcon([in] IPictureDisp* ppPictureDisp);

[id(0x00000005), propget, helpstring("Returns or sets a control's current Value property."), helpcontext(0x0003348a)] 
HRESULT Value([out, retval] float* pfValue);
[id(0x00000005), propput, helpstring("Returns or sets a control's current Value property."), helpcontext(0x0003348a)] 
HRESULT Value([in] float pfValue);

[id(0x0000060f), propget, helpstring("Returns/Sets whether this control can act as an OLE drop target."), helpcontext(0x00033692)] 
HRESULT OLEDropMode([out, retval] OLEDropConstants* psOLEDropMode);
[id(0x0000060f), propput, helpstring("Returns/Sets whether this control can act as an OLE drop target."), helpcontext(0x00033692)] 
HRESULT OLEDropMode([in] OLEDropConstants psOLEDropMode);

[id(0xfffffdf8), propget, helpstring("Returns/sets whether or not controls, Forms or an MDIForm are painted at run time with 3-D effects."), helpcontext(0x0003348b)] 
HRESULT Appearance([out, retval] AppearanceConstants* penumAppearances);
[id(0xfffffdf8), propput, helpstring("Returns/sets whether or not controls, Forms or an MDIForm are painted at run time with 3-D effects."), helpcontext(0x0003348b)] 
HRESULT Appearance([in] AppearanceConstants penumAppearances);

[id(0xfffffe08), propget, helpstring("Returns/sets the border style for an object."), helpcontext(0x0003348c)] 
HRESULT BorderStyle([out, retval] BorderStyleConstants* penumBorderStyles);
[id(0xfffffe08), propput, helpstring("Returns/sets the border style for an object."), helpcontext(0x0003348c)] 
HRESULT BorderStyle([in] BorderStyleConstants penumBorderStyles);

[id(0xfffffdfe), propget, helpstring("Returns/sets a value that determines whether a form or control can respond to user-generated events."), helpcontext(0x0003348d)] 
HRESULT Enabled([out, retval] VARIANT_BOOL* bEnabled);
[id(0xfffffdfe), propput, helpstring("Returns/sets a value that determines whether a form or control can respond to user-generated events."), helpcontext(0x0003348d)] 
HRESULT Enabled([in] VARIANT_BOOL bEnabled);

[id(0x00000006), propget, helpstring("Returns/sets a value that determines whether the Progress Bar is displayed vertically or horizontally."), helpcontext(0x0003376d)] 
HRESULT Orientation([out, retval] OrientationConstants* penumOrientation);
[id(0x00000006), propput, helpstring("Returns/sets a value that determines whether the Progress Bar is displayed vertically or horizontally."), helpcontext(0x0003376d)]
HRESULT Orientation([in] OrientationConstants penumOrientation);

[id(0x00000007), propget, helpstring("Returns/sets a value that determines whether the control displays progress with a standard segmented bar or a smooth bar."), helpcontext(0x0003376e)] 
HRESULT Scrolling([out, retval] ScrollingConstants* penumScrolling);
[id(0x00000007), propput, helpstring("Returns/sets a value that determines whether the control displays progress with a standard segmented bar or a smooth bar."), helpcontext(0x0003376e)] 
HRESULT Scrolling([in] ScrollingConstants penumScrolling);

Comparing the list of properties with the properties exposed on Application Builder Property Editor I found that all properties except Value property are available for change at design time.
The Value property looks like Max and Min properties, only the name and id are obviously specific, however we cannot change the Value property at runtime like we can do for Min and Max properties!
The question is: Why Value property is not available for change at design time? What is criteria used by Application Builder to decide not to expose this property at design time?

2. On the next step I tried to find out the structure of the .wrx file binary content and compare with the structure for PSTimer. The binary content for ProgressBar looks like:

          00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
          ------------------------------------------------
00000000: 00 00 00 00 00 00 00 00  64 00 00 00 64 00 00 00 
00000010: 26 7B 33 35 30 35 33 41  32 32 2D 38 35 38 39 2D 
00000020: 31 31 44 31 2D 42 31 36  41 2D 30 30 43 30 46 30 
00000030: 32 38 33 36 32 38 7D 0B  50 72 6F 67 72 65 73 73 
00000040: 42 61 72 48 00 00 00 39  00 33 00 36 00 38 00 32 
00000050: 00 36 00 35 00 45 00 2D  00 38 00 35 00 46 00 45 
00000060: 00 2D 00 31 00 31 00 64  00 31 00 2D 00 38 00 42 
00000070: 00 45 00 33 00 2D 00 30  00 30 00 30 00 30 00 46 
00000080: 00 38 00 37 00 35 00 34  00 44 00 41 00 31 00 50 
00000090: 00 00 00 21 43 34 12 08  00 00 00 56 0A 00 00 56 
000000A0: 0A 00 00 01 8A AB 97 00  00 06 00 1C 00 00 00 00 
000000B0: 00 00 00 00 00 C8 42 00  00 00 00 01 EF CD AB 00 
000000C0: 00 05 00 78 EA 29 00 00  00 00 00 FF FF FF FF FF 
000000D0: FF FF FF 84 EA 29 00 1F  DE EC BD 01 00 05 00 84 
000000E0: EA 29 00 08 00 00 00 0B  50 72 6F 67 72 65 73 73 
000000F0: 42 61 72 00 00 FF FF FF  FF 03 77 77 77 01 00 00 
00000100: 00 00 00 00 00 00 00 00  00

Doing a decoding on that binary data I decomposed the content as follow:
// HEADER
00000000: 00 00 00 00 00 00 00 00 64 00 00 00 64 00 00 00 // ??? Unknown 16 bytes header

// CLSID
00000010: 26 // size of CLSID string = 38
00000011: 7B 33 35 30 35 33 41 32 32 2D 38 35 38 39 2D 31 31 44 31 2D 42 31 36 41 2D 30 30 43 30 46 30 32 38 33 36 32 38 7D // CLSID = {35053A22-8589-11D1-B16A-00C0F0283628}

// COCLASS:NAME
00000037: 0B // size of string = 11 
00000038: 72 6F 67 72 65 73 73 42 61 72 // ProgressBar

// LICENSE http://www.dbase.com/KnowledgeBase/dbulletin/bu13_a.htm
//         https://github.com/atlab/matlab-utils/blob/master/actxlicense.m
//         
00000043: 48 00 00 00 // size = 72
00000047: 39 00 33 00 36 00 38 00 32 00 36 00 35 00 45 00 2D 00 38 00 35 00 46 00 45 00 2D 00 31 00 31 00 64 00 31 00 2D 00 38 00 42
          00 45 00 33 00 2D 00 30 00 30 00 30 00 30 00 46 00 38 00 37 00 35 00 34 00 44 00 41 00 31 00 // license = 9368265E-85FE-11d1-8BE3-0000F8754DA1

// OCX:DATA BEG (80 bytes)
0000008F: 50 00 00 00 // size of OCX:DATA = 80

00000093: 21 43 34 12 08 00 00 00 56 0A 00 00 56 0A 00 00 01 8A AB 97 00 00 06 00 1C 00 00 00 // ???

000000AF: 00 00 00 00 // Min = 0. (float)
000000B3: 00 00 C8 42 // Max = 100. (float)
000000B7: 01 00 // Orientation = 1. (short)
000000B9: 01 00 // Scrolling =`1. (short)
000000BB: 01 EF CD AB 00 00 05 00  // ???
000000C3: 78 EA 29 00 // ???

// store many properties values. decoded based on a binary mask.
000000C7: 00 06 // default

// BorderStyle  mask = 0x0001
// Enabled      mask = 0x0002
// Appearance   mask = 0x0004
// MousePointer mask = 0x03f8
// OleDropMode  mask = 0x2000

// 0x0006 - (0000 0000 0000 0110) default value for:
// BorderStyle  = 0.
// Enabled      = True.
// Appearance   = 1.
// MousePointer = 0.
// OleDropMode  = 0.

// 0x231F - (0010 0011 0001 1111) value for:
// BorderStyle  = 1.
// Enabled      = True.
// Appearance   = 1.
// MousePointer = 99.
// OleDropMode  = 1.

000000C9: 00 00 // ???
000000CB: FF FF FF FF FF FF FF FF // ???
000000D3: 84 EA 29 00  1F DE EC BD  01 00 05 00  84 EA 29 00 // ???

// OCX:DATA END

// FOOTER (0000008F + 0x00000050 + 0x04 = 000000E3)
000000E3: 08 00 00 00 // ???

000000E7: 0B // size of Name string 
000000E8: 50 72 6F 67 72 65 73 73 42 61 72 // Name = ProgressBar.

000000F3  00 00 FF FF FF FF // ???
000000F9: 00 // Tag
000000FA: 01 00 00 00 // HonorProKeys = True.
000000FE: 00 00 00 00 // HonorReturnKey = False.
00000102: 01 00 00 00 // Visible = True.

The general structure of data looks to have the same pattern like on PStimer. Let explain:
  • HEADER - is a 16 bytes array which I don't know how should be interpreted!
  • CL1SID - is a UTF-8 string containing the CLSID for ProgressBar.
  • COCLASS:NAME - Is the name of COCLASS, not to be confused with the name of the control instance.
  • LICENSE - This is something new that I found searching on the internet for UUID value. Yey, now we know that this field contains the OCX LICENSE. (http://www.dbase.com/KnowledgeBase/dbulletin/bu13_a.htm) On old OCX's could be a string instead of a UUID but this is not relevant.
  • OCX:DATA - Here, is where the OCX properties are stored. The first 4 bytes is the size of this block. Unfortunately there are a lot of fields which I cannot explain what they are meaning.
    In order to find out how and where property values are stored I used Property Editor to change properties one by one, then save and compare the result.
    Using this "hand" technique for ProgressBar I found:
    - Min and Max are stored as float values.
    - Orientation and Scrolling are enum values and according to MS IDL specifications (https://docs.microsoft.com/en-us/windows/win32/midl/midl-start-page), are short values by default.
    What's coming next is really difficult to understand and find the logic behind:
    - BorderStyle, Enabled, Appearance, MousePointer and OleDropMode values are stored in s single short value! All are enum types except Enabled which is a boolean value.
    In order to decode these values we have to use a bit masks as I observed during my tests!
    Those properties can be changed from custom properties dialog. Min, Max and Orientation, also could be changed from custom properties dialog but are stored separate not combined!
    Why Application Builder combine those values? Which criteria is used to decide how to combine values and for which properties?
    For example Orientation and Scrolling are also enum types having two possible values, 0 or 1, but they are stored separately!
    @Enabled
    is defined as VARIANT_BOOL (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/7b39eb24-9d39-498a-bcd8-75c38e5823d0) having 2 possible values
    0xFFFF (TRUE) or 0x0000 (FALSE). VT_BOOL is also similar defined (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-mqcn/085232f8-e4f3-4dcc-9150-df57c8bdbbfa)
    Looking at note #4438-10 for PSTimer the VARIANT_BOOL value is stored in 1 byte and in ProgressBar in 1 bit!
  • FOOTER - Is the last structure in the .wrx file. Even though here we have also unknown data, getting the Name of the control is not a problem. He is always a string at offset 0x0004 from
    the beginning of FOOTER.
My conclusions after decoding on ProgressBar .wrx binary content:
  • The structure pattern is the same, and seems to be always the same, a serie of structures: HEADER, CLSID, COCLASS:NAME, LICENSE, OCX:DATA and FOOTER.
  • It's easy to traverse and extract data for all these structures except for OCX:DATA which is the most complex structure and looks different for each OCX.
    Unfortunately it's impossible to find a logic behind the structure of OCX:DATA block! There are a lot of unknown data in this block which make impossible to "guess" the real structure.

#15 Updated by Greg Shah over 4 years ago

Why Value property is not available for change at design time? What is criteria used by Application Builder to decide not to expose this property at design time?

I think Value is treated as a special property. From IDispatch::GetIDsOfNames():

An IDispatch implementation can associate any positive integer ID value with a given name. Zero is reserved for the default, or Value property; –1 is reserved to indicate an unknown name; and other negative values are defined for other purposes.

The 4GL may simply only deal with non-zero property ids. On the other hand, the type library tool reports Value as property id 5 so I don't know. Perhaps the properties with the name "Value" are also excluded as special (not to be set at design time).

It's easy to traverse and extract data for all these structures except for OCX:DATA which is the most complex structure and looks different for each OCX.
Unfortunately it's impossible to find a logic behind the structure of OCX:DATA block! There are a lot of unknown data in this block which make impossible to "guess" the real structure.

I wonder if the 1C 00 00 00 at AB is the length of the property data (from AF through CA), just before the FFFFFFFFFFFFFFFF at CB. Then again, this doesn't appear in the PSTimer data so it is probably just me imagining patterns where none exist.

I guess one of the following is true:

  1. The structure of the OCX:DATA section can be deduced from the property type info that can be inspected via the COM APIs; OR
  2. The structure is encoded inside the .wrx itself. The only obvious possibilities of this is the data from 93 through AA and/or BB through C6 and/or the data from D3 through E6. Everything else seems to be accounted for.

Considering that the property ids are not encoded in the data and both the size and order of the OCX:DATA block is fixed, I suspect it can be deduced from the exposed COM API (e.g. inspecting the type library).

Why Application Builder combine those values? Which criteria is used to decide how to combine values and for which properties?

Are you sure that the Enabled property is stored in that same bit field? All the other values stored there are part of the same set of MS common control constants. In other words, the type library for the common controls probably exports this as a single bit field that has multiple enums defined as "sections". I suspect that the masks and encoding map directly to how the type library reports the properties structure.

As a next step, please dig in to the type library APIs (the ITypeLib and ITypeInfo seem most useful). Writing some code to use these APIs may explain some things that are otherwise quite strange right now.

#16 Updated by Vladimir Tsichevski over 4 years ago

Greg Shah wrote:

Why Value property is not available for change at design time? What is criteria used by Application Builder to decide not to expose this property at design time?

The following is an excerpt from here: https://documentation.progress.com/output/ua/OpenEdge_latest/dvpin/understanding-design-time-and-runtime-properties.html

Understanding design time and runtime properties

To customize the definition for an ActiveX control, you must change the values of control properties in the AppBuilder at design time. Each ActiveX control supports a specific list of properties that you can set at design time (design time properties) and another list of properties that you can read or write at runtime (run-time properties). (You can set some properties at both design time and runtime.)

To modify design time properties, you generally require a license from the control vendor. The vendor typically provides this license to application developers (as opposed to application end-users) for installation with the control.

The AppBuilder provides access to all available design time properties using the OCX Property Editor window. This window contains all design time properties including the extended properties that ABL adds. This is the only way to set values for properties writable only at design time.

To modify run-time properties, you write ABL code that sets the property value using the component handle to the control.

The Value property of ProgressBar is a runtime property, you cannot set it neither in AppBuilder not it the "native" property editor in VisualStudio.

#17 Updated by Greg Shah over 4 years ago

The appbuilder code (see possenet in the src/adeuib/ directory) itself uses an OCX control for the "Property Editor" (and probably for some other parts too). I wonder if this OCX is from Microsoft. Anyway, the reliance upon external native code may be one reason for the appbuilder to be quite unstable.

#18 Updated by Greg Shah over 4 years ago

  • Related to Feature #4174: implement calendar control/dtpicker OCX replacement added

#21 Updated by Greg Shah over 3 years ago

A very short knowledge base article asks:

What do wrx files contain?

Here is the documented answer:

WRX files contain design and runtime license keys (if required by the ActiveX Controls) along with any custom property settings that were made to any ActiveX Control in the window.

WRX files do not contain any ABL source or .r code.

#22 Updated by Hynek Cihlar over 2 years ago

Greg Shah wrote:

The appbuilder code (see possenet in the src/adeuib/ directory) itself uses an OCX control for the "Property Editor" (and probably for some other parts too).

Can't we use the OCX to parse WRX files?

#23 Updated by Vladimir Tsichevski over 2 years ago

Hynek Cihlar wrote:

Greg Shah wrote:

The appbuilder code (see possenet in the src/adeuib/ directory) itself uses an OCX control for the "Property Editor" (and probably for some other parts too).

Can't we use the OCX to parse the WRX files?

WRX files are from the OpenEdge domain, OCX controls know nothing about the WRX.

We can probably extract the data from a WRX file using the following steps:

  1. In AppBuilder, create a control for the same type of OCX, save the control along with a WRX file.
  2. Replace the WRX file by one we want to extract data from
  3. Re-open the control with AppBuilder, see the property values.

#24 Updated by Hynek Cihlar over 2 years ago

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Greg Shah wrote:

The appbuilder code (see possenet in the src/adeuib/ directory) itself uses an OCX control for the "Property Editor" (and probably for some other parts too).

Can't we use the OCX to parse the WRX files?

WRX files are from the OpenEdge domain, OCX controls know nothing about the WRX.

I meant the specific OCX control Greg mentions above. If it is used for showing/editing OCX properties, we could use it, too.

#25 Updated by Vladimir Tsichevski over 2 years ago

Hynek Cihlar wrote:

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Greg Shah wrote:

The appbuilder code (see possenet in the src/adeuib/ directory) itself uses an OCX control for the "Property Editor" (and probably for some other parts too).

Can't we use the OCX to parse the WRX files?

WRX files are from the OpenEdge domain, OCX controls know nothing about the WRX.

I meant the specific OCX control Greg mentions above. If it is used for showing/editing OCX properties, we could use it, too.

Which exactly OCX control is it?

#26 Updated by Hynek Cihlar over 2 years ago

Vladimir Tsichevski wrote:

Which exactly OCX control is it?

See Greg's note #4438-17. I just had a quick look in the possenet sources so I can't give you good answer ATM.

#27 Updated by Vladimir Tsichevski over 2 years ago

Hynek Cihlar wrote:

Vladimir Tsichevski wrote:

Which exactly OCX control is it?

See Greg's note #4438-17. I just had a quick look in the possenet sources so I can't give you good answer ATM.

If this OCX is from Microsoft, when it cannot be used to read WRX files.

#28 Updated by Hynek Cihlar over 2 years ago

The OCX properties are indeed handled by a COM component, but this comes with OpenEdge. It resides in prox.dll and the following is its type library.

Unfortunately it doesn't contain methods for manipulating individual OCX properties. The main method is EditProperties, it handles all the edit flow - UI and persistence.

When looking at the imported system functions of prox.dll the following few look interesting:

StgCreateDocfile
StgOpenStorage
CreateILockBytesOnHGlobal
StgCreateDocfileOnILockBytes
StgOpenStorageOnILockBytes

Apparently it uses COM compound storage for storing the properties (or perhaps only their related data). StgCreateDocfileOnILockBytes creates compound storage on a provided byte buffer, perhaps prox.dll embeds compound storage in wrx files? I.e. the wrx itself is proprietary but part of it is the standard COM compound storage?

#29 Updated by Hynek Cihlar over 2 years ago

Here are some more findings.

Every wrx file is a COM/OLE structured storage file so it can be treated with the COM/OLE Windows system functions like StgOpenStorage.

The structured storage format is a directory like structure. The data is stored in streams (represented by IStream COM interface), streams are placed in directories called storages (represented by IStrogare COM interface). Storages can be nested. Structured storage file can also contain property sets, which always occur at the top level and can't be nested. Property is a name-value structure.

The WRX file contains n number of root storages for every control frame. The storage is named after the control frame's NAME property value. Every control frame storage contains two streams, one named Frame and the other Control0. The WRX file at its root level doesn't contain any property sets.

Now the content of the streams is what's unknown. It is either completely proprietary or it also contains a structured storage blob embedded. I will do some quick tests tomorrow to confirm this.

I created a utility for dumping data of all the streams in a wrx file. I will post this tomorrow.

#30 Updated by Hynek Cihlar over 2 years ago

I have some more findings.

Please note that all the presented results are preliminary as they were tested on a single OCX object. It is possible that different OCX object capabilities (like the supported standard OLE interfaces) may make prox.dll choose different approach how it implements persistence of OCX properties.

When LoadControls is invoked the following happens:
1. prox.dll opens the corresponding wrx and reads the OCX Object IDs.
2. prox.dll uses the Object IDs and loads the type library of the OCXes being loaded, it introspects the OCX and gathers all the design properties, their names, IDs, enum labels and enum values, etc...
3. prox.dll instantiates the OCX object using the IDs loaded from Control0 stream.
4. prox.dll queries IPersistMemory and IPersistStreamInit interfaces from the instantiated OCX. It uses them (or one of them, not yet sure) to load the data from the Control0 stream (or part of it). Hence the property data format we see, if this will be confirmed with further tests, is proprietary to every OCX.
5. prox.dll queries the value of every design property of the OCX previously loaded with the stream data and displays it in the property grid.

We could create a utility that would essentially do the same, but instead of showing the property values in a grid we would output them in a predefined (xml) format in a file and use it during conversion (or runtime if LoadControls is preserved after conversion).

Unknowns:
1. Does prox.dll use all the stream data from the structured storage IStream for deserializing the props or does it use a subset?
2. Does the specific OCX have to support all the OLE COM interfaces mentioned above? How will prox.dll behave when some of the key interfaces are not supported by the OCX? Is this even an issue? Do all OCX's we currently convert support all the above mentioned OLE interfaces?
3. How does prox.dll select the design OCX properties?
4. There seem to be some 4GL specific properties shown in the prox.dll property editor, like Visible or Enabled. If needed these will have to be handled with a different logic.

My next step will be to write the utility to dump the property values and try it on the other OCXes.

#32 Updated by Hynek Cihlar about 2 years ago

I can report a success in parsing OCX properties for PSTimer. I successfully read the Interval and Enabled properties.

But there is still some work required. Especially in deserializing all the various property data types and confirming some of the assumptions (like the offset in the IStream stream where the actual OCX data start).

I will now focus on more complex OCXes and on adding the missing pieces for the proper parser.

#33 Updated by Greg Shah about 2 years ago

That is huge! It will be a big step ahead since we will be able capture the output for all .wrx files up front.

I assume the utility being developed compiles and links using the mingw approach we have in our src/native/. I'm looking forward to seeing the code. What are your thoughts on how to encode the output (e.g. XML)?

#34 Updated by Hynek Cihlar about 2 years ago

Greg Shah wrote:

That is huge! It will be a big step ahead since we will be able capture the output for all .wrx files up front.

I assume the utility being developed compiles and links using the mingw approach we have in our src/native/.

I'm currently compiling the code in Visual Studio, but I plan to convert the project into mingw.

I'm looking forward to seeing the code.

At this point it is very prototypal.

What are your thoughts on how to encode the output (e.g. XML)?

Yes, I think an XML processed during conversion should work. The structure could look like the one below.

<wrx-properties>
  <object id="<an identifier (clsid?)>">
     <property name="" type="" value=""/>
     <property name="" type="" value=""/>
     <property name="" type="" value=""/>
     ...
  </object>
  <object id="<an identifier (clsid?)>">
     <property name="" type="" value=""/>
     <property name="" type="" value=""/>
     <property name="" type="" value=""/>
     ...
  </object>
  ...
</wrx-properties>

#35 Updated by Greg Shah about 2 years ago

It all looks good.

#36 Updated by Hynek Cihlar about 2 years ago

Some of the OCXes require a license key otherwise they fail to instantiate. I found out the license key is part of the wrx file. However I don't know yet how to calculate its offset.

#37 Updated by Hynek Cihlar about 2 years ago

Hynek Cihlar wrote:

Some of the OCXes require a license key otherwise they fail to instantiate. I found out the license key is part of the wrx file. However I don't know yet how to calculate its offset.

Thanks Marius for figuring this out in note #4438-10 :-).

#38 Updated by Hynek Cihlar about 2 years ago

  • % Done changed from 0 to 90

The initial version of Wrx parser is finished. I can already use it to unblock some of the related issues.
The remaining tasks:
1. Parsing some wrx files gives errors. I think the problem is in the wrong object state data offset in the wrx file.
2. The sources must be currently built with Visual Studio until it is ported in MinGW.
3. Some code beautification.

Greg, where can I commit the sources? You mentioned src/native, but wouldn't tools be a better location? I think it is more in line with things like gen-text-metrics.p.

#39 Updated by Greg Shah about 2 years ago

2. The sources must be currently built with Visual Studio until it is ported in MinGW.

Eugenie can help with this.

Greg, where can I commit the sources? You mentioned src/native, but wouldn't tools be a better location? I think it is more in line with things like gen-text-metrics.p.

You aren't wrong that it does fit in tools but there is a benefit to making it easy to build this. If we put it in tools, then I think we should have a makefile. Putting it in src/native/ allows us to leverage that build environment.

#40 Updated by Hynek Cihlar about 2 years ago

Greg Shah wrote:

2. The sources must be currently built with Visual Studio until it is ported in MinGW.

Eugenie can help with this.

Greg, where can I commit the sources? You mentioned src/native, but wouldn't tools be a better location? I think it is more in line with things like gen-text-metrics.p.

You aren't wrong that it does fit in tools but there is a benefit to making it easy to build this. If we put it in tools, then I think we should have a makefile. Putting it in src/native/ allows us to leverage that build environment.

OK I checked the source file in src/native. I will provide the makefile later. See 3821c revision 13540.

Also available in: Atom PDF