It is currently February 20th, 2020, 6:25 pm

Plugin & External Libraries

Share and get help with Plugins and Addons
scotri83
Posts: 9
Joined: February 22nd, 2015, 4:27 am

Plugin & External Libraries

Post by scotri83 »

Hey everyone!

I'm working on a plugin which reads the wattage from my APC. I'm not too familiar with C#/Visual C++ & Visual Studio so please forgive me if this is a noob question...

What I'm trying to do is use LibUsbDotNet to read values from my APC and then communicate them to my rainmeter skin. I created a simple C# test app which uses the LibUsbDotNet library to read the values and it works! The next step was to make a rainmeter Plugin...

I created this simple plugin here to do that for me:

Code: Select all

using System;
using System.Runtime.InteropServices;
using Rainmeter;
using LibUsbDotNet;
using LibUsbDotNet.Main;

namespace APCPlugin
{
    internal class Measure
    {

        public static UsbDevice APC;

        internal Measure()
        {
            APC = UsbDevice.OpenUsbDevice(new UsbDeviceFinder(1309, 2));
        }

        internal void Reload(Rainmeter.API api, ref double maxValue)
        {
            APC = UsbDevice.OpenUsbDevice(new UsbDeviceFinder(1309, 2));
        }

        internal double Update()
        {
            if (APC != null)
            {
                int transferred = 0;
                byte[] buffer = new byte[2];
                UsbSetupPacket setup = new UsbSetupPacket(0xA1, 0x01, 0x0350, 0, 0x0005);
                bool result = APC.ControlTransfer(ref setup, buffer, 0x0040, out transferred);
                return 865 * buffer[1] / 100; //super hardcoded for my APC, it works I've tested in a separate application
            }
            return 5.0; //Return this for testing, just to see if any value is reported
        }
    }

    public static class Plugin
    {
        [DllExport]
        public static void Initialize(ref IntPtr data, IntPtr rm)
        {
            data = GCHandle.ToIntPtr(GCHandle.Alloc(new Measure()));
        }

        [DllExport]
        public static void Finalize(IntPtr data)
        {
            GCHandle.FromIntPtr(data).Free();
        }

        [DllExport]
        public static void Reload(IntPtr data, IntPtr rm, ref double maxValue)
        {
            Measure measure = (Measure)GCHandle.FromIntPtr(data).Target;
            measure.Reload(new Rainmeter.API(rm), ref maxValue);
        }

        [DllExport]
        public static double Update(IntPtr data)
        {
            Measure measure = (Measure)GCHandle.FromIntPtr(data).Target;
            return measure.Update();
        }

    }
}
The problem is, as you can see, I'm using LibUsbDotNet to get this information. When I run the build task it exports APC.dll along with the LibUsbDotNet.dll into the releases folder. However when I copy these over to the rainmeter plugins dir, then open rainmeter, rainmeter just crashes. It states:

Code: Select all

'Rainmeter.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\Matt\AppData\Roaming\Rainmeter\Plugins\APC.dll'. Module was built without symbols.
An unhandled exception of type 'System.IO.FileNotFoundException' occurred in APC.dll
Additional information: Could not load file or assembly 'LibUsbDotNet, Version=2.2.8.104, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
So I went off to google to see what others recommend. People suggested using "ILMerge" to merge the dll files into one. Which sort of worked, as in rainmeter no longer crashes. But nothing is reported back by the plugin.

Is there something I've done that obviously wrong in the code, or am I missing something in the bigger picture here? Thanks for the help!
ted_996
Posts: 7
Joined: March 17th, 2016, 12:20 pm

Re: Plugin & External Libraries

Post by ted_996 »

Hi!

I've run into the same issue. My solution was to dynamically load the library and use reflection (it helps if there are only a few types to use and methods to call, because it's ugly). But, the best way to show you is to show you code:

https://github.com/TED-996/NightshiftCs/blob/master/NightshiftPlugin/NightshiftPlugin.cs

I had to have every type as dynamic because, without linking the DLLs, the compiler cannot use the types. Then, every method has to be called through reflection. Ugly, but it works.

The DLLs were copied to @Resources/Lib. This path was set as a plugin parameter, so it could be accessed from the plugin. If you have any questions about the code, feel free to ask.
scotri83
Posts: 9
Joined: February 22nd, 2015, 4:27 am

Re: Plugin & External Libraries

Post by scotri83 »

Thanks Man! Its a bit of a hack but it should work! Will test it out tomorrow :)
ted_996
Posts: 7
Joined: March 17th, 2016, 12:20 pm

