After you make Rainmeter plugins for long enough you start to learn a lot of things you could have done better in your earlier plugins. This should help serve as a guide so you have as little of those moments as possible.
Some skin authors can be overzealous about turning on DynamicVariables on measures even if it is not needed. Because of this, you should attempt to minimize any overhead that might be caused if an author uses DynamicVariables. Common optimzations include:
- Move any code that should only be run once from the Reload function to the Initialize function.
- If an option cannot support DynamicVariables, call it from Intialize.
- If possible, cache values that require more computation to reduce resource overhead.
Taking advantage of
You might notice that every single function you can implement that Rainmeter can call has a argument called
data. You should set any measure specific data to this variable and Rainmeter will automatically pass it to most of the other functions when called. Care should be taken when modifying any data across multiple threads (see multithreading).
In all of our examples you will find that we use a "Measure" structure/class and store it in the
data variable. It is best to do this in the Intialize function, but not required. If you decide to utilize the
data variable, you may set/modify/use that structure in any function and it will stay specific to that measure.
Don't forget to clean up any data in the Finalize function!
When to save the
Most of the API functions that require the use of
rm pointer should be called in the Initialize or Reload functions. The main expection to this is calling the log functions, which often need to be called outside of the Initialize or Reload functions. Because of this, it might be required to save the
rm pointer for later use. The best place to save this is in the Initialize function since it's value will not change for the life of the measure.
While it is not required to send the
rm pointer to the logging functions, without it, there will be no source in the log.
Adding custom bangs
If your plugin needs to be called "on demand" when a user clicks a button (or any other actions), then you should consider supporting a custom bang. The skin will call your custom bang by using the !CommandMeasure bang and send the arguments to your ExecuteBang function.
The arguments will be sent to your plugin via a raw string. You will be responsible for any parsing that is needed, so be sure to account for all possibilities that can arise (like any string case issues).
Saving info in the
Rainmeter.data file for later
You may have some data that needs to persist across multiple measures or skins and need a place to save it for later use. You may use the
Rainmeter.data file to save this data if needed.
You can retrieve the full path of the Rainmeter.data file by calling the RmGetSettingsFile function.
Use a unique key so other plugins will not overwrite your data. Keeping track of all the measures that reference your plugin may be necessary in order to prevent other measures of your plugin to overwrite the data. To store data in the settings file, use WritePrivateProfileString. To retrieve data, use GetPrivateProfileString.
Custom section variables
There may be times when you want to return a custom value from your plugin that is independent from the string or number value of the measure. In these cases, you can export a custom function that will be called via section variables. Skin authors will be able to call this custom function "on demand" in any option that supports section variables.
DynamicVariables=1 will be required.
The string returned from your custom function will replace the variable in the option just like any other variable.
Make sure you document each custom function thoroughly so skin authors will know how to use these functions and what arguments are required (if any).
The GetString function can be called multiple times during the update cycle. This function is called whenever a meter accesses the "value" of the measure through the MeasureName option, whenever the measure is used as a section variable, and even if you open the skin in the About dialog. Because of this, it is recommended to set the string value of the measure to a variable in the Update function and access that variable in the GetString function. Reducing the processing in GetString can greatly improve the efficiency of your plugin.
You may choose to have your plugin return a string value in some cases and return a number value in other cases. Any string returned from GetString will be used as the value of the measure (except when explicitly getting the number value through section variables). If you want the number value to be used instead of the string value, return a
nullptr from GetString. This will tell Rainmeter there is no string value and it will use the number value instead.
Because Rainmeter runs in a serial fashion, care should be taken when accessing data or calling Rainmeter functions across multiple threads. Here are some things to keep in mind:
- Measures in Rainmeter can be paused/disabled at any time, which means the Update function will not be called. You should not rely on the Update function to control threads.
- Because a skin can be unloaded or refreshed at any time, make sure detached threads can do any clean up required to your objects.
- If accessing data across threads, make sure to lock any critical sections to prevent race conditions.
- Most of the API functions should not be called in separate threads as they are not thread-safe. RmExecute and RmLog will be the exceptions, but make sure to check if the skin is still running.
When making C# plugins you may notice references to IntPtr's and Marshal functions. Since Rainmeter is written in C++, variables are encoded differently, but don't be intimidated by them.
An IntPtr is basically an integer that represents a pointer to some data, which is why you must deallocate data in finalize as well as recast it in every function. Also since strings are formatted differently in C++, you should pass your string to Marshal.StringToHGlobalUni before returning it in GetString or custom section variables.
Also the MarshalAs in ExecuteBang and SectionVariable examples just makes sure that you get a C# style string or string array out of the box.