I developed a Registry function "class" for Ketarin, and I'm using Doxygen for Windows to doc it.
When I build the documentation (using Doxywizard) following the tutorial on: http://www.tech-coder.com/2016/12/generate-html-documentation-using.html
It docs only the two first functions and nothing else. I tried to check all boxes on the "Build" column at the "Expert" tab but no avail.
The code (including the documentation - that only show 2 functions) can be found on GitHub at https://github.com/coldscientist/RegClassCS
For example, the internal method below is showed normally on docs:
/// <summary>
/// Creates a new subkey or opens an existing subkey for write access.
/// </summary>
/// <param name="rootName">The HKEY to open.</param>
/// <param name="keyName">The name or path of the subkey to create or open. This string is not case-sensitive.</param>
/// <returns>The newly created subkey, or false if the operation failed. If a zero-length string is specified for subkey, the current RegistryKey object is returned.</returns>
Func<string, string, bool> RegCreateKey = new Func<string, string, bool>( (rootName, keyName) =>
{
try
{
Ketarin.Forms.LogDialog.Log("RegCreateKey(" + rootName + ", " + keyName + ")");
Microsoft.Win32.RegistryKey localKey = RegOpenSubKey(rootName);
// localKey = localKey.OpenSubKey(keyName, writable: true);
using (localKey)
{
RegCreateKey:
if (localKey != null)
{
localKey.CreateSubKey( keyName );
}
else
{
// Abort("Key " + rootName + #"\" + keyName + " not found.");
goto RegCreateKey;
}
}
}
catch (Exception ex)
{
// Abort(ex.ToString());
return false;
}
return true;
});
But the following method (below that one) is not documented:
/// <summary>
/// Deletes a subkey and any child subkeys recursively. No warning will be provided.
/// </summary>
/// <param name="rootName">The HKEY to open.</param>
/// <param name="subKeyName">The subkey to delete. This string is not case-sensitive.</param>
/// <returns>Returns false if the operation failed.</returns>
Func<string, string, bool> RegDeleteKey = new Func<string, string, bool>( (rootName, subKeyName) =>
{
try
{
Ketarin.Forms.LogDialog.Log("RegDeleteKey(" + rootName + ", " + subKeyName + ")");
string keyName = subKeyName.Substring(0, subKeyName.LastIndexOf(#"\"));
string subTreeName = subKeyName.Substring(subKeyName.LastIndexOf(#"\")+1);
Microsoft.Win32.RegistryKey localKey = RegOpenSubKey(rootName);
localKey = localKey.OpenSubKey(keyName, writable: true);
using (localKey)
{
if (localKey != null)
{
localKey.DeleteSubKeyTree(subTreeName);
}
}
}
catch (Exception ex)
{
return false;
// Abort(ex.ToString());
}
return true;
});
I'm suspecting that it is related to the way I coded it (the code compiles normally). Maybe there is a comma missing or some code formatting that is making Doxygen missing itself.
Here is the Doxygen output, if it helps:
Searching for include files...
Searching for example files...
Searching for images...
Searching for dot files...
Searching for msc files...
Searching for dia files...
Searching for files to exclude
Searching INPUT for files to process...
Searching for files in directory S:/Applications/Scripts/RegClassCS
Searching for files in directory S:/Applications/Scripts/RegClassCS/doc
Reading and parsing tag files
Parsing files
Reading S:/Applications/Scripts/RegClassCS/README.md...
Preprocessing S:/Applications/Scripts/RegClassCS/RegClass.cs...
Parsing file S:/Applications/Scripts/RegClassCS/RegClass.cs...
Building group list...
Building directory list...
Building namespace list...
Building file list...
Building class list...
Associating documentation with classes...
Computing nesting relations for classes...
Building example list...
Searching for enumerations...
Searching for documented typedefs...
Searching for members imported via using declarations...
Searching for included using directives...
Searching for documented variables...
Building interface member list...
Building member list...
Searching for friends...
Searching for documented defines...
Computing class inheritance relations...
Computing class usage relations...
Flushing cached template relations that have become invalid...
Computing class relations...
Add enum values to enums...
Searching for member function documentation...
Creating members for template instances...
Building page list...
Search for main page...
Computing page relations...
Determining the scope of groups...
Sorting lists...
Freeing entry tree
Determining which enums are documented
Computing member relations...
Building full member lists recursively...
Adding members to member groups.
Computing member references...
Inheriting documentation...
Generating disk names...
Adding source references...
Adding xrefitems...
Sorting member lists...
Computing dependencies between directories...
Generating citations page...
Counting data structures...
Resolving user defined references...
Finding anchors and sections in the documentation...
Transferring function references...
Combining using relations...
Adding members to index pages...
Generating style sheet...
Generating search indices...
Generating example documentation...
Generating file sources...
Generating file documentation...
Generating docs for file README.md...
Generating docs for file RegClass.cs...
Generating page documentation...
Generating docs for page md_README...
Generating group documentation...
Generating class documentation...
Generating namespace index...
Generating graph info page...
Generating directory documentation...
Generating index page...
Generating page index...
Generating module index...
Generating namespace index...
Generating namespace member index...
Generating annotated compound index...
Generating alphabetical compound index...
Generating hierarchical class index...
Generating member index...
Generating file index...
Generating file member index...
Generating example index...
finalizing index lists...
writing tag file...
lookup cache used 2/65536 hits=2 misses=2
finished...
S:/Applications/Scripts/RegClassCS/README.md:3: warning: Unexpected html tag  found within context
*** Doxygen has finished
I give up and migrate to Natural Docs. But I notice that after the internal functions that were not printed before by Doxygen, it showed up internal methods that doesn't exist on my code (but apparently it is obtained by internal methods args - see RegSetValue below). If I remove "RegCreateKey", the method below that ("string") is removed too (obviously). Maybe in Doxygen is happening the same thing, but instead of continue, it stop processing the file (I'm not sure of it). I'm just posting it to help other people and Doxygen to (maybe) check this on the future.
On Natural Docs, I can hide these undesired internal methods (that, in fact, doesn't exist) using the arg -do when building docs, so it shows up only documented internal methods.
Related
Intro
I am looking for more customized solution for translating my app. I will be using Humanizer and Smart.Format after obtaining entries. The problem is to define keys to obtain them in the first place.
Requirements
The requirements are:
Language keys must be defined in-code, preferably near place where they are used
Language keys must contain default-English values
All language keys must be listed (XML, CSV, JSON, anything) after building the app suite
Language entries must be provided from external source (like JSON file), without the need for any kind of recompilation
The app may contain multiple executables, shared libraries, etc. all of them in form of C# apps
Discarded solutions
First, the things I discarded:
Built-in C# Resources.dll; They violate (1) and (4)
External file with keys. Violates (1)
My idea for handling the problem
Now, my idea for the solution looks that way (and is inspired by C++ GetText)
There is a template class which contains keys:
private sealed class Module1Keys : LocalizationKeys<Module1Keys>
{
public static readonly LocalizationKey HelloWorld = DefineKey("/foo", "Hello World!");
public static readonly LocalizationKey HelloWorld2 = DefineKey("/bar", "Hello World2!");
}
And the class LocalizationKeys contains a static method that will actually register keys in simple collection
public abstract class LocalizationKeys<T> where T : LocalizationKeys<T>
{
protected static LocalizationKey DefineKey(string path, string english)
{
var ret = new LocalizationKey(typeof(T), path, english);
// Following registers localization key in runtime:
Localization.Instance.RegisterLocalizableKey(ret);
return ret;
}
}
Problem
The only thing left to handle in this approach is to list localizable keys during build... which is where I had hit the wall. It is very easy to list them during runtime, but I cannot run the code on build time (particularly it may be built as shared library).
Maybe I am overthinking myself and there is better, more clean solution - I don't need to stick with this solution, but Googling around has not yielded anything better...
Nailed it. In GetText times we have to resort to manually parse code.
... but now, with CSharp, we have a Roslyn, with CodeAnalysis API.
Solution
Wire up custom Console build tool that includes Microsoft.CodeAnalysis NuGet and have code like:
var model = compilation.GetSemanticModel(tree);
var methods = root.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach(var method in methods)
{
if(model.GetSymbolInfo(method).Symbol is IMethodSymbol symbol &&
symbol.ContainingNamespace.Name == "MyProject" &&
symbol.ContainingType.Name == "LocalizationKeys" &&
symbol.Name == "DefineKey")
{
var key = method.ArgumentList.Arguments.FirstOrDefault();
var eng = method.ArgumentList.Arguments.Skip(1).FirstOrDefault();
if(key.Expression is LiteralExpressionSyntax literalKey &&
eng.Expression is LiteralExpressionSyntax literalEng)
{
// "/foo" -> "Hello World!"
// "/bar" -> "Hello World2!"
Console.WriteLine(literalKey + " -> " + literalEng);
}
else
{
// Bonus: detect violation of key definition rule. It is not a literal!
}
}
}
Compile this Console tool as executable and add it as post-build step. Profit.
Quick question that's wrecked my morning and is driving me nuts.
I have a small project that includes a DLL from another project. The DLL has an XSL file embedded in it that I want to extract and apply to a webbrowser control.
I have no problem with extracting / accessing embedded resources in the main EXE file, but I cannot find the means of accessing it in the DLL!?
I've tried:
"SolutionName.DllName.Resource.xsl"
"ExeName.DllName.Resource.xsl"
"ProjectNamespace.DllNamespace.Resource.xsl"
...and pretty much every permutation thereof, but it's never able to find it.
I don't have a dot-notation reference for it in C# to use with nameof(), and I can't find any apparent reference / access to it with:
System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames();
So, what's the correct naming (or other?) method for retrieving this file?
In case any of this helps, here's some additional details:
Project Name: DataBuilder
Project Namespace: DataBuilder
DLL Name: CobCommon
DLL Namespaces: CobCommon, CobCommon.Classes, CobCommon.Classes.Data, CobCommon.Winforms, CobCommon.WinForms.Controls
XSL Resource Name: XmlFormating.xsl
The specified resource file operation is "Embedded Resource" and it's located in the "root" area of the DLL project.
Accessing global:: gives me CobCommon, and DataBuilder amongst the available choices, but CobCommon doesn't have either a .Properties or a .Resources option, and DataBuilder which does have .Properties.Resources gives "Culture" as the only reference.
The XSL file is listed on the DLL Project's "Properties|Resources|Files" tab.
What am I missing?
Using GetExecutingAssembly() will probably always refer to your assembly. Instead, create an instance of some innocuous, (hopefully) simple object declared in that external DLL, then use that instance object's...
<object-from-DLL>.GetType().Assembly.GetManifestResourceStream("what.youre.looking.for")
to get a stream handle to your embedded object.
So this was the way that I finally managed to put together a generic function to retrieve any text-encoded embedded resource from any project assembly (and that's working as intended in my project):
First, I extended the Assembly class to facilitate grabbing just the relevant leading portion of Assembly.FullName that we need to use to search for the requested resource:
/// <summary>Adds a function to dereference just the base name portion of an Assembly from the FullName.</summary>
/// <returns>The extracted base name if able, otherwise just the FullName.</returns>
public static string ExtractName(this Assembly root)
{
string pattern = #"^(?<assy>(?:[a-zA-Z\d]+[.]?)+)(?>,).*$", work = root.FullName;
MatchCollection matches = Regex.Matches(work, pattern, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
if (matches.Count > 0) return matches[0].Groups["assy"].Value;
if (work.IndexOf(",") > 3) return work.Substring(0, work.IndexOf(','));
return root.FullName;
}
Then I wrote this function to search for the specified assembly + embedded resource file and return its contents as a string, if found:
/// <summary>Searches all loaded assemblies in a project for the specified embedded resource text file.</summary>
/// <returns>If the specified resource is found, its contents as a string, otherwise throws a DllNotFoundException.</returns>
/// <exception cref="DllNotFoundException"></exception>
public static string FetchAssemblyResourceFile(string name)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
int i = -1; while ((++i < assemblies.Length) && !Regex.IsMatch(name, "^(" + assemblies[i].ExtractName() + ")", RegexOptions.IgnoreCase)) ;
if (i < assemblies.Length)
{
try {
using (System.IO.Stream stream = assemblies[i].GetManifestResourceStream(name))
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream))
return reader.ReadToEnd();
}
catch (Exception innerExc)
{
Exception result = new Exception("The requested resource (\"" + name + "\") was not found.");
result.InnerException = innerExc;
throw result;
}
}
throw new DllNotFoundException("The requested assembly resource (\"" + name + "\") could not be found.");
}
I really don't know/have the answer, knowledge to find a resource value using a key from a resx file in a assembly using c#.(or may be i am ignorant).
What would be the fastest code, way i can retrieve string values or values using a key from a resource file which is embedded as resource in a assembly. I am storing friendly messages for exceptions in the resource file and would like to use them when required.
Does a static class exist for this purpose?
Are there open source mature projects i can use for this?
If the resource is in the same assembly as the code, then the following will do:
String resourceValue = MyAssemblyNameSpace.Properties.Resources.ResourceName
Taken from this SO answer.
Assembly assembly = this.GetType().Assembly;
ResourceManager resourceManager = new ResourceManager("Resources.Strings", assembly);
string myString = resourceManager.GetString("value");
string val = Resources.ResourceManager.GetString("resource_name");
Given "resource_name" you can retrieve resource value.
you can use ResourceManger to get the string value from Assembly
Get Resource from Assembly
ResourceManager ResManager= new ResourceManager("yourResource",
Assembly.GetExecutingAssembly());
String strResourveValue = ResManager.GetString("YourStringKey");
var thread = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
var culture = new CultureInfo(thread);
var resourceManager = new ResourceManager(typeof(Resources.Resource));
string value = resourceManager.GetString(name, culture);
When I made a new project for my unit tests of type C# class library called UnitTests, I right clicked and Added a new Resource. I named that UnitTestsResources. I added 2 strings to that resource. I was then able to conveniently able to access them like this
UnitTestsResources.NoDeviceRequireMsg
I was curious how that worked so i pulled up the code behind the resource file and it makes sense. Visual Studio made a internal class with static accessors.. It looks like this for me
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class UnitTestsResources {
//Some auto generated code
/// <summary>
/// Looks up a localized string similar to OPOS Device is required for test.
/// </summary>
internal static string DeviceRequireMsg {
get {
return ResourceManager.GetString("DeviceRequireMsg", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OPOS Device must not be installed for test.
/// </summary>
internal static string NoDeviceRequireMsg {
get {
return ResourceManager.GetString("NoDeviceRequireMsg", resourceCulture);
}
}
}
Since it is only for my unit tests I am content with this. Hope it helps someone else.
To improve on Herzbube's answer I will show how I implemented this...
Rather than creating projects or folders for the resource file, just right click your project and do add -> new item, then choose resources file. Open the resources file stick in your strings, save as a useful name, and navigate over to C# where you want to use them, then it is just:
String resourceValue = MyProjectName.MyResourceFileName.MyResourceRowItem;
If that isnt working pay attention to the access modifier drop down when inside your resx file.
I've encountered the need to implement a plugin pattern that doesn't fit with anything I've seen elsewhere and I'm just wondering whether I'm looking at it the wrong way or whether anyone else has encountered the same problem and might have a soluton.
Essentially, we have a system that comprises of a core assembly, and a number of modules that plug into it. Some of the modules rely on other modules, but the need has arisen to potentially remove or replace some of those dependencies from time to time and I'd like to avoid recompiles as far as possible.
The system is a bespoke CMS and the modules are plugins providing features within the CMS. For example, we have a comments module and several content modules such as a news module, a blogs module etc. that can include commenting functionality. My problem is that some customers may not purchase the comments module, so I either need to find a way to prevent the dependent modules from depending on the existence of a comments module and, in some cases, may need to cater for a modified version of the comment module.
We're loading the modules at runtime and, at present, to avoid interdependencies between the modules, we're handling this using interfaces that are held in the core CMS assembly. My concern is that to avoid having to modify the core CMS assembly every time we create new modules where a dependency could exist, I need to use something a lot looser than interfaces and implementations of those interfaces.
I'm considering the following:
Core assembly contains an object that allows the registration and unregistration of shared input/output messages (for example "Comments.AddComment" or "Comments.ListComments")
When modules are loaded, they advertise the services they require and the services
they provide (for example, a news module would require the "Comments.AddComment" message and any variant of the comments module would provide the "Comments.AddComment" message).
Any objects or data that are passed to these messages will inherit from a very loose base class or implement an interface that exposes a property of type IDictionary that is contained within the core assembly. Alternatively, the contract for a message will require only a parameter of type object and I pass anonymous objects into them from the provider/consumer.
The downside is obviously losing strong typing, but the plus is that I don't rely on a strict interface implementation or require the inclusion of modules that may not exist at runtime.
Plugins are loaded via Reflection, checking referenced assemblies and looking for classes implementing a given interface. MEF and dynamic types aren't an option as I'm restricted to .NET 3.5.
Can anyone suggest anything better, or perhaps a different way of thinking about this problem?
You're right that if you use a base class or interface in your core app, then you need to rebuild the app and all the plugins that use that class/interface if it changes. So what can you do about it? Here are some ideas (not necessarily good ones, but they may spark some thoughts) that you can mix & match...
Put the interfaces in separate shared assemblies, so you at least don't need to recompile the core app if an interface changes.
Don't change any of your interfaces - keep them fixed in stone. Instead "version" them, so if you want to change the interface, you leave the old interface in place and just expose a completely new interface that extends or replaces the old API. This allows you to gradually deprecate old plugins rather than forcing an immediate global rebuild being required. This does tie your hands somewhat as it requires full backwards compatibility support for all the old interfaces at least until you know all your clients have moved on to newer builds of all their assemblies. But you can combine this with a less frequent "reinstall everything" release where you break backwards compatibility, clear out the defunct interfaces and upgrade all the client assemblies.
Look for interfaces where some parts of the interface are not needed by all plugins, and break up some interfaces into several simpler interfaces, to reduce dependencies/churn on each interface.
As you've suggested, convert interfaces into a runtime registration/discovery approach to minimise the churn on the interfaces. The more flexible and generic your interfaces are, the easier it will be to extend them without introducing breaking changes. For example, serialize data/commands to a string format, dictionary or XML and pass it in that form, rather than calling explicit interfaces. A data-driven approach like XML or a dictionary of name+value pairs is much easier to extend than an interface, so you can start supporting new elements/attributes while easily retaining backwards compatibility for clients that pass an older format to you. Instead of PostMessage(msg) + PostComment(msg) you could genericise the interface to a single method taking a type parameter: PostData("Message", msg) and PostData("Comment", msg) - that way it's easy to support new types without needing to define new interfaces.
If possible, try to define interfaces that anticipate expected future features. So if you think you might one day add an RSS capability, then think about how that might work, chuck in an interface, but don't provide any support for it. Then if you finally get around to adding an RSS plugin, it already has a defined API to plug into. Of course, this only works if you define flexible enough interfaces that they are actually usable by the system when it is implemented!
Or in some cases maybe you can ship the dependency plugins to all your customers, and use a licensing system to enable or disable their capabilities. Then your plugins can have dependencies on each other, but your customers can't utilise the facilities unless they've bought them.
Ok, did some digging and found what I was looking for.
NOTE : this is old code, it's not using any patterns or anything like that. Heck it's not even in it's own object, but it works :-) you'll need to adapt the idea's to work the way you want.
First things first, is a loop that gets all the DLL files found in a specific directory, in my case this was in a folder called 'plugins' under the apps install folder.
private void findPlugins(String path)
{
// Loop over a list of DLL's in the plugin dll path defined previously.
foreach (String fileName in Directory.GetFiles(path, "*.dll"))
{
if (!loadPlugin(fileName))
{
writeToLogFile("Failed to Add driver plugin (" + fileName + ")");
}
else
{
writeToLogFile("Added driver plugin (" + fileName + ")");
}
}// End DLL file loop
}// End find plugins
As you will see there is a call to 'loadPlugin' this is the actual routine that does the work of recognizing and loading an individual dll as a plugin for the system.
private Boolean loadPlugin(String pluginFile)
{
// Default to a successfull result, this will be changed if needed
Boolean result = true;
Boolean interfaceFound = false;
// Default plugin type is unknown
pluginType plType = pluginType.unknown;
// Check the file still exists
if (!File.Exists(pluginFile))
{
result = false;
return result;
}
// Standard try/catch block
try
{
// Attempt to load the assembly using .NET reflection
Assembly asm = Assembly.LoadFile(pluginFile);
// loop over a list of types found in the assembly
foreach (Type asmType in asm.GetTypes())
{
// If it's a standard abstract, IE Just the interface but no code, ignore it
// and continue onto the next iteration of the loop
if (asmType.IsAbstract) continue;
// Check if the found interface is of the same type as our plugin interface specification
if (asmType.GetInterface("IPluginInterface") != null)
{
// Set our result to true
result = true;
// If we've found our plugin interface, cast the type to our plugin interface and
// attempt to activate an instance of it.
IPluginInterface plugin = (IPluginInterface)Activator.CreateInstance(asmType);
// If we managed to create an instance, then attempt to get the plugin type
if (plugin != null)
{
// Get a list of custom attributes from the assembly
object[] attributes = asmType.GetCustomAttributes(typeof(pluginTypeAttribute), true);
// If custom attributes are found....
if (attributes.Length > 0)
{
// Loop over them until we cast one to our plug in type
foreach (pluginTypeAttribute pta in attributes)
plType = pta.type;
}// End if attributes present
// Finally add our new plugin to the list of plugins avvailable for use
pluginList.Add(new pluginListItem() { thePlugin = plugin, theType = plType });
plugin.startup(this);
result = true;
interfaceFound = true;
}// End if plugin != null
else
{
// If plugin could not be activated, set result to false.
result = false;
}
}// End if interface type not plugin
else
{
// If type is not our plugin interface, set the result to false.
result = false;
}
}// End for each type in assembly
}
catch (Exception ex)
{
// Take no action if loading the plugin causes a fault, we simply
// just don't load it.
writeToLogFile("Exception occured while loading plugin DLL " + ex.Message);
result = false;
}
if (interfaceFound)
result = true;
return result;
}// End loadDriverPlugin
As you'll see above, there is a struct that holds the info for a plugin entry, this is defined as:
public struct pluginListItem
{
/// <summary>
/// Interface pointer to the loaded plugin, use this to gain access to the plugins
/// methods and properties.
/// </summary>
public IPluginInterface thePlugin;
/// <summary>
/// pluginType value from the valid enumerated values of plugin types defined in
/// the plugin interface specification, use this to determine the type of hardware
/// this plugin driver represents.
/// </summary>
public pluginType theType;
}
and the variables that tie the loader to said struct:
// String holding path to examine to load hardware plugins from
String hardwarePluginsPath = "";
// Generic list holding details of any hardware driver plugins found by the service.
List<pluginListItem> pluginList = new List<pluginListItem>();
The actual plugin DLL's are defined using an Interface 'IPlugininterface' and also an Enumeration to define the plugin types:
public enum pluginType
{
/// <summary>
/// Plugin is an unknown type (Default), plugins set to this will NOT be loaded
/// </summary>
unknown = -1,
/// <summary>
/// Plugin is a printer driver
/// </summary>
printer,
/// <summary>
/// Plugin is a scanner driver
/// </summary>
scanner,
/// <summary>
/// Plugin is a digital camera driver
/// </summary>
digitalCamera,
}
and
[AttributeUsage(AttributeTargets.Class)]
public sealed class pluginTypeAttribute : Attribute
{
private pluginType _type;
/// <summary>
/// Initializes a new instance of the attribute.
/// </summary>
/// <param name="T">Value from the plugin types enumeration.</param>
public pluginTypeAttribute(pluginType T) { _type = T; }
/// <summary>
/// Publicly accessible read only property field to get the value of the type.
/// </summary>
/// <value>The plugin type assigned to the attribute.</value>
public pluginType type { get { return _type; } }
}
for the custom attribute that we search for in a plugin to know it's ours
public interface IPluginInterface
{
/// <summary>
/// Defines the name for the plugin to use.
/// </summary>
/// <value>The name.</value>
String name { get; }
/// <summary>
/// Defines the version string for the plugin to use.
/// </summary>
/// <value>The version.</value>
String version { get; }
/// <summary>
/// Defines the name of the author of the plugin.
/// </summary>
/// <value>The author.</value>
String author { get; }
/// <summary>
/// Defines the name of the root of xml packets destined
/// the plugin to recognise as it's own.
/// </summary>
/// <value>The name of the XML root.</value>
String xmlRootName { get; }
/// <summary>
/// Defines the method that is used by the host service shell to pass request data
/// in XML to the plugin for processing.
/// </summary>
/// <param name="XMLData">String containing XML data containing the request.</param>
/// <returns>String holding XML data containing the reply to the request.</returns>
String processRequest(String XMLData);
/// <summary>
/// Defines the method used at shell startup to provide any one time initialisation
/// the client will call this once, and once only passing to it a host interface pointing to itself
/// that the plug shall use when calling methods in the IPluginHost interface.
/// </summary>
/// <param name="theHost">The IPluginHost interface relating to the parent shell program.</param>
/// <returns><c>true</c> if startup was successfull, otherwise <c>false</c></returns>
Boolean startup(IPluginHost theHost);
/// <summary>
/// Called by the shell service at shutdown to allow to close any resources used.
/// </summary>
/// <returns><c>true</c> if shutdown was successfull, otherwise <c>false</c></returns>
Boolean shutdown();
}
For the actual plugin interface. This needs to be referenced both by the client app, and any plugin that uses it.
You'll see one other interface mentioned, this is the Host interface for the plugin to call back to, if you don't need to use it for 2 way comms then you can strip it out, but in case it's needed:
public interface IPluginHost
{
/// <summary>
/// Defines a method to be called by plugins of the client in order that they can
/// inform the service of any events it may need to be aware of.
/// </summary>
/// <param name="xmlData">String containing XML data the shell should act on.</param>
void eventCallback(String xmlData);
}
Finally, to make a DLL that acts as a plugin, using a separate DLL project, and referencing the interfaces where needed, you can use the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using pluginInterfaces;
using System.IO;
using System.Xml.Linq;
namespace pluginSkeleton
{
/// <summary>
/// Main plugin class, the actual class name can be anything you like, but it MUST
/// inherit IPluginInterface in order that the shell accepts it as a hardware driver
/// module. The [PluginType] line is the custom attribute as defined in pluginInterfaces
/// used to define this plugins purpose to the shell app.
/// </summary>
[pluginType(pluginType.printer)]
public class thePlugin : IPluginInterface
{
private String _name = "Printer Plugin"; // Plugins name
private String _version = "V1.0"; // Plugins version
private String _author = "Shawty"; // Plugins author
private String _xmlRootName = "printer"; // Plugins XML root node
public string name { get { return _name; } }
public string version { get { return _version; } }
public string author { get { return _author; } }
public string xmlRootName { get { return _xmlRootName; } }
public string processRequest(string XMLData)
{
XDocument request = XDocument.Parse(XMLData);
// Use Linq here to pick apart the XML data and isolate anything in our root name space
// this will isolate any XML in the tags <printer>...</printer>
var myData = from data in request.Elements(this._xmlRootName)
select data;
// Dummy return, just return the data passed to us, format of this message must be passed
// back acording to Shell XML communication specification.
return request.ToString();
}
public bool startup(IPluginHost theHost)
{
bool result = true;
try
{
// Implement any startup code here
}
catch (Exception ex)
{
result = false;
}
return result;
}
public bool shutdown()
{
bool result = true;
try
{
// Implement any shutdown code here
}
catch (Exception ex)
{
result = false;
}
return result;
}
}// End class
}// End namespace
With a bit of work, you should be able to adapt all of this to do what you need, originally the project this was written was speced for dot net 3.5 and we did have it working in a windows service.
If you want to be more generic as possible, IMHO you should abstract UI layer too over pugins.
So the user actual interaction with UI exposed by Plugin (if there is any UI in it), like for Comments have to be a part of Plugin definition. The Host container has to provide a space where any plugin can push whatever he wants. The space requirement can be also a part of plugin descriptive manifest too. In this case Host, basically:
finds a plugin
loads it in memory
reads how much and what kind of space it needs
checks if specified space can be provided in this presice moment, if yes, allows plugin to fill its interface with plugins UI data.
And after or event pumping/user interaction is made by plugin itself.
This idea you can find, more or less on banners concepts in Web Development, or in mobile development, for example defining your app UI layout on Android.
Hope this helps.
I am trying to properly integrate our app with the Windows 7 Jump Lists. We allow opening files within the application and I added this a while ago to add the items to the jump list:
var list = JumpList.CreateJumpList()
list.AddToRecent(file);
list.Refresh();
where JumpList is from the WindowsAPICodePack
There were two issues with this approach.
Occasionally users would get a ComException on the Refresh() call (Unable to remove the file to be replaced. (Exception from HRESULT: 0x80070497)).
The JumpList would only contain files with the applications file extension.
We allow importing other files in our application via the Open method and I want these files to also show up in the Jump List but they don't.
I searched through the questions regarding JumpLists here on SO and found a different way to add recently used files in this answer:
void AddFileToRecentFilesList(string fileName)
{
SHAddToRecentDocs((uint)ShellAddRecentDocs.SHARD_PATHW, fileName);
}
/// <summary>
/// Native call to add the file to windows' recent file list
/// </summary>
/// <param name="uFlags">Always use (uint)ShellAddRecentDocs.SHARD_PATHW</param>
/// <param name="pv">path to file</param>
[DllImport("shell32.dll")]
public static extern void SHAddToRecentDocs(UInt32 uFlags,
[MarshalAs(UnmanagedType.LPWStr)] String pv);
enum ShellAddRecentDocs
{
SHARD_PIDL = 0x00000001,
SHARD_PATHA = 0x00000002,
SHARD_PATHW = 0x00000003
}
This seemed more appropriate as it is also backwards compatible with XP, Vista - Problem is that the JumpList still only contains files with my associated file extension.
I have two questions:
What is the better way to add items to the Jump List.
How do I get any file to show up on my Jump List, regardless of file extension?
From MSDN:
An application must be a registered
handler for a file type for an item of
that type to appear in its Jump List.
It does not, however, need to be the
default handler for that file type
So you must add register yourself with every filetype you care about, either by adding a verb to the ProgId or possibly just adding your ProgId or exe name to OpenWithProgIds or OpenWithList (HKCR\%.ext%\OpenWithProgIds)
The fact that windows requires this is a bit stupid IMHO, but I guess they need to know how to pass the file path to your app when you click on a jump list item.
SHAddToRecentDocs has more parameter types than you have listed, the docs for SHARDAPPIDINFOLINK does not say if you need to be registered anywhere for it to work so you could try that instead of adding a basic path...