Plugin private fields shared across form instances  Topic is solved

Discussions relating to plugin development, and the Jiwa API.

Plugin private fields shared across form instances  Topic is solved

Postby neil.interactit » Thu May 31, 2018 12:12 pm

Hey everyone,

Sorry if this has been covered elsewhere - if so, I missed it and got caught out. To help other plugin developers this is an alert that plugin private fields are shared across form instances.

I'm guessing this is intentional/intended behaviour, just that from the "outside looking in" as a developer it caught me out as completely unexpected.

So to explain ... with this simple test plugin ...
Code: Select all
        public void Setup(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
        {
            if (!(JiwaForm is frmDebtor)) return;
            var form = (frmDebtor)JiwaForm;
            form.Debtor.ReadEnd += (sender, e) => RunTest(form);
        }
        private string _private;
        private void RunTest(frmDebtor form)
        {
            _private += "A";
            form.Tag += "B";
            File.AppendAllText("C:\\temp\\jiwalog.txt", _private + "\n");
            File.AppendAllText("C:\\temp\\jiwalog.txt", form.Tag + "\n\n");
        }

Open a debtor maintenance form, click "next" twice, open a second debtor maintenance form (tab), click "next" twice, go back to the first debtor maintenance form (tab), click "next" twice.

The result is:
Code: Select all
A
B

AA
BB

AAA
BBB

AAAA
B

AAAAA
BB

AAAAAA
BBB

AAAAAAA
BBBB

AAAAAAAA
BBBBB
Using the (passed) form.Tag, you are seeing a 1/2/3 Bs from the first debtor tab, then 1/2/3 Bs from the second debtor tab, then 4/5/6 Bs from the first debtor tab, all good!
However, the private field is in common across the 2 tabs, so in the test above, each debtor tab continues to alter the value from the other tab.

So I have been in the habit of creating a private form field and passing it around the class ...
Code: Select all
        private frmDebtor _form;
        public void Setup(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
        {
            if (!(JiwaForm is frmDebtor)) return;
            _form = (frmDebtor)JiwaForm;
            _form.Debtor.ReadEnd += Debtor_ReadEnd;
        }
        private void Debtor_ReadEnd(object sender, System.EventArgs e)
        {
            _form.Tag += "B";
        }
... thinking private meant "private per tab instance". This results in all sorts of issues as soon as a user opens a second tab of the same form. In this case, the _form.Debtor on both tabs is the last debtor selected on either tab.

The solution is to pass everything that you need to persist to your handlers (as the samples the Jiwa guys provide do!). This is often the "sender", but if not, you can lambda it ...
Code: Select all
        public void Setup(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
        {
            if (!(JiwaForm is frmDebtor)) return;
            var form = (frmDebtor)JiwaForm;
            form.Debtor.ReadEnd += (sender, e) => Debtor_ReadEnd(form);
        }
        private void Debtor_ReadEnd(frmDebtor form)
        {
            form.Tag += "B";
        }

Again, sorry is this is dealt with elsewhere, but I hope this might help others.

Cheers,
Neil.
neil.interactit
Kohai
Kohai
 
Posts: 227
Joined: Wed Dec 03, 2014 2:36 pm
Topics Solved: 6

Re: Plugin private fields shared across form instances

Postby Mike.Sheen » Thu May 31, 2018 8:52 pm

Hi Neil,

Sorry to hear of your troubles, but glad you've worked it out. Thanks for the post and comprehensive detail.

I don't believe we explicitly mention in any documentation that private members in our plugin classes are shared across form instances - that's something we need to work on to make all our lives a little easier.

If you are curious as to why - it's because we found early on it at times can be rather expensive to instantiate the plugin class instances. This was a while ago that discovery was made (pre-initial V7 release), and things have evolved a fair bit since then - so I need to revisit that.

When Jiwa first loads (to be accurate it's when the Login method of the JiwaApplication.Manager class is invoked), it looks for all enabled plugins requiring compilation (the ones where the hash indicates the cached assembly in the %ProgramData%\Jiwa Financials... folder differ from the hash stored in the SY_Plugin table) and compiles those assemblies. We then load each assembly into the app domain of Jiwa, and instantiate an instance of the form or business logic class - that can all be a fairly expensive operation - even just the class instantiations.

When a form or business logic is requested via our factories, we look at what forms or business logic the plugin has registered an interest in (done by adding a form to the forms tab of the plugin, or the business logic tab of the assemblies tab of the plugin), and if there is a registered interest of the item the factory is creating - then after instantiating an instance of the form or business logic we then iterate through the plugins which have registered an interest in the form or business logic and invoke the SetupBeforeHandlers method of the appropriate plugin class, then we invoke the Setup method of the form or business logic and finally we invoke the Setup method of each registered plugin class.

So - as you've worked out - our plugin classes are instantiated once at application start, and the SetupBeforeHandlers and Setup methods are invoked as forms or business logic are requested from their respective factory.

It does mean there is potentially some very nasty gotcha's the uninitiated may code in, and it won't be an issue until a second form or business logic object is loaded. We have been caught by this ourselves!

I plan on doing some experimentation on seeing if we can make it so the factories create a new instance of the plugin classes without a significant impact on performance - that will likely end up being an opt-in property of the plugin to preserve any existing plugin behaviour.

Mike
Mike Sheen
Chief Software Engineer
Jiwa Financials

If I do answer your question to your satisfaction, please mark it as the post solving the topic so others with the same issue can readily identify the solution
User avatar
Mike.Sheen
Overflow Error
Overflow Error
 
Posts: 2444
Joined: Tue Feb 12, 2008 11:12 am
Location: Perth, Republic of Western Australia
Topics Solved: 756

Re: Plugin private fields shared across form instances

Postby neil.interactit » Fri Jun 01, 2018 12:20 pm

Thanks Mike.

I spent yesterday retrofitting my plugins and arrived at a pretty efficient pattern, worth noting ...

Code: Select all
        private frmDebtor _debtorForm;
        private UltraGroupBox _planUltraGroupBox;
        private bool _settingCustomField;
replaced with
Code: Select all
        public class Fields
        {
            public frmDebtor Form { get; set; }
            public UltraGroupBox PlanUltraGroupBox { get; set; }
            public bool SettingCustomField { get; set; }
        }
...
            var fields = new Fields { Form = form, PlanUltraGroupBox = planUltraGroupBox, };
            form.Debtor.PropertyChanged += (sender, e) => GetCustomFieldValues(fields);
neil.interactit
Kohai
Kohai
 
Posts: 227
Joined: Wed Dec 03, 2014 2:36 pm
Topics Solved: 6

Re: Plugin private fields shared across form instances

Postby SBarnes » Sat Jun 02, 2018 1:06 pm

My way of usually dealing with this is to use the forms GenericObjectCollection so values go with the form itself, although this does create an amount of extra code to store and read variables.

It would be good if this could be simplified.
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175

Re: Plugin private fields shared across form instances

Postby Mike.Sheen » Wed Jun 20, 2018 12:04 pm

SBarnes wrote:It would be good if this could be simplified.


I've added some helper methods in 07.01.03 - DEV-6669 - this should make things a bit easier when dealing with the generic object collection.

SetOrAdd
This function allows a single call to either set the value in the collection with a key provided, or add a new item.
Code: Select all
public GenericObjectItem SetOrAdd<T><string RecID, T Value>

Example usage:
Code: Select all
inventoryForm.GenericObjectCollection.SetOrAdd<string>("oldPartNo", inventoryForm.Inventory.PartNo);


GetValue
This function returns either null or the object value for a given key.
Code: Select all
public T GetValue<T>(string RecID)

Example usage:
Code: Select all
string oldPart = inventoryForm.GenericObjectCollection.GetValue<string>("oldPartNo");


Add
This function overloads the existing Add, accepting a generic parameter.
Code: Select all
public GenericObjectItem Add<T>(string RecID, T Value)

Example usage:
Code: Select all
inventoryForm.GenericObjectCollection.Add<string>("oldPartNo", inventoryForm.Inventory.PartNo);
Mike Sheen
Chief Software Engineer
Jiwa Financials

If I do answer your question to your satisfaction, please mark it as the post solving the topic so others with the same issue can readily identify the solution
User avatar
Mike.Sheen
Overflow Error
Overflow Error
 
Posts: 2444
Joined: Tue Feb 12, 2008 11:12 am
Location: Perth, Republic of Western Australia
Topics Solved: 756

Re: Plugin private fields shared across form instances

Postby SBarnes » Wed Jun 20, 2018 12:17 pm

Thanks Mike very 8-)
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175


Return to Technical and or Programming

Who is online

Users browsing this forum: No registered users and 17 guests

cron