Re: Plugin & External Libraries

Post by ted_996 »

Glad to help! I know it's a hack, but i couldn't find any better solution...
scotri83
Posts: 9
Joined: February 22nd, 2015, 4:27 am

Re: Plugin & External Libraries

Post by scotri83 »

ted_996 wrote:Glad to help! I know it's a hack, but i couldn't find any better solution...
Worked like a charm! Thanks!

Image

Code for those who are curious... Soon to be on github!

Code: Select all

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Rainmeter;

namespace APCPlugin
{
    internal class Measure
    {

        static API api;
        static string dllDir;
        static Type UsbDeviceType;
        static Type UsbDeviceFinderType;
        static Type UsbSetupPacketType;
        dynamic APC;

        internal Measure(IntPtr rm) {
            api = new API(rm);
            dllDir = api.ReadPath("DllFolder", "");
            AppDomain.CurrentDomain.AssemblyResolve += AssemblyDependsResolve;
            try
            {
                var dll = Assembly.LoadFile(Path.Combine(dllDir, "LibUsbDotNet.dll"));
                UsbDeviceType = dll.GetType("LibUsbDotNet.UsbDevice", true);
                UsbDeviceFinderType = dll.GetType("LibUsbDotNet.Main.UsbDeviceFinder", true);
                UsbSetupPacketType = dll.GetType("LibUsbDotNet.Main.UsbSetupPacket", true);
            }
            catch (Exception ex)
            {
                API.Log(API.LogType.Error, $"Could not find DLL or its types. Exception: " + ex);
                throw;
            }
            APC = UsbDeviceType.InvokeMember(
                "OpenUsbDevice",
                BindingFlags.InvokeMethod,
                null,
                null,
                new object[] {
                    Activator.CreateInstance(
                        UsbDeviceFinderType,
                        new object[] {
                            1309,
                            2
                        }
                    )
                }
            );
        }

        internal void Reload(Rainmeter.API api, ref double maxValue)
        {
            APC = UsbDeviceType.InvokeMember(
                "OpenUsbDevice",
                BindingFlags.InvokeMethod,
                null,
                null,
                new object[] {
                    Activator.CreateInstance(
                        UsbDeviceFinderType,
                        new object[] {
                            1309,
                            2
                        }
                    )
                }
            );
        }

        internal double Update()
        {
            if (!object.ReferenceEquals(null, APC))
            {
                int transferred = 0;
                byte[] buffer = new byte[2];

                API.Log(API.LogType.Debug, "TEST");
                dynamic setup = Activator.CreateInstance(
                    UsbSetupPacketType,
                    new object[] {
                        (Byte)0xA1,
                        (Byte)0x01,
                        (Int16)0x0350,
                        (Int16)0,
                        (Int16)0x0005
                    }
                );

                UsbDeviceType.InvokeMember(
                    "ControlTransfer",
                    BindingFlags.InvokeMethod,
                    null,
                    APC,
                    new object[] {
                        setup, buffer, 0x0040, (Int32) transferred
                    }
                );

                return 865 * buffer[1] / 100;
            }
            return 5.0;
        }

        static Assembly AssemblyDependsResolve(object sender, ResolveEventArgs e)
        {
            string dllName = e.Name.Split(',')[0] + ".dll";
            string dllPath = Path.Combine(dllDir, dllName);
            if (!File.Exists(dllPath))
            {
                return null;
            }
            return Assembly.LoadFrom(dllPath);
        }
    }

    public static class Plugin
    {
        [DllExport]
        public static void Initialize(ref IntPtr data, IntPtr rm)
        {
            data = GCHandle.ToIntPtr(GCHandle.Alloc(new Measure(rm)));
        }

        [DllExport]
        public static void Finalize(IntPtr data)
        {
            GCHandle.FromIntPtr(data).Free();
        }

        [DllExport]
        public static void Reload(IntPtr data, IntPtr rm, ref double maxValue)
        {
            Measure measure = (Measure)GCHandle.FromIntPtr(data).Target;
            measure.Reload(new Rainmeter.API(rm), ref maxValue);
        }

        [DllExport]
        public static double Update(IntPtr data)
        {
            Measure measure = (Measure)GCHandle.FromIntPtr(data).Target;
            return measure.Update();
        }

    }
}
ted_996
Posts: 7
Joined: March 17th, 2016, 12:20 pm

Re: Plugin & External Libraries

Post by ted_996 »

Awesome! It sucks that this is the best option we could find, but it's still better than nothing. At least it works :))