Late Binding or its Equivalent  Topic is solved

Discussions relating to plugin development, and the Jiwa API.

Late Binding or its Equivalent

Postby SBarnes » Fri Apr 01, 2016 8:31 am

Hi Guys,

Would it be possible to provide an example of what might be the equivalent to late binding under COM against the Jiwa assemblies to login to Jiwa and then access the Inventory object I am trying to put together some code for a utlity program external to Jiwa that wouldn't be bound to a specific version of the assemblies but would rather load them at runtime access the inventory object and insert an image for the given product into the database. This will be all based upon the convention that the image name matches the Jiwa part number.

Thanks.
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175

Re: Late Binding or its Equivalent

Postby Mike.Sheen » Fri Apr 01, 2016 1:52 pm

SBarnes wrote:Hi Guys,

Would it be possible to provide an example of what might be the equivalent to late binding under COM against the Jiwa assemblies to login to Jiwa and then access the Inventory object I am trying to put together some code for a utlity program external to Jiwa that wouldn't be bound to a specific version of the assemblies but would rather load them at runtime access the inventory object and insert an image for the given product into the database. This will be all based upon the convention that the image name matches the Jiwa part number.

Thanks.


Reflection is what is needed to do this. It can be a bit cumbersome without using any references, but possible - this example code uses no references to the Jiwa assemblies, but loads them dynamically at runtime to logon and then create an inventory business logic object and read records:

Code: Select all
// Load the JiwaApplication.dll assembly using file path
var assembly = System.Reflection.Assembly.Load(System.Reflection.AssemblyName.GetAssemblyName(@"C:\Program Files (x86)\Jiwa Financials\Jiwa 7\JiwaApplication.dll"));
// Create a type for JiwaApplication.Manager
var jiwaApplicationManagerType = assembly.GetType("JiwaFinancials.Jiwa.JiwaApplication.Manager");
object jiwaApplicationManager = Activator.CreateInstance(jiwaApplicationManagerType);

//Get the JiwaApplication.Manager.Instance property value
System.Reflection.PropertyInfo jiwaApplicationManagerInstanceProperty = jiwaApplicationManagerType.GetProperty("Instance");
object jiwaApplicationManagerInstance = jiwaApplicationManagerInstanceProperty.GetValue(jiwaApplicationManager, null);

// Invoke the Logon method of the JiwaApplication.Manager.Instance
System.Reflection.MethodInfo logonMethod = jiwaApplicationManagerInstance.GetType().GetMethod("Logon");
object[] logonArguments = new object[] { "JiwaMike2", "JiwaDemo157", 1, "Admin", "password" };
logonMethod.Invoke(jiwaApplicationManagerInstance, logonArguments);


// Get the business logic factory instance
var businessLogicFactoryType = assembly.GetType("JiwaFinancials.Jiwa.JiwaApplication.BusinessLogicFactory");
object businessLogicFactory = Activator.CreateInstance(businessLogicFactoryType);
// In a perfect world, we shoule be able to find the method using this: System.Reflection.MethodInfo businessLogicFactoryMethod = businessLogicFactory.GetType().GetMethod("CreateBusinessLogic");
// but we cannot as it is overloaded and uses a type as a parameter that we don't know about (JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm).
// So instead we need to iterate through the methods, finding the one with the name and signature we want :
// public IJiwaBusinessLogic CreateBusinessLogic(string ClassName, IJiwaForm Client)

System.Reflection.MethodInfo businessLogicFactoryMethod = null;
var businessLogicFactoryMethods = businessLogicFactoryType.GetMethods();
foreach (System.Reflection.MethodInfo method in Array.FindAll<System.Reflection.MethodInfo>(businessLogicFactoryMethods, m => m.Name == "CreateBusinessLogic"))
{
   System.Reflection.ParameterInfo[] parameters = method.GetParameters();
   foreach (System.Reflection.ParameterInfo p in parameters)
   {
      // if we have a string parameter, then we know this is the signature we want as only one of the overloaded CreateBusinessLogic has a string parameter
      if (p.ParameterType == typeof(string))
      {
         businessLogicFactoryMethod = method;
         break;
      }
   }
}

