I'm working in c# and I'm passing in chart properties as a json string, for example:
{'chart':{
'series':{
'count':3,
'style':'3 Series Scaled Bars',
'data':[
{'x':'Brand','y':'Avg Comments','type':'barScaled','position':1}
{'x':'Brand','y':'Avg Likes','type':'barScaled','position':2}
{'x':'Brand','y':'Avg Shares','type':'barScaled','position':3}
]
}}}
What I'd like to be able to do is pass in something like this: 'markerSize': 8 and be able to set the property with the string name of the property, something likes this:
Excel.SeriesCollection lines = (Excel.SeriesCollection)chrt.SeriesCollection();
Excel.Series ser = sers.Item(1);
ser.Properties("markerSize") = 8;
Is that possible, or do I have to write code to handle each property that I need to be able to modify?
System.Reflection class may provide what you seek for.
In case some of properties will be in fact fields following code handles both situations.
For COM objects it supports only properties, I am too tired to think of a way to support both fields and properties for COM objects without ugly try-catch blocks.
Why previous code failed for Interop objects? Because they are evil COM objects.
While Reflection can normally return fields and properties for interface without any problem, for COM objects it failed because their true type during runtime is System._ComObject, which of course lacked properties you were looking for.
Workaround is to use Invoke method, which deals with horrors of COM on its own.
System._ComObject is hidden type, hence its tested as string instead of Type. (I am tired)
using System.Reflection;
...
/// <summary>
/// Dynamicaly sets property of given object with given value. No type casting is required. Supports COM objects properties.
/// </summary>
/// <param name="target">targeted object</param>
/// <param name="propertyName">property name</param>
/// <param name="value">desired value</param>
/// <returns>True if success, False if failure (non existing member)</returns>
static bool SetProperty(object target, string propertyName, object value)
{
Type t = target.GetType();
if(t.ToString()=="System.__ComObject")
{
t.InvokeMember(propertyName, BindingFlags.SetProperty, null, target, new object[] { value });
return true;
}
PropertyInfo propertyInfo = t.GetProperty(propertyName);
FieldInfo fieldInfo = t.GetField(propertyName);
if (propertyInfo != null)
{
propertyInfo.SetValue(target, Convert.ChangeType(value, propertyInfo.PropertyType, null));
return true;
}
if (fieldInfo!=null)
{
fieldInfo.SetValue(target, Convert.ChangeType(value, fieldInfo.FieldType, null));
return true;
}
return false;
}
//usage:
foo bar = new foo();
SetProperty(bar,"myPropertyOrField","myValue");
Related
I have an application that produces SVG diagrams, we would like to create an export to Visio option that would, ideally, behind the scenes (via server-side C#) take an SVG file and convert it directly to an MS Visio .vsdx file that the user can download and then ofcourse open and edit in Visio seamlessly (as opposed to having to download the SVG file, and then importing it themselves into Visio). In effect, trying to replicate the "Open SVG" functionality that the Visio GUI supplies in backend C#.
I have seen a lot of answers on going from Visio TO SVG, but this is the opposite.
I also know that .vsdx is simply a zip file, but with that extension. Inside are a bunch of visio specific files and folders - if anyone knows what is the bare minimum required of these files/folders and their contents and where the SVG fits in, perhaps that's one way to do it.
Any guidance would be much appreciated.
Dug this up, credit/source: https://programmer.group/5c650f3227420.html
SVG can be converted to Visio's vsd format. The method is very simple. The main method is to open the SVG file and save it as a vsd file. The invocation method is as follows:
/// <summary>
/// svg turn vsd
/// </summary>
/// <param name="svgFn">svn file name</param>
/// <param name="desVsdFn">Preserved vsd file name</param>
private static void Svg2Vsd(string svgFn, string desVsdFn)
{
var app = ComObj.Create("Visio.Application");
app["Visible"] = new ComObj(false);
var docs = app["Documents"];
short visOpenHidden = 64, visOpenRO = 2;
var doc = docs.Call("OpenEx", svgFn, visOpenHidden + visOpenRO);
doc.Call("SaveAs", desVsdFn);
doc.Call("Close");
var win = app["Window"];
app.Call("Quit");
}
Here I use a ComObj class I wrote myself. Its purpose is to make it easy to invoke Com components such as Office by reflection, and to make the code concise when invoking.
Why use reflection to invoke dynamically instead of directly referencing Com components? The main purpose is to reduce the dependence and coupling of program code to COM components, so as to facilitate the compilation and distribution of code deployment. Dynamic invocation can be compiled and run without adding component references. If the Com component is not installed on the server, you can also give an intuitive prompt instead of a program error.
The code for this class is as follows:
using System;
using System.Reflection;
namespace HZ.Common
{
/// <summary>
/// For convenience Com Object attributes, method calls
/// </summary>
public class ComObj
{
public static ComObj Create(string progId)
{
var type = Type.GetTypeFromProgID(progId);
if (type == null)
{
throw new Exception("Servers need to be installed" + progId + "To use this feature");
}
return new ComObj(Activator.CreateInstance(type));
}
private object _val;
/// <summary>
/// Actual value
/// </summary>
public object Val
{
get { return _val; }
}
public ComObj(object comObject)
{
_val = comObject;
}
public ComObj Call(string mehtod, params object[] args)
{
if (_val == null)
return null;
var ret = _val.GetType().InvokeMember(mehtod, BindingFlags.InvokeMethod, null, _val, args);
return new ComObj(ret);
}
public ComObj this[string property]
{
get
{
if (_val == null)
return null;
var ret = _val.GetType().InvokeMember(property, BindingFlags.GetProperty, null, _val, null);
return new ComObj(ret);
}
set
{
if (_val != null)
_val.GetType().InvokeMember(property, BindingFlags.SetProperty, null, _val, new object[] { value.Val });
}
}
}
}
I am trying to write a MongoDb serializer in c# that will allow me to decorate properties via a [Encrypt()] attribute and then at runtime it would allow me to generate an additional property called PropertyName_Encrypted which would contain the encrypted value.
On deserialization, the encrypted property value would be set in the parent property so that the default GET for the property always returns the encrypted value. Users will then call an optional Decrypt() method on the object to get decrypted values.
In doing so, I'm running into some interesting challenges:
How do I add Additional properties to the document when I am serializing current Element? How do I get the current element's name?
Is there a way I can read a specific property from the document/object? For e.g. say I want to pass a symmetric encryption key and read that to encrypt the data while serializing the current element? Is there any way I can do that?
Here are things I have done so far:
I've built an Encrypt Attribute as follows:
[AttributeUsage(AttributeTargets.Property)]
public class EncryptAttribute : Attribute
{
private readonly EncryptedFieldType _fieldType;
private readonly bool _tokenizeDisplay;
private readonly string _encryptedFieldName;
/// <summary>
///
/// </summary>
/// <param name="fieldType">The field type to encrypt. Useful if display needs to show some formatting. If no formatting is necessary, simply set to "Other".</param>
/// <param name="tokenizeDisplay">If set to true, will persist the tokenized value in the original field for display purposes.</param>
/// <param name="encryptedFieldName">Optional. If set, will save the encrypted value in the field name specified. By default all encrypted field values are stored in the corresponding _Encrypted field name. So EmailAddress field if encrypted, would have value under EmailAddress_Encrypted.</param>
public EncryptAttribute(EncryptedFieldType fieldType, bool tokenizeDisplay, string encryptedFieldName = "")
{
_fieldType = fieldType;
_tokenizeDisplay = tokenizeDisplay;
_encryptedFieldName = encryptedFieldName;
}
}
I read this Attribute on Startup and add an Encryption Serializer to the properties that are decorated using this attribute. The code that does that is like so:
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("MongoCustomSerializer"))
.ToList();
var mapper = new Mapper();
foreach (var assembly in assemblies)
{
mapper.Map(assembly);
}
The mapper simply checks which properties in the document have the Encrypt attribute to add the serializer:
public sealed class Mapper
{
public void Map(Assembly assembly)
{
var encryptableTypes = assembly.GetTypes().Where(p =>
typeof(IEncryptable).IsAssignableFrom(p) && p.IsClass && !p.IsInterface && !p.IsValueType &&
!p.IsAbstract).ToList();
if (encryptableTypes.Any())
{
foreach (var encryptableType in encryptableTypes)
{
Map(encryptableType);
}
}
}
private void Map(Type documentType)
{
var properties =
documentType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
if (properties.Length <= 0)
{
return;
}
foreach (var property in properties)
{
RegisterEncrpytionSerializer(property, typeof(EncryptAttribute), documentType);
}
}
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var memberMap = BsonClassMap.LookupClassMap(documentType).GetMemberMap(property.Name);
memberMap?.SetSerializer(new EncryptionSerializer());
}
}
In my unit tests, I'm getting an error stating that the Bson Class Map is already frozen. Even if I were to figure out a way to bypass that, how would this EncryptionSerializer class work to where I could write an additional property?
Would love to see if someone can assist!
UPDATE 1 - I was able to get the FREEZE error taken care of. It would appear that the LookupClassMap freezes the Member and Class Map info.
This change from the link allows me to take care of that issue:
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var classMapDefinition = typeof(BsonClassMap<>);
var classMapType = classMapDefinition.MakeGenericType(documentType);
var classMap = (BsonClassMap)Activator.CreateInstance(classMapType);
classMap.AutoMap();
var memberMap = classMap.GetMemberMap(property.Name);
memberMap?.SetSerializer(new KeyVaultEncryptionSerializer(memberMap.ElementName));
}
Are you using a service for saving/retrieving your items that actually call the DB?
I believe you should move the responsibility for writing/reading encrypted values to the calling service (i.e a repository implementation) instead of the BsonSerializer.
It would make sense to me that encryption/decryption is part of the persistence layer and something not handled in the application when needed.
Your implementation targets only the specified property you want to serialize. It doesn't make sense that it creates another property.
A second thought is that your suggested approach with properties that change value based on Decrypt() probably isn't a good idea since it makes your code unpredictable and hard to read. Make your properties dead simple.
What extra security in your code does it really give you if you can decrypt properties by just calling a method anyway?
If you still need to have a Decrypt() would suggest that you create methods for decrypting that return the decrypted value like GetUnencryptedCode() etc, it could just as well be an extension method but still not a readable property.
You should also be looking into using SecureString depending on your use case.
I'm looking for a way to programmatically get the summary portion of Xml-comments of a method in ASP.net.
I have looked at the previous related posts and they do not supply a way of doing so in a web environment.
I can not use any 3rd party apps and due to a web environment, Visual studio plugin's aren't much use either.
The closest thing I have found to a working solution was the JimBlackler project, but it only works on DLL's.
Naturally, something like 'supply .CS file, get XML documentation' would be optimal.
Current situation
I have a web-service and trying to dynamically generate documentation for it.
Reading the Methods, and properties is easy, but getting the Summary for each method is throwing me off a bit.
/// <summary>
/// This Is what I'm trying to read
/// </summary>
public class SomeClass()
{
/// <summary>
/// This Is what I'm trying to read
/// </summary>
public void SomeMethod()
{
}
}
A Workaround - Using reflection on Program.DLL/EXE together with Program.XML file
If you take a look at the sibling .XML file generated by Visual Studio you will see that there is a fairly flat hierarchy of /members/member.
All you have to do is get hold on each method from your DLL via MethodInfo object. Once you have this object you turn to the XML and use XPATH to get the member containing the XML documentation for this method.
Members are preceded by a letter. XML doc for methods are preceded by "M:" for class by "T:" etc.
Load your sibling XML
string docuPath = dllPath.Substring(0, dllPath.LastIndexOf(".")) + ".XML";
if (File.Exists(docuPath))
{
_docuDoc = new XmlDocument();
_docuDoc.Load(docuPath);
}
Use this xpath to get the member representing the method XML docu
string path = "M:" + mi.DeclaringType.FullName + "." + mi.Name;
XmlNode xmlDocuOfMethod = _docuDoc.SelectSingleNode(
"//member[starts-with(#name, '" + path + "')]");
Now scan childnodes for all the rows of "///"
Sometimes the /// Summary contains extra blanks, if this bothers use this to remove
var cleanStr = Regex.Replace(row.InnerXml, #"\s+", " ");
The XML summary isn't stored in the .NET assembly - it's optionally written out to an XML file as part of your build (assuming you're using Visual Studio).
Consequently there is no way to "pull out" the XML summaries of each method via reflection on a compiled .NET assembly (either .EXE or .DLL) - because the data simply isn't there for you to pull out. If you want the data, you'll have to instruct your build environment to output the XML files as part of your build process and parse those XML files at runtime to get at the summary information.
You could 'document' your method using the System.ComponentModel.DataAnnotations.DisplayAttribute attribute, e.g.
[Display(Name = "Foo", Description = "Blah")]
void Foo()
{
}
then use reflection to pull the description at runtime.
A deleted post, made by #OleksandrIeremenko, on this thread links to this article https://jimblackler.net/blog/?p=49 which was the basis for my solution.
Below is a modification of Jim Blackler's code making extension methods off the MemberInfo and Type objects and adding code that returns the summary text or an empty string if not available.
Usage
var typeSummary = typeof([Type Name]).GetSummary();
var methodSummary = typeof([Type Name]).GetMethod("[Method Name]").GetSummary();
Extension Class
/// <summary>
/// Utility class to provide documentation for various types where available with the assembly
/// </summary>
public static class DocumentationExtensions
{
/// <summary>
/// Provides the documentation comments for a specific method
/// </summary>
/// <param name="methodInfo">The MethodInfo (reflection data ) of the member to find documentation for</param>
/// <returns>The XML fragment describing the method</returns>
public static XmlElement GetDocumentation(this MethodInfo methodInfo)
{
// Calculate the parameter string as this is in the member name in the XML
var parametersString = "";
foreach (var parameterInfo in methodInfo.GetParameters())
{
if (parametersString.Length > 0)
{
parametersString += ",";
}
parametersString += parameterInfo.ParameterType.FullName;
}
//AL: 15.04.2008 ==> BUG-FIX remove “()” if parametersString is empty
if (parametersString.Length > 0)
return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name + "(" + parametersString + ")");
else
return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name);
}
/// <summary>
/// Provides the documentation comments for a specific member
/// </summary>
/// <param name="memberInfo">The MemberInfo (reflection data) or the member to find documentation for</param>
/// <returns>The XML fragment describing the member</returns>
public static XmlElement GetDocumentation(this MemberInfo memberInfo)
{
// First character [0] of member type is prefix character in the name in the XML
return XmlFromName(memberInfo.DeclaringType, memberInfo.MemberType.ToString()[0], memberInfo.Name);
}
/// <summary>
/// Returns the Xml documenation summary comment for this member
/// </summary>
/// <param name="memberInfo"></param>
/// <returns></returns>
public static string GetSummary(this MemberInfo memberInfo)
{
var element = memberInfo.GetDocumentation();
var summaryElm = element?.SelectSingleNode("summary");
if (summaryElm == null) return "";
return summaryElm.InnerText.Trim();
}
/// <summary>
/// Provides the documentation comments for a specific type
/// </summary>
/// <param name="type">Type to find the documentation for</param>
/// <returns>The XML fragment that describes the type</returns>
public static XmlElement GetDocumentation(this Type type)
{
// Prefix in type names is T
return XmlFromName(type, 'T', "");
}
/// <summary>
/// Gets the summary portion of a type's documenation or returns an empty string if not available
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string GetSummary(this Type type)
{
var element = type.GetDocumentation();
var summaryElm = element?.SelectSingleNode("summary");
if (summaryElm == null) return "";
return summaryElm.InnerText.Trim();
}
/// <summary>
/// Obtains the XML Element that describes a reflection element by searching the
/// members for a member that has a name that describes the element.
/// </summary>
/// <param name="type">The type or parent type, used to fetch the assembly</param>
/// <param name="prefix">The prefix as seen in the name attribute in the documentation XML</param>
/// <param name="name">Where relevant, the full name qualifier for the element</param>
/// <returns>The member that has a name that describes the specified reflection element</returns>
private static XmlElement XmlFromName(this Type type, char prefix, string name)
{
string fullName;
if (string.IsNullOrEmpty(name))
fullName = prefix + ":" + type.FullName;
else
fullName = prefix + ":" + type.FullName + "." + name;
var xmlDocument = XmlFromAssembly(type.Assembly);
var matchedElement = xmlDocument["doc"]["members"].SelectSingleNode("member[#name='" + fullName + "']") as XmlElement;
return matchedElement;
}
/// <summary>
/// A cache used to remember Xml documentation for assemblies
/// </summary>
private static readonly Dictionary<Assembly, XmlDocument> Cache = new Dictionary<Assembly, XmlDocument>();
/// <summary>
/// A cache used to store failure exceptions for assembly lookups
/// </summary>
private static readonly Dictionary<Assembly, Exception> FailCache = new Dictionary<Assembly, Exception>();
/// <summary>
/// Obtains the documentation file for the specified assembly
/// </summary>
/// <param name="assembly">The assembly to find the XML document for</param>
/// <returns>The XML document</returns>
/// <remarks>This version uses a cache to preserve the assemblies, so that
/// the XML file is not loaded and parsed on every single lookup</remarks>
public static XmlDocument XmlFromAssembly(this Assembly assembly)
{
if (FailCache.ContainsKey(assembly))
{
throw FailCache[assembly];
}
try
{
if (!Cache.ContainsKey(assembly))
{
// load the docuemnt into the cache
Cache[assembly] = XmlFromAssemblyNonCached(assembly);
}
return Cache[assembly];
}
catch (Exception exception)
{
FailCache[assembly] = exception;
throw;
}
}
/// <summary>
/// Loads and parses the documentation file for the specified assembly
/// </summary>
/// <param name="assembly">The assembly to find the XML document for</param>
/// <returns>The XML document</returns>
private static XmlDocument XmlFromAssemblyNonCached(Assembly assembly)
{
var assemblyFilename = assembly.Location;
if (!string.IsNullOrEmpty(assemblyFilename))
{
StreamReader streamReader;
try
{
streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename, ".xml"));
}
catch (FileNotFoundException exception)
{
throw new Exception("XML documentation not present (make sure it is turned on in project properties when building)", exception);
}
var xmlDocument = new XmlDocument();
xmlDocument.Load(streamReader);
return xmlDocument;
}
else
{
throw new Exception("Could not ascertain assembly filename", null);
}
}
}
You can use Namotion.Reflection NuGet package to get these information:
string summary = typeof(Foo).GetXmlDocsSummary();
You can look at https://github.com/NSwag/NSwag - source for nuget NSwag.CodeGeneration - it gets summary as well, usage
var generator = new WebApiAssemblyToSwaggerGenerator(settings);<br/>
var swaggerService = generator.GenerateForController("namespace.someController");<br/>
// string with comments <br/>
var swaggerJson = swaggerService.ToJson();
(try ILSPY decompiler against your dll, you check code and comments)
If you have access to the source code you're trying to get comments for, then you can use Roslyn compiler platform to do that. It basically gives you access to all the intermediary compiler metadata and you can do anything you want with it.
It's a bit more complicated than what other people are suggesting, but depending on what your needs are, might be an option.
It looks like this post has a code sample for something similar.
How can both the IPC client and IPC server call the shared remoting interface (the class inheriting MarshalByRefObject) to communicate, without having to put the interface class inside in the injecting application? For example, if I put the interface class in the injected library project that gets injected into my target process, my injecting application cannot reference that interface.
Edit: I have answered the question below.
As of EasyHook Commit 66751 (tied to EasyHook 2.7 alpha), it doesn't seem possible to get the instance of the remoting interface in both the client (that process that initiated injection of your DLL) and the server (the injected process running your injected DLL).
What do I mean?
Well, in the FileMon and ProcessMonitor examples, notice how the shared remoting interfaces (FileMonInterface, embedded in Program.cs, for Filemon, and DemoInterface, in its own file, for ProcessMonitor) are placed in the injecting assembly. FileMonInterface is in the FileMon project. DemoInterface is in the ProcessMonitor project.
Why not the other round? Why not put FileMonInterface in the project FileMonInject, and put DemoInterface in ProcMonInject? Because then the interfaces will no longer be accessible to the calling applications (FileMon and ProcessMonitor).
The reason is because EasyHook internally uses:
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(TRemoteObject),
ChannelName,
InObjectMode);
This remoting call allows clients to call your (server) interface, but the server itself (you, the application) cannot call it.
The Solution
Instead, use:
// Get the instance by simply calling `new RemotingInterface()` beforehand somewhere
RemotingServices.Marshal(instanceOfYourRemotingInterfaceHere, ChannelName);
What I did was add an overload to EasyHook's RemoteHook.IpcCreateServer() to accept my new "way" of doing .NET remoting.
It's ugly, but it works:
The Code
Replace the entire IpcCreateServer method (from brace to brace) with the following code. There are two methods shown here. One is the more detailed overload. The second is the "original" method calling our overload.
public static IpcServerChannel IpcCreateServer<TRemoteObject>(
ref String RefChannelName,
WellKnownObjectMode InObjectMode,
TRemoteObject ipcInterface, String ipcUri, bool useNewMethod,
params WellKnownSidType[] InAllowedClientSIDs) where TRemoteObject : MarshalByRefObject
{
String ChannelName = RefChannelName ?? GenerateName();
///////////////////////////////////////////////////////////////////
// create security descriptor for IpcChannel...
System.Collections.IDictionary Properties = new System.Collections.Hashtable();
Properties["name"] = ChannelName;
Properties["portName"] = ChannelName;
DiscretionaryAcl DACL = new DiscretionaryAcl(false, false, 1);
if (InAllowedClientSIDs.Length == 0)
{
if (RefChannelName != null)
throw new System.Security.HostProtectionException("If no random channel name is being used, you shall specify all allowed SIDs.");
// allow access from all users... Channel is protected by random path name!
DACL.AddAccess(
AccessControlType.Allow,
new SecurityIdentifier(
WellKnownSidType.WorldSid,
null),
-1,
InheritanceFlags.None,
PropagationFlags.None);
}
else
{
for (int i = 0; i < InAllowedClientSIDs.Length; i++)
{
DACL.AddAccess(
AccessControlType.Allow,
new SecurityIdentifier(
InAllowedClientSIDs[i],
null),
-1,
InheritanceFlags.None,
PropagationFlags.None);
}
}
CommonSecurityDescriptor SecDescr = new CommonSecurityDescriptor(false, false,
ControlFlags.GroupDefaulted |
ControlFlags.OwnerDefaulted |
ControlFlags.DiscretionaryAclPresent,
null, null, null,
DACL);
//////////////////////////////////////////////////////////
// create IpcChannel...
BinaryServerFormatterSinkProvider BinaryProv = new BinaryServerFormatterSinkProvider();
BinaryProv.TypeFilterLevel = TypeFilterLevel.Full;
IpcServerChannel Result = new IpcServerChannel(Properties, BinaryProv, SecDescr);
if (!useNewMethod)
{
ChannelServices.RegisterChannel(Result, false);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(TRemoteObject),
ChannelName,
InObjectMode);
}
else
{
ChannelServices.RegisterChannel(Result, false);
ObjRef refGreeter = RemotingServices.Marshal(ipcInterface, ipcUri);
}
RefChannelName = ChannelName;
return Result;
}
/// <summary>
/// Creates a globally reachable, managed IPC-Port.
/// </summary>
/// <remarks>
/// Because it is something tricky to get a port working for any constellation of
/// target processes, I decided to write a proper wrapper method. Just keep the returned
/// <see cref="IpcChannel"/> alive, by adding it to a global list or static variable,
/// as long as you want to have the IPC port open.
/// </remarks>
/// <typeparam name="TRemoteObject">
/// A class derived from <see cref="MarshalByRefObject"/> which provides the
/// method implementations this server should expose.
/// </typeparam>
/// <param name="InObjectMode">
/// <see cref="WellKnownObjectMode.SingleCall"/> if you want to handle each call in an new
/// object instance, <see cref="WellKnownObjectMode.Singleton"/> otherwise. The latter will implicitly
/// allow you to use "static" remote variables.
/// </param>
/// <param name="RefChannelName">
/// Either <c>null</c> to let the method generate a random channel name to be passed to
/// <see cref="IpcConnectClient{TRemoteObject}"/> or a predefined one. If you pass a value unequal to
/// <c>null</c>, you shall also specify all SIDs that are allowed to connect to your channel!
/// </param>
/// <param name="InAllowedClientSIDs">
/// If no SID is specified, all authenticated users will be allowed to access the server
/// channel by default. You must specify an SID if <paramref name="RefChannelName"/> is unequal to <c>null</c>.
/// </param>
/// <returns>
/// An <see cref="IpcChannel"/> that shall be keept alive until the server is not needed anymore.
/// </returns>
/// <exception cref="System.Security.HostProtectionException">
/// If a predefined channel name is being used, you are required to specify a list of well known SIDs
/// which are allowed to access the newly created server.
/// </exception>
/// <exception cref="RemotingException">
/// The given channel name is already in use.
/// </exception>
public static IpcServerChannel IpcCreateServer<TRemoteObject>(
ref String RefChannelName,
WellKnownObjectMode InObjectMode,
params WellKnownSidType[] InAllowedClientSIDs) where TRemoteObject : MarshalByRefObject
{
return IpcCreateServer<TRemoteObject>(ref RefChannelName, InObjectMode, null, null, false, InAllowedClientSIDs);
}
That's it. That's all you need to change. You don't have to change IpcCreateClient().
Using the Code
Here's how you would use the new overloaded method:
Say you have
public class IpcInterface : MarshalByRefObject { /* ... */ }
as your shared remoting interface.
Create a new instance of it, and store its reference. You'll be using this to communicate with your client.
var myIpcInterface = new IpcInterface(); // Keep this reference to communicate!
Here's how you would have created that remoting channel before:
ipcServer = RemoteHooking.IpcCreateServer<IpcInterface>(ref IpcChannelName, WellKnownObjectMode.Singleton, WellKnownSidType.WorldSid);
Here's how you would create that remoting channel now:
ipcServer = RemoteHooking.IpcCreateServer<IpcInterface>(ref IpcChannelName, WellKnownObjectMode.Singleton, myIpcInterface, IpcChannelName, true, WellKnownSidType.WorldSid);
Don't forget to...
I got this solution from this StackOverflow post. Please be sure to do as he says and override InitializeLifetimeService to return null:
public override object InitializeLifetimeService()
{
// Live "forever"
return null;
}
I think this is supposed to keep the client from losing the remoting interface.
Uses
Now, instead of being forced to place your remoting interface file in the same directory as your injecting project, you can create a library specifically for your interface file.
This solution may have been common knowledge to those having had experience with .NET remoting, but I know nothing about it (might have used the word interface wrong in this post).
I am working on a project that needs to be able to call functions in Win32 DLLs. However, the name of the DLL, function, and data types of all arguments and return type are not known at compile time, so using DLLImport is not an option. Basically, this routine could feasibly call any function in any DLL and pass in any arguments. After a lot of searching, I have been able to successfully put together some code that can do this, including passing numerics and strings into the function and even passing numeric arguments by reference. However, I have hit a brick wall trying to pass strings back from the DLL.
To simplify testing, I used Visual Studio 6 to compile a simple Win32 DLL called PassCharPtr.dll containing one function as follows:
PassCharPtr.def file:
EXPORTS
TestPassCharPtr
PassCharPtr.h file:
#include <windows.h>
extern "C" int __stdcall TestPassCharPtr(LPSTR, LONG);
PassCharPtr.cpp file:
#include "PassCharPtr.h"
int WINAPI DllEntryPoint(HINSTANCE hinst,
unsigned long reason,
void*)
{
return 1;
}
/*----------------------------------------------------------------*/
int __stdcall TestPassCharPtr(LPSTR szString, LONG cSize)
{
MessageBox(0, szString, "Inside PassCharPtr.dll", 0);
char buffer[] = "abcdefghijklmnopqrstuvwxyz";
if (cSize > strlen(buffer))
{
strcpy(szString,buffer);
return strlen(buffer);
}
return -cSize;
}
To test my DLL, I created a simple VB6 application:
Private Declare Function TestPassCharPtr Lib "PassCharPtr" (ByVal buffer As String, ByVal lSize As Long) As Long
Private Sub btnTest_Click()
Dim sBuffer As String
Dim lResult As Long
sBuffer = "This is a very long string!!!!!!!!!"
lResult = TestPassCharPtr(sBuffer, Len(sBuffer))
Debug.Print "Result: "; lResult
Debug.Print "Buffer: "; Left(sBuffer, lResult)
End Sub
Everything is working great. Now, here's my C# test project in VS2010 that attempts to access this function:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
namespace TestPassCharPtr
{
class Program
{
/// <summary>
/// Define DLL and function to call. Setup data types for return value and arguments
/// and setup values to pass into function.
///
/// All data types should be declared using C\C++ names to facilitate using
/// existing Win32 API documentation to define function calls.
///
/// When passing a string to a function call, enclose the string in quotes ("")
///
/// </summary>
/// <param name="args">Unused</param>
static void Main(string[] args)
{
string fileName = "PassCharPtr.dll";
string funcName = "TestPassCharPtr";
string returnType = "int";
// comma-delimited list of argument data types
// using this declaration successfully passes string in
// but generates exception when passing string back!
string argTypesList = "char[], int";
// using this declaration fails to pass string in, but does
// not generate an exception when passing string back!
//string argTypesList = "LPSTR, int";
// comma-delimited list of argument values
string argValuesList = "\"This is a very long string!!!!\", 30";
TestDLLFunction(fileName, funcName, returnType, argTypesList, argValuesList);
MessageBox.Show("Done");
}
/// <summary>
/// Calls a DLL function.
///
/// Reference: http://www.pcreview.co.uk/forums/calling-native-c-function-using-definepinvokemethod-and-returning-calculated-value-pointer-reference-t2329473.html
///
/// </summary>
/// <param name="dllFilename">Filename of DLL (excluding path!)</param>
/// <param name="entryPoint">Function name</param>
/// <param name="retType">Return value data type</param>
/// <param name="argTypesList">Comma-delimited list of argument data types</param>
/// <param name="argValuesList">Comma-delimited list of argument values</param>
private static void TestDLLFunction(string dllPath, string entryPoint, string retType, string argTypesList, string argValuesList)
{
Type returnType = null;
Type[] argTypesArray = null;
object[] argValuesArray = null;
object returnValue = null;
// get return data type
returnType = Type.GetType(ConvertToNetType(retType));
// get argument data types for the function to call
string[] argTypes = argTypesList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (argTypes.Length > 0)
{
// create a list of data types for each argument
List<Type> listArgTypes = new List<Type>();
foreach (var argType in argTypes)
{
string netType = ConvertToNetType(argType);
string byRef = IsPointer(argType) ? "&" : "";
listArgTypes.Add(Type.GetType(netType + byRef));
}
// convert the list to an array
argTypesArray = listArgTypes.ToArray<Type>();
// get values to pass as arguments to the function
string[] argValues = argValuesList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
// remove quotes from strings
for (int i = 0; i < argValues.Length; i++)
{
argValues[i] = argValues[i].Replace("\"", "").Trim();
}
argValuesArray = argValues.ToArray<object>();
// verify argument data types count and argument values count match!
if (argValuesArray.Length != argTypesArray.Length)
{
Console.WriteLine(string.Format("The number of parameter types ({0}) does not match the number of parameter values ({1}).", argTypesArray.Length, argValuesArray.Length));
return;
}
// convert all argument values to the proper data types
for (int i = 0; i < argValuesArray.Length; i++)
{
if (argTypesArray[i] == Type.GetType("System.IntPtr&"))
{
argValuesArray[i] = (IntPtr)0;
}
else
{
argValuesArray[i] = ConvertParameter(argValuesArray[i], argTypesArray[i]);
}
}
}
else
{
argTypesArray = null;
argValuesArray = null;
}
// Create a dynamic assembly and a dynamic module
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = dllPath;
AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("tempModule");
// Dynamically construct a global PInvoke signature using the input information
MethodBuilder dynamicMethod = dynamicModule.DefinePInvokeMethod(entryPoint, dllPath,
MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.PinvokeImpl,
CallingConventions.Standard, returnType, argTypesArray, CallingConvention.Winapi, CharSet.Ansi);
// Add PreserveSig to the method implementation flags. NOTE: If this line
// is commented out, the return value will be zero when the method is invoked.
dynamicMethod.SetImplementationFlags(dynamicMethod.GetMethodImplementationFlags() | MethodImplAttributes.PreserveSig);
// This global method is now complete
dynamicModule.CreateGlobalFunctions();
// Get a MethodInfo for the PInvoke method
MethodInfo mi = dynamicModule.GetMethod(entryPoint);
// Invoke the function
try
{
returnValue = mi.Invoke(null, argValuesArray);
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Error: {0}", ex.Message));
if (ex.InnerException != null)
{
Console.WriteLine(string.Format(" Error: {0}", ex.InnerException.Message));
}
}
if (returnValue != null)
{
Console.WriteLine(string.Format("Return value: {0}", returnValue.ToString()));
}
if (argValuesArray != null)
{
for (int i = 0; i < argValuesArray.Length; i++)
{
if (argValuesArray[i] != null)
{
Console.WriteLine(string.Format("Argument {0}: {1}", i, argValuesArray[i].ToString()));
}
}
}
}
/// <summary>
/// Converts a string to another data type.
/// </summary>
/// <param name="value">Value to be converted</param>
/// <param name="dataType">Data type to convert to</param>
/// <returns>Converted value</returns>
private static object ConvertParameter(object value, Type dataType)
{
// determine the base data type (remove "&" from end of "by reference" data types)
string baseDataType = dataType.ToString();
if (baseDataType.EndsWith("&"))
{
baseDataType = baseDataType.Substring(0, baseDataType.Length - 1);
}
return Convert.ChangeType(value, Type.GetType(baseDataType));
}
/// <summary>
/// Determines whether the indicated native data type is a pointer
/// </summary>
/// <param name="dataType">Native (unmanaged) data type</param>
/// <returns>true if data type is a pointer; false otherwise</returns>
private static bool IsPointer(string dataType)
{
string lowerDataType = dataType.ToLower();
if (lowerDataType.StartsWith("lp") || lowerDataType.EndsWith("*"))
{
return true;
}
return false;
}
/// <summary>
/// Convert unmanaged data type names to .NET data type names
///
/// (for simplicity, all types unused by this example were removed)
///
/// </summary>
/// <param name="type">Unmanaged data type name</param>
/// <returns>Corresponding .NET data type name</returns>
private static string ConvertToNetType(string type)
{
string lowerType = type.ToLower();
if (lowerType.Contains("int"))
{
return "System.Int32";
}
else if (lowerType.Contains("lpstr"))
{
return "System.IntPtr";
}
else if (lowerType.Contains("char[]"))
{
return "System.String";
}
return "";
}
}
}
If I declare the first argument as char[] (System.String), I can successfully pass a string into the function, but it generates an exception (accessing protected memory) when the DLL attempts to replace that string with the string to return.
If I declare the first argument as LPSTR (System.IntPtr), I cannot pass a string into the function. However, upon returning from the call, argValuesArray[0] contains what appears to be an address. I have not been able to figure out how to convert that address into the returned string yet. I have tried using String mvValue = Marshal.PtrToStringAnsi((IntPtr)argValuesArray[0]), but this returns an empty string.
There are still a lot of holes in this code, but I hope the general idea is clear enough. Can anyone tell me what data type the first argument should be declared as to be able to pass strings both into and out of this function successfully and how to do any necessary conversions on that data type?
LPSTR is generally marshalled as StringBuilder.