if (businessLogicFactoryMethod == null)
{
   throw new Exception("Could not reflect CreateBusinessLogic(string ClassName, IJiwaForm Client)");
}

// Create an instance of the Inventory business logic using our factory
object[] factoryArguments = new object[] { "JiwaFinancials.Jiwa.JiwaInventory.Inventory", null };
object inventoryBusinessLogic = businessLogicFactoryMethod.Invoke(businessLogicFactory, factoryArguments);

// invoke the read method
// public void Read(string RecID)
System.Reflection.MethodInfo readMethod = inventoryBusinessLogic.GetType().GetMethod("Read");
readMethod.Invoke(inventoryBusinessLogic, new object[] { "000000002400000002PQ" });

// invoke the find method
// public void Find(JiwaApplication.IJiwaNavigable.ReadModes ReadMode, string FieldName, string FieldValue, string FilterSQL)
System.Reflection.MethodInfo findMethod = inventoryBusinessLogic.GetType().GetMethod("Find");
findMethod.Invoke(inventoryBusinessLogic, new object[] { 0, "IN_Main.PartNo", "1170", "" });
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: Late Binding or its Equivalent  Topic is solved

Postby Mike.Sheen » Fri Apr 01, 2016 5:33 pm

Another, cleaner way to do this is to use C#'s dynamic type:

Code: Select all
// Load the JiwaApplication.dll assembly using file path
var assembly = System.Reflection.Assembly.Load(System.Reflection.AssemblyName.GetAssemblyName(@"C:\Program Files (x86)\Jiwa Financials\Jiwa 7\JiwaApplication.dll"));
// Create a type for JiwaApplication.Manager
var jiwaApplicationManagerType = assembly.GetType("JiwaFinancials.Jiwa.JiwaApplication.Manager");
// Create an instance of JiwaApplication.Manager
dynamic jiwaApplicationManager = Activator.CreateInstance(jiwaApplicationManagerType);

//Get the JiwaApplication.Manager.Instance property value
System.Reflection.PropertyInfo jiwaApplicationManagerInstanceProperty = jiwaApplicationManagerType.GetProperty("Instance");
dynamic jiwaApplicationManagerInstance = jiwaApplicationManagerInstanceProperty.GetValue(jiwaApplicationManager, null);

// Invoke the Logon method of the JiwaApplication.Manager.Instance
System.Reflection.MethodInfo logonMethod = jiwaApplicationManagerInstance.GetType().GetMethod("Logon");
object[] logonArguments = new object[] { "JiwaMike2", "JiwaDemo157", 1, "Admin", "password" };
logonMethod.Invoke(jiwaApplicationManagerInstance, logonArguments);

// Create an instance of the Inventory business logic (this uses the class name "JiwaFinancials.Jiwa.JiwaInventory.Inventory" which the CreateBusinessLogic uses to look up the SY_BusinessLogic table to obtain the assembly information, create the instance, invoke the Setup method and invoke plugins interested in this business logic)
dynamic inventoryBusinessLogic = jiwaApplicationManagerInstance.BusinessLogicFactory.CreateBusinessLogic("JiwaFinancials.Jiwa.JiwaInventory.Inventory", null);

// invoke the read method
// public void Read(string RecID)
inventoryBusinessLogic.Read("000000002400000002PQ");

// invoke the find method
// public void Find(JiwaApplication.IJiwaNavigable.ReadModes ReadMode, string FieldName, string FieldValue, string FilterSQL)
inventoryBusinessLogic.Find(0, "IN_Main.PartNo", "1170", "");


This makes it somewhat easier for the use of the inventory business logic, but you're still stuck with using the method.Invoke technique for logging on, as if you tried something like:
Code: Select all
jiwaApplicationManagerInstance.Logon("JiwaMike2", "JiwaDemo157", 1, "Admin", "password")

It will complain about the signature not matching (the 1 is not of the right type - it wants the enumeration, not an int).

But, it's a small inconvenience - at least the code is back to being readable when using the dynamic type.
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: Late Binding or its Equivalent

Postby SBarnes » Fri Apr 01, 2016 5:57 pm

Hi Mike,

Thanks for the quick response, I had figured it was goinng to require using reflection.

I actually needed the code in c# given I already have the image load etc in c#, which I probably should have said in the first place so version two is going to make things easier anyway and I agree with your comments about it being more readable as the better option here.

I will test the code out in the in the application.
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175

Re: Late Binding or its Equivalent

Postby SBarnes » Fri Apr 01, 2016 11:08 pm

Hi Mike,

I've now been able to get everything going one snag I did run into was with the login when it tried to load up the plugins as it didn't know where to find the assemblies i.e it threw an exception with HandleApplicationManagerPluginExceptions in the stack trace because all the dlls are not in the application directory which is the same problem you helped with under ASP before.

This time I worked out after doing some searching on the web that writing an assembly resolver event handler was the simplest solution, I have included the hooking up of the event handler and the signature of the handler below if it helps anyone else coming accross this problem.

Again thanks for all the help.


AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);

Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175

Re: Late Binding or its Equivalent

Postby Mike.Sheen » Sat Apr 02, 2016 2:03 pm

SBarnes wrote:one snag I did run into was with the login when it tried to load up the plugins as it didn't know where to find the assemblies


Oh, yes - I forgot about that.

SBarnes wrote:This time I worked out after doing some searching on the web that writing an assembly resolver event handler was the simplest solution


Glad you worked it out. We do exactly the same thing within Jiwa to resolve assemblies.
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: Late Binding or its Equivalent

Postby Mike.Sheen » Sun Apr 03, 2016 11:44 am

For completeness and the sake of others looking at this thread wishing to do something similar, a sample of the AssemblyResolve handler is as follows:

Code: Select all
static System.Reflection.Assembly AssemblyResolve(object sender, ResolveEventArgs e)
{
   string jiwaApplicationPath = @"C:\Program Files (x86)\Jiwa Financials\Jiwa 7";
   System.Reflection.Assembly resolvedAssembly = null;
   
   // NOTE: sometimes e.Name is the assembly FullName, sometimes it may be a path - depending on how the assembly is attempted to be loaded - the below will handle both cases
   string filename = e.Name.Split(new Char[] { ',' })[0].Replace(@"\\", @"\");
   string filenameAndPath = System.IO.Path.Combine(jiwaApplicationPath, filename) + ".dll";
   
   if (System.IO.File.Exists(filenameAndPath))
   {
      resolvedAssembly = System.Reflection.Assembly.LoadFrom(filenameAndPath);
   }
   
   return resolvedAssembly;
}


And somewhere in your application (before any reference to a Jiwa class), add the handler:
Code: Select all
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;


In the above I've hard coded the path for the Jiwa assemblies location, but you probably want to make it a configuration setting, and if you want to get really fancy - interrogate the system to find the install path of the Jiwa application (but do that once at application start and store the path - don't interrogate the system within the AssemblyResolve handler or performance will suffer).

Note that the above code does not throw an exception if it cannot locate the assembly, it simply returns a null - this is intentional as the JiwaApplication.Manager has it's own AssemblyResolve handler and will be invoked if the above returns null and should successfully resolve any plugin references (references plugins may have to other plugins, or embedded references) - it knows to look in the %ProgramData%\Jiwa Financials folder for assemblies which are compiled or placed there.
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


Return to Technical and or Programming

Who is online

Users browsing this forum: No registered users and 6 guests

cron