-
Posts
595 -
Joined
-
Last visited
-
Days Won
4
Everything posted by bc-vnt
-
Stii tu man ca el stie macar atat in VB.NET ?
-
+rep man
- 65 replies
-
- facebook like
- facebook like program
-
(and 2 more)
Tagged with:
-
Introduction In some projects we might need a proper math parser to either do simple (a little calculator) or more complex mathematics. CodeProject offers us a variaty of math parsers ranging from ports of C/C++ ones to those purely written in C#. This article tries to create a powerful parser in pure C#, using the properties that come with C# and the .NET Framework. The parser itself is perfectly capable of running on other platforms as well, as it has been written on Linux. The parser itself is Open Source. The full source code of YAMP (that's the name of this parser) is available on Github. A NuGet package will also be available soon. Personal background Years ago I've written a math parser using the C# compiler and some inheritance. The way was to provide a base class that offered all possible methods (like sin(), exp(), ...), which can be used over the inheritance process. Then a string has been built that represented valid C# code with a custom expression in its middle, which has then to be compiled. Of course a pre-parser was necessary to give some data types. On the other side operator overloading and other C# stuff could then be used. The problem with this solution is that the C# compiler is kind of heavy for a single document of code. Also the resulting assembly had to be loaded first. So after the pre-parsing process we had to deal with the C# compiler and the assembly. This means that reflection was necessary as well. The minimum time for a any expression was therefore O(102) ms. This is far too long and does not provide a smooth handling of input and output. There are several projects on the CodeProject, which aim to provide a math parser. All of those projects are faster than this previous / old solution - they are also more robust. However, they are not as complete as my project was - therefore I now try to succeed in this very old project with a new way. This time I have built a parser from scratch, which uses (and abuses) reflection and therefore provides an easy to extend code base, that contains everything from operators to expressions over constants and functions. Even assignments and index operators are possible. What YAMP can do ? Before we dig to deep into YAMP we might have a look on what YAMP offers us. First a brief look at some features: Assignment operators (=, +=, -=, ...) Complex arithmetic using i as imaginary constant. Matrix algebra including products, divisions. Trigonometric functions (sin, cos, sinh, cosh) and their inverse (arcsin, arccos, arcosh, arsinh). More complicated functions like Gamma and others. Faculty, transpose and left divide operator. Using indices to get and set fields. Complex roots and powers. The core element of YAMP is the ScalarValue class, which is an implementation of the abstract Value class. This class provides everything that is required to do numerics with a complex type. Implementations of the basic trigonometric functions with complex arguments have been included as well. The MatrixValue class is basically a (two dimensional) list of ScalarValue instances. We might think that it should be possible to make this list n dimensional, however, that would offer some complications like the need to specify a way to set a value in the n dimension. At the moment we will avoid this issue. Now that we have a brief overview of the main features of YAMP, let's look at some of the expressions that can be parsed by YAMP: -2+3+5/7-3*2 This is a simple expression, but it already expressions that are not trivial. In order to parse this expression successfully, we need to be careful about operator levels (the multiply operator * has a higher level than the one for subtraction -, same goes for the division operator /). We also have the minus sign used as a unitary operator, i.e. we do not have a heading zero here. 2^2^2^2 This is often parsed wrong. Asking MATLAB for this expression we get 256. That seemed kind of low and wrong. Asking YAMP first resulted in this as well - until we include the rule that even operators of the same level will be treated separately - from right to left. This rule does not make a difference in any expression - except with the power operator. Now we get the same result as Google shows us - 65536. x=2,3,4;5,6,7 We could use brackets, but in this case we do not need them. This is a simple matrix assignment to the symbol x. Our own assigned symbols are case sensitive, while included functions and constants are not. Therefore we could assign our own pi and still access the original constant by using a different upper- / lowercase notation like Pi. e^pi Again, some constants are built in like phi, e and pi. Therefore this expression is parsed successfully. Using YAMP as an external library it is easily possible to add our own constants. 2^x-5 This seems odd, but it will make sense. First of all we have a scalar (2) powered by a matrix (the one we assigned before). This is not defined, therefore YAMP will perform the power operation on every value in the matrix. The resulting matrix will have the same dimensions as the exponent - it will be a 2 times 3 matrix. This matrix then performs the subtraction with 5 (a scalar) on the right side. This is again not defined, therefore YAMP performs the operation on every cell again. In the end we have a 2 times 3 matrix. y=x[1,:] We use the range operator : to specify all available indices. The row index is set to 1 (we use rows before columns as MATLAB and Fortran does - and we are now 1 based) - the first row. Therefore the result will be a matrix with dimension 1 times 3 (a transposed three vector). The entries will be 2, 3, 4 x[:]=8:13 Here we reassign every value of the matrix to a new value - given in the vector 8, 9, 10, 11, 12, 13. If we just use one index it will be a combined one. Therefore a 2 times 3 matrix might have the pair 2,3 as last entry (and the pair 1,1 as first entry), but might also have the index 6 as last entry (and the index 1 as first entry). xt=x' The adjungate operator either conjugates scalars (a projection on the real axis, i.e. changing the sign of the imaginary value) or transposes the matrix along with conjugating its entries. Therefore xt now stores a 3 times 2 matrix. If we just want to transpose (without conjugating) we can use the .' operator. z=sin(pi * (a = eye(4))) Here we assign the value of eye(4) to the symbol a. Afterwards the value is multiplied with pi and then evaluated by the sin() function. The outcome is saved in the symbol z. The eye() function generates an n dimensional unit matrix. If we apply trigonometric functions on matrices we end up (as with the power function and others) with a matrix, where each value is the outcome of the former value used as an argument for the specific function. whatever[10,10]=8+3i Here we will get information about all even rows (2:2:end means: start at index 2 and go to the end (a short macro) with a step of 2) with the first half of their columns. The displayed matrix will be a 5 times 5 one with rows 2,4,6,8,10 and columns 1,2,3,4,5. YAMP does take matrices seriously. Multiplying matrices is done with respect to the proper dimensions. Let's have a look at a sample console application: The index operator allows us to select (multiple) columns and / or rows from matrices. The following output shows the selection of the inner 3x3 matrix (from the 5x5 matrix). This concludes our short look at YAMP. Of course YAMP is also able to perform queries like i^i or (2+3i)^(7-i) or 17! correctly. The most important data type for accessing YAMP outside the library is the Parser class. We'll introduce this concept later on. Right now we want to have a short look at the class diagram for the whole library. First of all the class diagram in principle: The Parser generates a ParseTree, that has full access to the singleton class Tokens. The singleton is used to find operators, functions, expressions and constants, as well as resolving symbols. YAMP relies heavily on reflection. The first time YAMP is used might be a little bit slow (compared to further usages). Therefore calling the static Load() method of the Parser should be done before any measurements or user input. This guarantees the shortest possible execution time for the given expression. Any expression will always consist of a number of sub-expressions and operators. Operators can either be binary (like +, -, ...) or unary (like !, [], ', ...). The binary ones need two operands (left and right), while unary ones just need one. An expression might be one of the following: A bracket expression, which contains a full ParseTree again. A number - it can either be positive, negative, real or complex. An absolute value, which is sometime like a bracket expression that calls the abs() function after being evaluated. A symbolic value - either to be resolved (could be a constant or something that has been stored previously) or to be set. A function expression can be viewed as a symbolic value that has a bracket expression directly attached (without an operator between the expression). This results in the following class diagram: Let's have a look at the class diagram for the operators next: Here we see that the AssignmentOperator is also nothing more than another BinaryOperator. For simplicity and code-reuse another abstract class called AssignmentPrefixOperator has been added as an additional layer between any combined assignment operator (like += or -=) and the original assignment operator. Overall the following assignment operators have been added: +=, -=, *=, /=, \=, ^=. The difference between the left division (/) and right division (\) is that, while left division means A = B / C = B-1 * C, right division means A = B \ C = B * C-1. The difference between both can be crucial for matrices, where it matters, if we multiply from left or right. The two operands are always of type Value. This type is an abstract class, which forms the basis for the following derived data types: ScalarValue for numbers (can be imaginary) MatrixValue for matrices (can be only vectors), which consist of ScalarValue entries StringValue for strings ArgumentsValue for a list of arguments The functions are also a quite important part of YAMP. The class diagram for the functions look like: Here we create a standard type StandardFunction. This type has to implement the interface IFunction, as required. The only thing that will be called is the Perform() method. The StandardFunction contains already a framework that works only with numeric types like MatrixValue and ScalarValue. If the Perform() method won't be changed, every number of a given matrix will be changed to the result of the function that is being called. Another useful type here is the ArgumentFunction type. This one can be used to create a fully operational overload machine. Once we derive from this class we only have to create one or more functions that are called Function() with return type Value. Reflection will find out how many arguments are required for each function and call the right one (or return an error to the user) for the number of given arguments. Let's consider the RandFunction: class RandFunction : ArgumentFunction { static readonly Random ran = new Random(); public Value Function() { return new ScalarValue(ran.NextDouble()); } public Value Function(ScalarValue dim) { var k = (int)dim.Value; if(k <= 1) return new ScalarValue(ran.NextDouble()); var m = new MatrixValue(k, k); for(var i = 1; i <= k; i++) for(var j = 1; j <= k; j++) m[j, i] = new ScalarValue(ran.NextDouble()); return m; } public Value Function(ScalarValue rows, ScalarValue cols) { var k = (int)rows.Value; var l = (int)cols.Value; var m = new MatrixValue(k, l); for(var i = 1; i <= l; i++) for(var j = 1; j <= k; j++) m[j, i] = new ScalarValue(ran.NextDouble()); return m; } } With this code YAMP will return results for zero, one or two arguments. If the user provides more than two arguments, YAMP will throw an exception with the message that no overload has been found for the number of arguments the user provided. Implementing new standard functions, i.e., those functions which just have one argument, is easy as well. Here the implementation of the ceil() function is presented: class CeilFunction : StandardFunction { protected override ScalarValue GetValue(ScalarValue value) { var re = Math.Ceiling(value.Value); var im = Math.Ceiling(value.ImaginaryValue); return new ScalarValue(re, im); } } The implementation of the BinaryOperator class is quite straight forward. Since any binary operator requires two expressions the implementation of the Evaluate() method throws an exception if the given number of expressions is different from two. Two new methods have been introduced: One that can be overridden called Handle() and another one that has to be implemented called Perform(). The previous one will be used by the standard implementation of the first one. abstract class BinaryOperator : Operator { public BinaryOperator (string op, int level) : base(op, level) { } public abstract Value Perform(Value left, Value right); public virtual Value Handle(Expression left, Expression right, Hashtable symbols) { var l = left.Interpret(symbols); var r = right.Interpret(symbols); return Perform(l, r); } public override Value Evaluate (Expression[] expressions, Hashtable symbols) { if(expressions.Length != 2) throw new ArgumentsException(Op, expressions.Length); return Handle(expressions[0], expressions[1], symbols); } } Let's discuss now how YAMP is actually generated the available objects. Most of the magic is done in the RegisterTokens() method of the Tokens class. void RegisterTokens() { var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes(); var ir = typeof(IRegisterToken).Name; var fu = typeof(IFunction).Name; foreach(var type in types) { if (type.IsAbstract) continue; if(type.GetInterface(ir) != null) (type.GetConstructor(Type.EmptyTypes).Invoke(null) as IRegisterToken).RegisterToken(); if(type.GetInterface(fu) != null) AddFunction(type.Name.Replace("Function", string.Empty), (type.GetConstructor(Type.EmptyTypes).Invoke(null) as IFunction).Perform, false); } } The code goes over all types that are available in the executing assembly (YAMP). If the type is abstract, we avoid it. Otherwise we look if the type implements the IRegisterToken interface. If this is the case we will call the constructor, treat it as a the interface and call the RegisterToken() method. This method usually looks like the following (for operators): public void RegisterToken () { Tokens.Instance.AddOperator(_op, this); } Now we have the case that an operator is searched from our ParseTree. In this case the FindOperator() method of the Tokens class will be called. public Operator FindOperator(string input) { var maxop = string.Empty; var notfound = true; foreach(var op in operators.Keys) { if(op.Length <= maxop.Length) continue; notfound = false; for(var i = 0; i < op.Length; i++) if(notfound = (input[i] != op[i])) break; if(notfound == false) maxop = op; } if(maxop.Length == 0) throw new ParseException(input); return operators[maxop].Create(); } This method will find the maximum operator for the current input. If no operator was found (the length of the operator with the maximum length is still zero) an exception is thrown. Otherwise the found operator will call the Create() method. This method just returns another instance of the operator. A simple example of reflection's magic We've already discussed the ability to add custom functions by deriving either from StandardFunction or ArgumentFunction. If we want to start completely from scratch we could also write an own function class by just implementing the IFunction interface. Let's have now a look at the ArgumentFunction implementation. Here we will see that reflection is quite an important part. First let's look at the code for this class: abstract class ArgumentFunction : StandardFunction { protected Value[] arguments; IDictionary<int, MethodInfo> functions; public ArgumentFunction () { functions = new Dictionary<int, MethodInfo>(); var methods = this.GetType().GetMethods(); foreach(var method in methods) { if(method.Name.Equals("Function")) { var args = method.GetParameters().Length; functions.Add(args, method); } } } public override Value Perform (Value argument) { if(argument is ArgumentsValue) arguments = (argument as ArgumentsValue).Values; else arguments = new Value[] { argument }; return Execute(); } Value Execute() { if(functions.ContainsKey(arguments.Length)) { var method = functions[arguments.Length]; try { return method.Invoke(this, arguments) as Value; } catch (Exception ex) { throw ex.InnerException; } } throw new ArgumentsException(name, arguments.Length); } } The crucial part here happens within the constructor. Here the (specific implementation) is inspected. Every method that has the name Function() will be added to a dictionary with the number of arguments as key. The information in the dictionary is then used in the Execute() method. Here the method with the specified number of arguments will be invoked. If the number of arguments does not match any method, an exception will be thrown. In order to call the Execute() method the method Perform() has been overridden. Here any argument is transformed to an array of Value instances. Usually the number of arguments in the array is one, except if the passed argument is of type ArgumentsValue. In this case the Value type already contains zero to many Value instances. Parsing a (simple?) expression In the following paragraphs we are going to figure out how YAMP is parsing a simple expression. The query is given in form of 3 - (2-5 * 3++(2 +- 9)/7)^2. We actually included some whitespace, as well as some (obvious) mistakes in the expression. First of all "++" should just be expressed as "+". Second the operation "+-" should be replaced by just "-". First of all the problem is divided into two specific tasks: Generate the expression tree, i.e. separate operators from expressions and mind the operator levels. Interpret the expression tree, i.e. evaluate the expressions by using the operators. The parser then ends up with an expression tree that looks like the following image. In the second step we call the method to start the interpretation of the elements. Therefore the interpreter works and calls the Interpret() method on each bracket. Every bracket looks at the number of operators. If no operator is available, then there has to be only one expression. The value of the expression is then returned. Otherwise the Handle() method of the operator is called, passing the array of Expression instances. Since the operator is either a specialized BinaryOperator or a specialized UnaryOperator, the method will always call the right function with the right number of arguments. In this example our expression tree consists of five layers. Even though the interpretation starts at the top level (1), it requires the information of the lowest level (5) to be executed. Once 5 has been interpreted, level 4 can be interpreted, then level 3 and so on. The final result of the given expression is -193. YAMP against other math parsers We can guess that YAMP is probably slower than most parser written in C/C++. This is just the native advantage (or managed disadvantage) we have to play with. Therefore benchmarks against parser written in C++ may be unfair. Since Marshalling / Interop takes some time as well, parsing small queries and comparing the result might also be unfair (this time probably unfair for the C/C++ parser). Finally a fair test might therefore only consist of C# only parser. Having a look at CodeProject we can find the following parsers: MathParser ( MP ) - Math Parser - CodeProject MathParser TK (MPTK) - Math Parser NET C# - CodeProject MathParser.NET (MP.NET) - Math Parser .NET - CodeProject Math Formula Parser (MFP) - MathParser - Math Formula Parser - CodeProject LL Mathematical Parser (LLMP) - LL Mathematical Parser - CodeProject We set up the following four tests: Parse and interpret 100000 randomly generated queries* Parse and interpret 10000 times one pre-defined query (long with brackets) Parse and interpret 10000 times one pre-defined query (medium size) Parse and interpret 10000 times one pre-defined query (small) * The expression random generator works like this: First we generate a number of (binary) operators, then we alternate between expression and binary operator, always randomly picking one. The expression is just a randomly picked number (integer) and the operator is a randomly chosen one from the list of operators (+, -, *, /, ^). In the end we have a complete expression with n operators and n+1 numbers. The problem with this benchmark is that only 3 (YAMP, MathParser.NET, and LL Mathematical Parser) parsers have been able to parse all queries (even excluding the ones using the power operator). Therefore this benchmark was just able to give a speed comparison between these three. However, we can use this test to identify that YAMP is really able to parse a lot of different queries. If we look at the time that is required per equation in dependency of the number of operators, we see that YAMP scales actually pretty well. MFP and LLMP are significantly better here, however, MFP could not parse much at all and LLMP does not support complex arguments nor does it support matrices. The biggest problem with the fastest implementations are the constraints given by them. They are hard to extend and less flexible than YAMP. While YAMP does contain a lot of useful features (indices, assignments, overloaded functions, ...), none of the other parsers could come even close to the level of detail. The longer (and more complex) the benchmark, the better for YAMP. This means that YAMP performs solid in the region of short expressions (where no one will notice much about great performance), while it performs great in the region of long expressions (where most would notice delays and other annoying stuff otherwise). In the table shown above we see that YAMP can parse all equations and does perform quite well. Considering the early stage of the development process and the set of features, YAMP is certainly a good choice for any problem involving numerical mathematics. How to use YAMP in your code If we use YAMP as an external library we need to hook up every chance we want to do. Luckily for us, this is not a hard task. Consider the case of adding a new constant. YAMP.Parser.AddCustomFunction("G", v => new YAMP.ScalarValue((v as YAMP.ScalarValue).Value * Math.PI) ); This way we can override existing constants. If we remove our custom constant later one, any previously overridden constant will be available again. This way we can also add or remove custom functions. Here our own function needs to have a special kind of signature, providing one argument (of type Value) and returning an instance of type Value. The following example uses a lambda expression to generate a really simple function in one line: YAMP.Parser.AddCustomFunction("G", v => new YAMP.ScalarValue((v as YAMP.ScalarValue).Value * Math.PI) ); What does it take to actually use YAMP? Let's see some sample first: try { var parser = YAMP.Parser.Parse("2+(2,2;1,2)"); var result = parser.Execute(); } catch (Exception ex) // this is quite important { //ex.Message } So everything we need to do is to call the static Parse() method in the Parser class (contained in the YAMP namespace). The method results in a new Parser instance that contains the full ParseTree. If we want to obtain the result of the given expression, we only need to call the Execute() method. A word of caution here regarding the provided solution: Compiling the console, debug or release project may result in an error, due to a bug in MonoDevelop or Visual Studio. If otherwise referenced projects are excluded from the built mappings (as this is the case with the YAMP.Compare project in these 3 built projects), then an error is thrown on first built. Therefore we have to change the project's mappings to include YAMP.Compare again. (Current) limitations YAMP has some limitations. Some of those drawbacks are temporary - others are permanent. Let's have a look what is planned for the future: A kind of scripting language with all necessary operators, if, for, while, switch etc... Multi line statements Custom output formatters (currently the output format is fixed, along with the internal precision and others) Ability to set the precision Asynchronous calls (you call the parser / interpreter asynchronous, i.e. in a new thread - you have to set an event handler then for getting the result) More numeric algorithms More logic functions (currently only isint(), isprime() are implemented) More options for clearing, loading and saving variables This is just a short list of planned features. Some of them are easier to implement, some of them are harder. Some of them are in a fixed time window, while others give coding tasks for eternity. Together with the limitations that will (hopefully) be broken, some limitations, which are here to stay, exist: There are no multi-dimensional matrices. A matrix is always 2D at max. Matrices can only contain complex numbers (ScalarValue) and not other matrices, strings etc. Variables are case-sensitive (along with the case-insensitive constants and functions, this was designed on purpose as a feature). Only the previous output is saved in the $ variable Variables can only contain letters and numbers, while it must start with a letter. Overwriting the constant i (complex identity) with a variable is possible. YAMP differences between variables and functions, i.e. it is not possible to overwrite a function sin() with a variable named sin. Points of interest YAMP makes use of reflection to make extending the code as easy as possible. This way adding an operator can be achieved by just adding a class that inherits from the abstract Operator, BinaryOperator or UnaryOperator class or any other operator that is available. YAMP is independent of the set culture (the US numbering style has been set as numbering format explicitly). Strings and others are stored with UTF-8 encoding. However, symbols can only start with a letter (A-Za-Z) and can then only contain letters and numbers (A-Za-z0-9). The project is currently under heavy development, which results in a quite fast update cycle (one to two new versions per week at least). This means that you should definitely consider taking a look at the GitHub page (and maybe this article), if you are interested in the project. Generally YAMP will require more interested people working on it (mostly on some numeric functions). If you think you can contribute, then feel free to do so! Everyone is more than welcome to contribute to this project. A strong request If you have something to criticize, then please describe it briefly in a comment. Right now now some people voted 4 without leaving a comment. Usually a vote lower than 5 indicates some wrong behavior or mistake from my side. If this is the case, then please tell me why. Otherwise you should think about why you vote lower than 5, when there is nothing wrong with the article. History v1.0.0 | Initial release | 19.09.2012. v1.0.1 | Added a table with benchmark data | 19.09.2012 v1.1.0 | Added a section with an example process | 20.09.2012 v1.2.0 | Added a section with limitations | 21.09.2012 v1.3.0 | Added a section regarding ArgumentFunction | 22.09.2012 v1.3.1 | Fixed some typos | 24.09.2012 v1.3.2 | Fixed some typos | 25.09.2012 Download it demo : YAMP_Demo.zip download - 2shared Download source : YAMP_Source.zip download - 2shared http://www.codeproject.com/Articles/462180/Yet-Another-Math-Parser-YAMP
-
Fa si tu 1 .
-
Introduction This is an attempt to explain new && reference present in latest versions of compillers as part of implenmenting the new C++ 11 standard. Such as those shiping with Visual studio 10-11-12 and gcc 4.3-4, or beautifull fast( equally if not more) opensource alternative to gcc CLang. Why you should start using it ? In short : Nontrivial performance gain. For example inserts to std::vector (or in fact any array creation) will not cost huge amount of allocs/copies anymore. But before we go to detail of new c++ 11 "Move semantics" we need to understand the core of problem. We need to understand why performance problem of c++ language with = assign operation often resulting to useless alloc/copy exists. We live in the world where we need to work with a lot of data. We need to store and process it in an effective manner. Problem with c and c++ is that we got used to do it inefectively. a = 1 // we assigned (we could say copied) number to variable And since in natural language we think about "assigning" data to arrays too. It was only natural and could be expected that we continued to assign to arrays in C language the same way. textures[1] = new Texture(640*480*32,"cube.jpg"); The real price for copying via = was small since pointers are just numbers (containing memory addresses). But with objects the story is different. How the performance problem started Now I will try to keep this short but it is important to really understand why && operator was born. In C++ working with references become prevalent since they are safer alternative to pointers and very importanty new language features such as automatic call of constructors or destructors (automatic cleanup) or operators worked only with them. Now make no misteake. References are internally still just pointers but this time made little bit safer and automaticaly dereferenced. Period. How are they made safer? Well. They always point to existing static data. But whenever you pass reference to function in fact it is still just pointer that is internally pushed on stack. void proc( Texture * texture ) { Save(texture); } // unsafe since invalid pointer can be passed in void proc( Texture & texture ) { Save(texture); } // safer if Texture don't contain invalid pointer inside. C++ wanted to make our life easier by doing routine pointer de/referencing for us (and hiding this pointer wizardry from us). Illusion of passing or working with objects instead of pointers to them was so perfect that many lost sense what is actually reference and what is object. Exactly this ambiguity had unfortunate side effect that led us into beliving that this Texture texture[1000]; texture[1] = Texture("sky.jpg"); // we are assigning reference to what ? array of references? is just is safer alternative to this Texture * texture[1000]; texture[1] = new Texture("sky.jpg"); // we are assigning pointer to array of pointers We just got rid of unsafe pointers in favor of safer references like everywhere else.But more importantly this has given us automatic call of constructors destructor and operators. Right? Wrong. There is no such thing as array of references in C++. No pointer magic behind the scene is going on this time like it is within functions. So what we actualy created is algorithmically very bad decision. We created an array of objects stored by value. And no. Removing * from declaration doesnt automaticaly make pointer variable reference. From performance point of view there is really no alternative to array of pointers . With big objects containing static structured data there is big performance difference when creating sorting searching and mainly reallocating 100mb array of values. Then it is with few bytes of pointers to them. It pretty much kills the possibility to work within processor cache with as much data as possible. So we sort/search/reallocate etc on the speed of main memory order of magnitude slover instead of cpu cache which we now uselesly pollute with unimportant object data. With objects containing big dynamic data situation is better when sorting etc. But still bad when assigned to array. Since then we need to allocate and copy large chunks of dynamic mem plus rest of object. But I would say mentioned reallocation is worst consequence of storing object by values. So every time you think of "nah lets put vector<largeobject> there". Your memory requirements will be twice of what they need to be and your performance abysmal due to fact that whole allocated mem will be uselesly moved around after each realloc. One would think that this is just price we indeed agreed to pay for generic approaches and lazyness. But by my opinion this is the price for storing objects instead of pointers for the oo reasons (automatic constructors destructors operators) mentioned above. But back to the topic. As we remember assign = "actually" copies data. And this leads us to another big performance problem with arrays. How to efficiently build array of large objects. Suppose you wana build a city full of scyscrappers and you obviously (due to large scale) can't afford any waste of time or resources. Now think of city as an array analogy. So what you actually do is you "create" building inside city you allocate large space for building inside of city and obviously you just build building in this space. But thanks to our habit of using = operator to store object pointers to array in good old C. Its only natural that we attempt to use the same "create and assign" paradigm with references too; In other words. We simply got used to it and what's worse every c++ book teaches as to do it this ineffective way. Skyscrapper city[1000]; city[1] = Skyscrapper("Empire State Building"); So instead of "create inside array" which we learned from city analogy. you allocate large temporary space outside city // = Skyscrapper(); you build scyscrapper in this temporary space you allocate the same large space inside city // city[1].operator = (Skyscrapper &in) you use large amount uf trucks gas and time to move this scyscrapper to space in city Now worst is the useless copy part. Why? Its an order of magnitude slower than all the steps combined. Because when usually even the largest objects are created this large memory is merely preallocated. That means usually no usefull data are yet in them. Yet by forcing copy operation we are forced to move them byte by byte no matter wether data is valid or not. The larger the object static+dynamic memory the larger the performance drop. How big is the performance drop ? Now It would seem unbelivable but as we speak every usual object assigned by reference and not by pointer in c++ suffers this and assigning to arrays is worst. just check benchmark results in middle of article. That is pretty much most of the code around you. Go ahead an check nearest code It manifests with arrays so strongly because of sheer number of inefective assigments. In case of our benchmark its 500 assigments but if you do any reference assigment that can be handled by moving (as explained later) and not copying in loop with 500 iterations you have basically the same problem. But back to the arrays. So are there ways in c++ to store object to array effectively ?(damn... I got used to it too Wink | <img src= . I mean create object in a array effectively) without wasted cpu and mem? Since as you seen in benchmark. The larger the objects are the more it matters. Yes there are but they are nor intuitive or obvious and are hack like in nature. Now if C++ did allow us to invoke specialized constructor and create objects using already allocated space inside array(that was allocated just once for all elements by one efficient alloc). Then this could saved zillion of allocs/copies most of us usually do by assining new objects via = ; Skyscrapper city[1000]; city[1]("empire state building") //not possible in c++ will not compile Still. There are some ways to do it. You can for example move all your initialization code to method and invoke it on array element explicitly. Skyscrapper city[1000]; city[1].create("empire state building"); you allocate large space for building inside city you build building in this space. Hurray. The problem introduced by using = is gone. That means now it doesn't matter if object have large static array or structure. No copy no problem. Most importantly the wasted cpu on moving mostly usleless empty bytes is gone. Also positive is the fact that reading and writing such a large chunk of memory which pretty much flushed all cpu caches bringing down performance of the rest of the application is gone too. That's all nice and great. But chances that people will stop puting code to constructors in favor of some standard create method are pretty slim. Using constructors to create everything is paradigm that we got so used to and love. Exactly as we got trained by misleading books and used = operator for storing data to arrays. Still. There is way to do it with constructor via little known variant of operator new Its called "placement new" where you construct object on existing memory with this pointer provided by you. But now we are entering very weird confusing and little bit danferous territory due to word new flying around static array. New that doesnt allocate anything. New that is here just as a kludge to invoke constructor. Why dangerous? The moment you overload something as fundamental as allocator New brace yourself for all kind of troubles Dr. Dobb's | Sutter’s Mill: To New, Perchance To Throw [1] (Part 1 of 2) | March 01, 2001 . #include <new> Skyscrapper city[1000];// we allocate static array with all objectsall just once new (&city[1]) Skyscraper("empire state building"); //no object alloc just calls constructor city[1].~Skyscraper(); // frees resources allocated by previous constructor call new (&city[1]) Skyscraper("renovated empire state building"); city[1].~Skyscraper(); // frees resources allocated by previous constructor call It's unnatural and this time problematic plus very confusing too. But Storing objects was always very bad idea anyway as you can see in sorting results in benchmark bellow. So what about returning to storing just pointers? As we mentioned above no oo goodies for pointers. { vector<Texture*> textures(100); // no constructors called textures[1] = new Texture(640*480*32,"cube.jpg"); //no operators invoked on textures[1] } // leaving scope but no destructors called on vector items ;( Most importantly when vector goes out of scope no destructors are automaticaly called. it can be done manually but it reintroduces source of bugs. C++ could solve it by introducing scoped version of new. like new_local. In such case compiller would generate calling destructors when leaving scope exactly as it is doing today with static objects. Is automatic cleanup of objects stored by pointers really that imposible in current c++? Now consider following weird but perfectly working example. Remember stack is defaultly limited (unless you change it in linker settings) resource. So take this as purely academic example that aray of pointers that automatically calls destructors when going out of scope is possible. struct A{ int no; operator A* () { return this; } // just typecast }; void array_of_objects_stored_by_pointers_with_autocleanup() { A* a[10000]={0}; // an array of references which was not supposed to exist ? a[7] = A(); a[7]->no=7; // no "new A()". Since "A()" is the same just not on heap but on stack a[4] = A(); a[4]->no=4; // and auto cleanup of stack is build-in } The moment array of objects stored by pointers goes out of scope they are automatically deallocated without any manual destructor invocation; How come this works ? What is going on? = A() is internally the same as = new A(); the same constructor is invoked. Except for first stack is used by allocator and heap for second. both return pointers. references are pointers(just meeting certain criteria to deserve label reference) as we remember right ? Yes for heap pointers (created by new) there is kludge of wrapping all pointers to objects simulating pointers via operator overloading aka(smart pointers) in std::shared_ptr and alike. and store just those. So if you dont mind wraping all your variables to functionality hiding impossible to debug macros/templates then this is very good solution for you. But I strongly belive that simple things shall not be encrypted or hidden from sight nor does every variable. Programmer must be avare of what is going on as much as possible like it was in C. without having template and macro expander build in his head. And if you ask Linus to obfuscate every pointer to template wrapper he would most probably kill you. I remember strong backlash against excesive macro usage in C. And there was rational reason for that. That reason was "complexity and hiding code logic is source of bugs". The possibly best solution is to fix C++ compillers = operation After all those attempts to store anything via = without waste I think that compiller should do "placement new" transparently for us whenever he encounters = on movable object. Instead of forcing us to implement zillion operators that solve only heap side of problem. Deciding what kind of "this" object should use is easy since static analysis deciding what object is movable is already part of new compillers as part of support for &&. Skyscrapper city[1000]; // instead of temp on any assign city[1] = Skyscrapper("Empire State Building"); // compiller should use &city[1] as this pointer So this internally can be optimized (via explicit optimization switch) to something like Skyscrapper city[1000]; // we mark every element as in default constructor initialized state try { // to enable this optimization default constructor can contain only memset calls to solve reinvocation problem new (&city[1]) Skyscrapper("Empire State Building"); } catch(...) { // in case of exception new (&city[1]) Skyscrapper() // we restore object to post default constructor state to keep standard behavior of C++ standard throw; // and we rethrow any exception } This would fix heap and static waste = zero alloc/copy since elements are created just once in already allocated memory as it always should had been for performance reasons. Why is static mem waste equally if not more important? Majority of objects are small thus majority of their memory is static. And when you look at benchmark bellow storing static array took 5662 ms yet storing dynamic array took 1372 ms. Also. After such change to compiller all old code using big static objects would start pretty much flying at completely different speeds just by recompiling. Because I am curious person I am attempting to implement and test it in clang fantastic open source c++compiller as a optimization switch or pragma. Should you wana lend a hand I will be more than thankfull Clang Developers - Proposed C++ optimization with big speed gains with big objects | Threaded View . But let's focus on latest C++ solution to it (unfortunately only for heap mem in your objects and with a lot of code changes) The new C++ 11 Move semantics Move semantics enables you to write code that transfers dynamically allocated memory from one object to another. Move semantics works because it enables this memory to be transferred from temporary objects(by copying just pointers) that cannot be referenced elsewhere in the program. Unfortunatelly large static data must still be uselessly copied since they are contained within temp object themself that is about to be destroyed. To implement move semantics, you typically provide a move constructor, and optionally a move assignment operator= to your class. Copy and assignment operations whose sources are (temp objects or data that can't change) then automatically take advantage of move semantics. Unlike the default copy constructor, the compiler does not provide a default move constructor. You can also overload ordinary functions and operators to take advantage of move semantics. Visual C++ 2010 introduces move semantics into the Standard Template Library (STL). For example, the string class implements operations that perform move semantics. Consider the following example that concatenates several strings. string s = string("h") + "e" + "ll" + "o"; Before && references existed, each call to operator+ allocated and returned a new temp object. operator+ couldn't append one string to the other because it didnt know whether content of the source can be tampered with (temps) or not (variables). If the source strings are both variables, they might be referenced elsewhere in the program and therefore must not be modified. But now thanks to && reference we now know that temp (which cannot be referenced elsewhere in the program) was passed in. Therefore, operator+ can now safely append one string to another. This can significantly reduce the number of dynamic memory allocations that the string class must perform. Move semantics also helps when the compiler cannot use Return Value Optimization (RVO) or Named Return Value Optimization (NRVO). In these cases, the compiler calls the move constructor if the type defines it. As an another example consider the example of inserting an element into a vector object. If the capacity of the vector object is exceeded, the vector object must reallocate memory for its elements and then copy each element to another memory location to make room for the inserted element. When an insertion operation copies an element, it creates a new element, calls the copy constructor to copy the data from the previous element to the new element, and then destroys the previous element. Move semantics enables you to move objects directly without having to perform expensive memory allocation and copy operations. So. To take advantage of move semantics to allow efficient insert of your objects in the std::vector, you must write a move constructor to allow moving of data from one object to another. So let's see what is going on within our usual inefficient city copy operator = example but with move operator = implemented Well. Aditionally to your copy operator = (&) where you always just copy assigned variable data Now you define aditional move operator = (&&) that will be called instead when data that canot change (such as temp object created when assigning to array) is passed in. Skyscrapper city[1000]; city[1] = Skyscrapper("Empire State Building"); you allocate large space for building outside of city // notice = Skyscrapper(); you build building in this space. you just mark this already created building as part of city // no trucks (copying) needed void operator = ( Skyscraper && in ) { // temp obj was passed in mem = in.mem; // We copy just data pointer from temp (ie we move data) size = in.size; // which is safe since temp obj can't change in.mem = 0; // THIS IS KEY PART: we prevent deallocation when temp is destroyed } ~Skyscrapper() { if(mem) delete mem; } //BUT destructor must have delete as conditional Hurray no allocate/copy from temp object = finally no cpu is wasted and cache trashed Smile | <img src= Memory allocated and initialized by temp is not released since it has new owner. For complete compilable example copy benchmark code bellow to dev env of your choice. No for those who thing everything was clear and obvious in previous example. Dont' let the eyes fool you. void operator = ( Skyscraper && in ) { // From now on in is of type Skyscrapper & and not && next_proc(in); // Skyscrapper & is passed in and is not movable anymore next_proc((Skyscrapper &&)in); // we need to explicitly retype it && temp to be movable again } Skyscraper && in is not actually of type && anymore. The moment it enters function its & again. So if you wana forward && to another function you need to cast it to && again (in stl via std::move). Why c++ decided to do this behind your back hidden functionality ? Well I am being told that it's security precaution. That any && having name is in risk of being referenced somewhere else in code and thus it's not deemed safe for keeping "moveable" status. Seems like some unfinished c++ business to me since I can't imagine referencing local variable outside of this proc. Also there is little know feature of ref-specifiers where you can restrict operator/methods to accept just temps or just variables. struct string { string& operator=(string const& other) & { /* ... */ } }; Now, you can't anymore say string() = "hello"; Unfortunatelly this doesn't yet seem to be supported in Visual Studio 2012 RC1 that I am using right now. So to summarize. Unless you use contained big static arrays the result is significant speedup of your existing code. Tho see how much you can speedup your existing code (well... actually you stop slowing it down)I created simple practical && example along with benchmark results. But if you do more than just stupid memset in your constructors/destructors speedups will be significantly higher. Benchmark Results: store objects containing static array took 5662 ms // even with && this is still problem sort objects containing static array took 17660 ms // this is why you should not store objects store objects containing dynamic array by copying took 1372 ms store objects containing dynamic array by moving (c++ 11) took 500 ms store just pointers to objects took 484 ms sort just pointers to objects took 0 ms Benchmark Code To have an idea how bad the usual careless assign = is. I created example storing 500 large objects to array via different methods and measured time it takes. Texture represents standard large object we work in c++ on daily basis = just large chunk of data and its size plus mandatory operator = to be able to be stored by value. Now it's stripped to bare minimum on purpose(no chaining consts etc) . with only simple types so you can focus only on those two operators. And sort has < reversed to simulate worst case scenario. #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <algorithm> using namespace std; // current C++ Average Joe Object struct Texture { int size; int color; void* mem; Texture() : size(0), color(0), mem(0) {} ~Texture() { if(mem) delete mem; mem = 0; } Texture ( int Size, int Color ) { mem=new char[size=Size]; memset(mem,color=Color,size); } void operator = ( Texture & in ) { // variable passed in color = in.color; mem = new char[size=in.size]; // so we need copy memcpy(mem,in.mem,in.size); // since variables change } }; // C++ 11 Enhanced version struct Texture2 : virtual Texture { void operator = ( Texture && in ) { // temp obj is passed in color= in.color; mem = in.mem; // We copy just data pointer from temp (ie we move data) size = in.size; // which is safe since temp obj can't change in.mem = 0; // THIS IS KEY PART: we prevent deallocation when temp is destroyed } }; // C++ 11 unfortunately cant solve the need to copy static object data // so this object can't by stored in c++ via = efficiently without useless copy // and even new operator && will not help since he solves only heap part of the problem. // Point is. Don't use large static arrays/structures/too many members // or = with objects containing them if you care about speed. struct Texture2StaticArray : Texture2 { Texture2StaticArray() : Texture() {} Texture2StaticArray( int Size, int Color ) { memset(static_array,color=Color,sizeof(static_array)); } char static_array[640*480*8]; }; #define TEXTURES 500 void store_objects_containing_static_array() { Texture2StaticArray* texture =new Texture2StaticArray[TEXTURES]; DWORD start = GetTickCount(); for(int i=0;i<TEXTURES;i++) { texture[i] = Texture2StaticArray(0,i); } printf("\nstore objects containing static array took %d ms", GetTickCount()-start ); start = GetTickCount(); sort(texture,texture+TEXTURES, [] (Texture2StaticArray& a, Texture2StaticArray& { return a.color > b.color; } ); printf("\nsort objects containing static array took %d ms", GetTickCount()-start ); delete [] texture; } void store_objects_containing_dynamic_array_current_standard() { Texture texture [TEXTURES]; DWORD start = GetTickCount(); for(int i=0;i<TEXTURES;i++) { texture[i] = Texture(640*480*8,i); } printf("\nstore objects containing dynamic array by copying took %d ms", GetTickCount()-start ); } void store_objects_containing_dynamic_array_new_standard() { Texture2 texture [TEXTURES]; DWORD start = GetTickCount(); for(int i=0;i<TEXTURES;i++) { texture[i] = Texture(640*480*8,i); } printf("\nstore objects containing dynamic array by moving (c++ 11) took %d ms", GetTickCount()-start ); } void store_pointers_to_any_object() { Texture* texture [TEXTURES]; DWORD start = GetTickCount(); for(int i=0;i<TEXTURES;i++) { texture[i] = new Texture(640*480*8,i); } printf("\nstore just pointers to objects took %d ms", GetTickCount()-start ); start = GetTickCount(); sort(texture,texture+TEXTURES, [] (Texture* a, Texture* { return a->color > b->color; }); printf("\nsort just pointers to objects took %d ms", GetTickCount()-start ); for(int i=0;i<TEXTURES;i++) { // We need to call destructors manually delete texture[i]; } } void main() { store_objects_containing_static_array(); store_objects_containing_dynamic_array_current_standard(); store_objects_containing_dynamic_array_new_standard(); store_pointers_to_any_object(); Sleep(-1); } Now the more observable of you would probably would start arguing... "This is nothing new I could do this data "moving" (or just passing data along between objects without copying) the same way in current standard c++ operator = & so why do I need new && operator ? Yes you can and No you can't. If you did moving in operator = & like this. Imagine what would happen ... // This will not work as intended. Explanation bellow void operator = ( const string & in ) { mem = in.mem; // We move data by pointing to it size = in.size; in.mem = 0; } ... string a("white"),b("yellow"); string c=b; // c is "yellow" now ... b="gotcha..." // but c is now "gotcha..." too -> should not happen !!! If we moved = copied just pointers to data in standard operator = & Then whenewer b changes c changes too; And this was not intended so we actually wana make copy when assigning from data that can change and we just copy pointers to data that we are sure will not change Unfortunately & up to c++ 11 could not distingush if passed data can change so moving was not possible in current c++ standard for the reasons explained in c=b example. the new && in turn can distingush that data which cant change was passed in and thus its safe just point to its data and skip copying. So to summarize. in new c++ 11 standard you are now supposed to keep two sets of operators and constructors operator = and constructor taking & where you copy from data that can change (variables etc,) operator = and constructor taking && where you just point to data that will not change and save mem and cpu by skipping copying (temp objects,etc) Unfotunately that means you will now have to implement two sets of pretty much every operator under the sun that you declared with generic copy/paste like code yet still fixing only heap side of the performance problem. So reconsider using = on objects at all. At least until compiller writers fix heap and static mem waste by doing internal placement new for = on movable objects. string b(a); //compiller invokes constructor (&) on b and we make copy since a can change string c=b; //compiller invokes operator = & on c and we make copy since b can change string texts[10]; texts[1]= string("white fox"); //compiller invokes =&& on texts[1] since temp obj was passed in Why it's called rvalue reference && Now the whole article I was deliberately was not using termins like rvalues(cant change) and lvalues(can change) since they are not what their names imply. lvalue should had been named something like "variable" rvalue should had been named something like "temp" So whenever you read text using lvalue and rvalue just replace those two and sudenly text will make sense Wink | <img src= They are just technical grammar remnants confusing people. the were born from how C grammar in lex and yacc was described eg on what side of "that particular grammar rule they vere located = left or right" BUT that particular rule can be part of larger expression and lvalue is sudenly rvalue. Or let me explain it this way. Anything not having name is rvalue otherwise it's lvalue Take care Smile | <img src= http://www.codeproject.com/Articles/453022/The-new-Cplusplus-11-rvalue-reference-and-why-you
-
Introduction One of the common problems with ASP.NET MVC is the handling of buttons on a form when multiple 'submit' buttons are present. Take for instance the following demonstration form (from the enclosed sample app): Every button posts the form so that the information filled in does not get lost. But every button should have a different effect. In addition, some buttons are repeated per element in collections. For instance, there is an “Add Employee” button for each department, and an “+50” button for each employee. Several solutions exist to handling multiple buttons on a form, see: a : http://stackoverflow.com/questions/442704/how-do-you-handle-multiple-submit-buttons-in-asp-net-mvc-framework b : http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form.aspx c : http://blog.ashmind.com/2010/03/15/multiple-submit-buttons-with-asp-net-mvc-final-solution/ d : http://blog.maartenballiauw.be/post/2009/11/26/Supporting-multiple-submit-buttons-on-an-ASPNET-MVC-view.aspx However, I found none of the existing solutions to be sufficiently complete to handle the cases I wanted to handle in a clean way. The features of the button handler I here present include: Simple and intuitive HTML-code in the view Simple attribute based controller action method selection Supports button with values Supports indexing of buttons (for button per array/collection element) No localization issues Fully javascript/JQuery free Some examples A simple button For a first example, let’s see how the HTML/Razor code looks like for a simple button as the “Add Department” button at the bottom of the above screen dump: <button type="submit" name="AddDepartment">Add Department</button> Or, using the INPUT element: <input type="submit" name="AddDepartment" value="Add Department" /> The code in the MVC controller to handle this button is the following: [ButtonHandler] public ActionResult AddDepartment(Company model) { model.Departments.Add(new Department()); return View(model); } It is a regular action method returning an ActionResult. The differences are that the method has a [buttonHandler] attribute, and that the name of the method does not match the action (the post action in this sample is “Index”), but matches the button name! However, if you like, you could mention the action name, either by using an ActionName attribute, or by setting the ActionName property of the ButtonHandler. You could also explicitely set the ButtonName property, in which case the name of the method does not matter any more. Following are valid alternatives of the above ButtonHandler attribute: [ActionName("Index"), ButtonHandler()] [ActionName("Index"), ButtonHandler(ButtonName = "AddDepartment")] [ButtonHandler(ActionName = "Index")] [ButtonHandler(ActionName = "Index", ButtonName = "AddDepartment")] So the [buttonHandler] attribute is used to mark the action method that will handle a button action. Buttons with values Now let’s take a look at the company budget buttons: It would have been possible to create two distinct buttons with distinct names and distinct button handler action methods. But in this case I’ve solved this differently. The HTML/Razor code is the following: html]<label>Remaining budget of the company :</label> @Html.EditorFor(m => m.Budget, @readonly) <button type="submit" name="UpdateCompanyBudget" value="100">Budget+100</button> <button type="submit" name="UpdateCompanyBudget" value="-100">Budget-100</button> As you can see, both buttons have the same name ! However, they also have a (different) value. This allows them to be handled by the same MVC controller action method, which looks like: [ButtonHandler] public ActionResult UpdateCompanyBudget(Company model, decimal value) { // Increase the bonus budget by lowering the benefits of the shareholders: model.ShareHoldersBenefit -= value; model.Budget += value; return View(model); } Still we have a simple [buttonHandler] attribute, and an action method of which the name matches the button name. In addition we have a ‘value’ argument. This value argument will contain the value of the button (100 or -100-). The name of the argument (‘value’) is hardcoded but can be overwritten by using the ‘ValueArgumentName’ of the ButtonHandler property. For instance: [ButtonHandler(ValueArgumentName = "amount")] public ActionResult UpdateCompanyBudget(Company model, decimal amount) ... f course, you can still explicitely mention ActionName and/or ButtonName properties. Having buttons with values helps in a better separation between controller code and view: the view can decide to add even more buttons with different values without impacting the controller code. For the INPUT element in HTML, the value represents also the displayed button caption. Therefore, if you want to use INPUT elements instead of BUTTON elements, either consider button values as not supported, or accept that they will also be the caption of your button. Indexed buttons Another situation are buttons that are repeated per element in a collection or array. That is for instance the case of the “Delete” button to delete a department: If you add multiple departments, you will have multiple instances of this delete button. How can we detect which instance of the button was deleted ? We could of course have used the value of the button to hold an index value. That’s OK for a single loop level, but collections can be nested in which case a single index value is not sufficient. ASP.Net MVC solves collection element indexing issue by adding index values within the names of the HTML input control elements. For instance, the rendering of the name of a department (knowing that the model contains a collection of departments) is done using the following Razor expression: @Html.EditorFor(m => m.Departments[d].Name) Where ‘d’ is the for-loop index of the department. This translates in the rendered HTML into: <input id="Departments_0__Name" name="Departments[0].Name" type="text" value="Los Angeles" /> The name of the input field contains an index number between square brackets. Well, we’ll use the same trick to identify the instance of our delete button: <button type="submit" name="DeleteDepartment[@(d)]">Delete</button> Or, using an INPUT element: <input type="submit" name="DeleteDepartment[@(d)]" value="Delete" /> To handle this button, we need an action method named “DeleteDepartment”, that takes an argument for the button index. Here it is: [ButtonHandler(ArgumentNames = "departmentId")] public ActionResult DeleteDepartment(Company model, int departmentId) { // Delete the given department: model.Departments.RemoveAt(departmentId); return View(model); } When our button has arguments, we need to declare the arguments with the ArgumentNames parameter (which takes a comma-separated list of argument names). These arguments will then be used to bind the real method arguments to. It is possible to have multiple arguments (for nested loops for instance) combined with a value on the button. Let’s take a look at an example that combines nested loops and button values. Multiple indexes and values combined The “+50”, “+100”, “-50”, “-100” buttons on the above screen dump are an example of a combination of multiple indexes combined with values. On the controller side, a single action method handles all those buttons. Let’s first take a look at a simplified version of the Razor view containing these buttons: ... @for (int d = 0; d < Model.Departments.Count; d++) { ... for(int e = 0; e < Model.Departments[d].Employees.Count; e++) { <li> @Html.EditorFor(m => m.Departments[d].Employees[e].Name) Assigned bonus : @Html.EditorFor(m => m.Departments[d].Employees[e].Bonus) <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="50">+50</button> <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="100">+100</button> <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="-50">-50</button> <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="-100">-100</button> </li> } } ... The four buttons are placed within a nested loop. Therefore, the button name takes two indexes. The rendered HTML will be similar to (for the first employee of the fourth department): <li> <input id="Departments_3__Employees_0__Name" name="Departments[3].Employees[0].Name" type="text" value="Lauren Walker" /> Assigned bonus : <input id="Departments_3__Employees_0__Bonus" name="Departments[3].Employees[0].Bonus" type="text" value="0,00" /> <button type="submit" name="UpdateBonus[3][0]" value="50">+50</button> <button type="submit" name="UpdateBonus[3][0]" value="100">+100</button> <button type="submit" name="UpdateBonus[3][0]" value="-50">-50</button> <button type="submit" name="UpdateBonus[3][0]" value="-100">-100</button> </li> The controller action method to handle the UpdateBonus button is: [ButtonHandler(ArgumentNames = "departmentId, employeeId")] public ActionResult UpdateBonus(Company model, int departmentId, int employeeId, decimal value) { // Increase the bonus of the employee by lowering his departments budget: model.Departments[departmentId].Budget -= value; model.Departments[departmentId].Employees[employeeId].Bonus += value; return View(model); } Our button handler takes two index arguments and a value argument (as well as the model argument to hold the form postback). ButtonHandler reference The ButtonHandler attribute has following properties: ActionName The name of the MVC action. By default, the action name is not checked, only the button name is checked. Alternatively, you can use the [ActionName] attribute. ButtonName The name of the button to be handled. By default this is the name of the action method. ArgumentNames A comma-separated ordered list of argument names matching arguments of the action method. ValueArgumentName The name of the action method argument to bind the button value to. AllowGetRequests Whether the button handler accepts Http GET requests. By default, GET requests are not accepted. ButtonHandler code using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Web.Mvc; namespace MvcMultiButtonSampleApp { /// <summary> /// An MVC ActionName Selector for actions handling form buttons. /// </summary> public class ButtonHandlerAttribute : ActionNameSelectorAttribute { private readonly Regex ButtonNameParser = new Regex("^(?<name>.*?)(\\[(?<arg>.+?)\\])*$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled); private string argumentNames; private string[] arguments; /// <summary> /// Indicates this action handles actions for a button with the name of the action method. /// </summary> public ButtonHandlerAttribute() { this.ValueArgumentName = "value"; } /// <summary> /// Whether GET-requests are allowed (by default not allowed). /// </summary> public bool AllowGetRequests { get; set; } /// <summary> /// Name of the MVC action. /// </summary> public string ActionName { get; set; } /// <summary> /// Name of the button (without arguments). /// </summary> public string ButtonName { get; set; } /// <summary> /// Comma-separated list of argument names to bind to the button arguments. /// </summary> public string ArgumentNames { get { return this.argumentNames; } set { this.argumentNames = value; if (String.IsNullOrWhiteSpace(value)) this.arguments = null; else this.arguments = value.Split(',').Select(s => s.Trim()).ToArray(); } } /// <summary> /// Name of the method argument to bind to the button value. /// </summary> public string ValueArgumentName { get; set; } /// <summary> /// Determines whether the action name is valid in the specified controller context. /// </summary> public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo) { // Reject GET requests if not allowed: if (!AllowGetRequests) if (controllerContext.HttpContext.Request.GetHttpMethodOverride().Equals("GET", StringComparison.OrdinalIgnoreCase)) return false; // Check ActionName if given: if (this.ActionName != null) if (!this.ActionName.Equals(actionName, StringComparison.OrdinalIgnoreCase)) return false; // Check button name: var values = new NameValueCollection(); if ((this.arguments == null) || (this.arguments.Length == 0)) { // Buttonname has no args, perform an exact match: var buttonName = this.ButtonName ?? methodInfo.Name; // Return false if button not found: if (controllerContext.HttpContext.Request[buttonName] == null) return false; // Button is found, add button value: if (this.ValueArgumentName != null) values.Add(this.ValueArgumentName, controllerContext.HttpContext.Request[buttonName]); } else { // Buttonnname has arguments, perform a match up to the first argument: var buttonName = this.ButtonName ?? methodInfo.Name; var buttonNamePrefix = buttonName + "["; string buttonFieldname = null; string[] args = null; foreach (var fieldname in controllerContext.HttpContext.Request.Form.AllKeys .Union(controllerContext.HttpContext.Request.QueryString.AllKeys)) { if (fieldname.StartsWith(buttonNamePrefix, StringComparison.OrdinalIgnoreCase)) { var match = ButtonNameParser.Match(fieldname); if (match == null) continue; args = match.Groups["arg"].Captures.OfType<Capture>().Select(c => c.Value).ToArray(); if (args.Length != this.arguments.Length) continue; buttonFieldname = fieldname; break; } } // Return false if button not found: if (buttonFieldname == null) return false; // Button is found, add button value: if (this.ValueArgumentName != null) values.Add(this.ValueArgumentName, controllerContext.HttpContext.Request[buttonFieldname]); // Also add arguments: for(int i=0; i<this.arguments.Length; i++) { values.Add(this.arguments[i], args[i]); } } // Install a new ValueProvider for the found values: var valueProviders = new List<IValueProvider>(); valueProviders.Add(new NameValueCollectionValueProvider(values, Thread.CurrentThread.CurrentCulture)); valueProviders.Add(controllerContext.Controller.ValueProvider); controllerContext.Controller.ValueProvider = new ValueProviderCollection(valueProviders); // Return success: return true; } } } The sample The sample that comes with this article contains a simple MVC application to distribute budget over employee bonusses. When you start the application, the company has $1000 of shareholder benefit and $500 reserved for employee bonusses. When you increase that budget, the shareholder benefit is lowered. When you add budget to a department, that budget is taken from the companies budget. When you increase the bonus of an employee, that amount is taken from the departments budget. It’s just a sample app to try out multiple functional buttons on a form. There’s no backend database. Download it : http://www.2shared.com/file/hgBwR0rk/MvcMultiButtonSample.html http://www.codeproject.com/Articles/464178/Multiple-parameterized-localizable-form-buttons-in
-
Ai dreptate man , sorry , ii las aici un tutorial cum sa isi faca un botnet - https://rstcenter.com/forum/58767-build-your-own-botnet.rst
-
Sper sa te ajute : https://rstcenter.com/forum/58712-emulare-dos.rst
-
Introduction Modifying .NET methods' MSIL codes during run-time is very cool, it helps to implement hooking, software protection, and other amazing stuff. That's why I want it, but there is a big challenge on the road -- the MSIL code could have been complied to native code by JIT-complier before we have a chance to modify them; also the .NET CLR implantation is not documented and it changes during each version, we need a reliable and stable way without dependency to the exact memory layout. Anyway, after more than one week research, finally I made it! Here is a simple method in the demo problem: protected string CompareOneAndTwo() { int a = 1; int b = 2; if (a < { return "Number 1 is less than 2"; } else { return "Number 1 is greater than 2 (O_o)"; } } Certainly it returns "Number 1 is less than 2"; let's try to make it return the incorrect result "Number 1 is greater than 2 (O_o)". Looking at the MSIL code for this method, we can do it by changing the opcode from Bge_S to Blt_S. And then the jump works in a different logic which returns in a wrong result, that is what I need. And if you try in the demo application, it shows a wrong answer as below. Here is the code replacing the IL, I assume there are enough comments between the lines. You can download the demo program and have a try. Supports variants of .NET versions from 2.0 to 4.0 Supports variants of methods to be modified, including dynamic methods and generic methods. Supports release mode .NET process. Using the code public static class InjectionHelper { // Load the unmanaged injection.dll, the initlaization happens in a background thread // you can check if the initialization is completed by GetStatus() public static void Initialize() // Unload the unmanaged injection.dll public static void Uninitialize() // Update the IL Code of a Method. public static void UpdateILCodes(MethodInfo method, byte[] ilCodes) // The method returns until the initialization is completed public static Status WaitForIntializationCompletion() // Query the current status of the unmanaged dll, returns immediately. public static Status GetStatus() } The InjectionHelper.Initialize method loads the unmanaged injection.dll from the directory of the current assembly directory, so all the related files need to be there, or you can modify the code to change the location. Here is the file list. Background Replace the IL code First, take a look at how the CLR and JIT works. The JIT implementation DLLs (clrjit.dll for .Net4.0+ / mscorjit.dll for .NET 2.0+) expose a _stdcall method getJit, which returns the ICorJitCompiler interface. The CLR implementation DLLs (clr.dll for .NET 4.0+ / mscorwks.dll for .NET 2.0+) invokes the getJit method to obtain the ICorJitCompiler interface, then calls its compileMethod method to compile MSIL code to native code. CorJitResult compileMethod(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode); This part is quite easy, just find the location of the compileMethod method, replace the entry via EasyHook - The reinvention of Windows API Hooking // define the interface method function pointer typedef CorJitResult (__stdcall ICorJitCompiler::*PFN_compileMethod)(ICorJitInfo * pJitInfo , CORINFO_METHOD_INFO * pMethodInfo , UINT nFlags , LPBYTE * pEntryAddress , ULONG * pSizeOfCode ); // store the address of the real compileMethod PFN_compileMethod_V4 s_pComplieMethod = ...; // hook the compileMethod with my own compileMethod LhInstallHook( (PVOID&)s_pComplieMethod , &(PVOID&)CInjection::compileMethod , NULL , &s_hHookCompileMethod ); // and here is my compileMethod CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo , CORINFO_METHOD_INFO * pCorMethodInfo , UINT nFlags , LPBYTE * pEntryAddress , ULONG * pSizeOfCode ) { // TO DO: modify IL codes here // Call real compileMethod CorJitResult result = (pCorJitCompiler->*s_pComplieMethod_V4)( pJitInfo, pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode); return result; } Modify IL code for JIT-complied methods Now we are here, the compileMethod method above won't be called by CLR for the JIT-compiled method. To solve this problem, my idea is to restore the data structures in CLR to the previous status before JIT-compliation. And in this case, complileMethod will be called again and we can replace the IL. Thus we have to look into the implementation of CLR a bit, SSCLI Download: Shared Source Common Language Infrastructure 2.0 Release - Microsoft Download Center - Download Details is a good reference from Microsoft although it is quite out of date and we can't use it in our code. The above diagram is a bit out of date, but the primary structure is the same. For each "class" in .NET, there is at least one MethodTable structure in memory. And each MethodTable is related to a EEClass, which stores the runtime type information for Reflection and other use. For each "method", there is a MethodDesc data structure in memory containing the information of this method like flags / slot address / entry address / etc. Before a method is JITted-complied, the slot is pointed to a JMI thunk (prestub), which triggers JIT compliation; when the IL code is complied, the slot is rewritten to point to the JMI thunk, which jumps to complied native code directly. To restore the data structure, first clear the flags, then modify the entry address back to a temporary entry address, and so on. I successfully did that in the debugger by modifying the memory directly. But this is messy, it depends on the layout of the data structures, and the code is unreliable for different versions of .NET. Luckily, I found the method MethodDesc::Reset in SSCLI doing the same job. void MethodDesc::Reset() { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END // This method is not thread-safe since we are updating // different pieces of data non-atomically. // Use this only if you can guarantee thread-safety somehow. _ASSERTE(IsEnCMethod() || // The process is frozen by the debugger IsDynamicMethod() || // These are used in a very restricted way GetLoaderModule()->IsReflection()); // Rental methods // Reset any flags relevant to the old code ClearFlagsOnUpdate(); if (HasPrecode()) { GetPrecode()->Reset(); } else { // We should go here only for the rental methods _ASSERTE(GetLoaderModule()->IsReflection()); InterlockedUpdateFlags2(enum_flag2_HasStableEntryPoint | enum_flag2_HasPrecode, FALSE); *GetAddrOfSlotUnchecked() = GetTemporaryEntryPoint(); } _ASSERTE(!HasNativeCode()); } As you can see above, it is doing the same thing for me. Hence the target is changed to invoke this method to reset the MethodDesc status to pre-JITted. Certainly I can't use the MethodDesc from SSCLI, and the MethodDesc is internal used by MS, whose exact implementation and layout are unknown to everyone except Microsoft. After endless mountains and rivers that leave doubt whether there is a path out, suddenly one encounters the shade of a willow, bright flowers, and a lovely village. Yes, luckily this internal method exists in the PDB symbol from Microsoft Symbol Server, and this solves my problem. The Reset() method's address in the CLR DLL can be known by parsing the PDB file. Now only one mandatory parameter is left -- the this pointer of MethodDesc. It is not hard to obtain this pointer. Actually MethodBase.MethodHandle.Value == CORINFO_METHOD_HANDLE == MethodDesc address == this pointer of MethodDesc . Thus, I have my MethodDesc class below defined in unmanaged code. typedef void (MethodDesc::*PFN_Reset)(void); typedef BOOL (MethodDesc::*PFN_IsGenericMethodDefinition)(void); typedef ULONG (MethodDesc::*PFN_GetNumGenericMethodArgs)(void); typedef MethodDesc * (MethodDesc::*PFN_StripMethodInstantiation)(void); typedef BOOL (MethodDesc::*PFN_HasClassOrMethodInstantiation)(void); typedef BOOL (MethodDesc::*PFN_ContainsGenericVariables)(void); typedef DictionaryLayout * (MethodDesc::*PFN_GetDictionaryLayout)(void); typedef Dictionary * (MethodDesc::*PFN_GetMethodDictionary)(void); typedef MethodDesc * (MethodDesc::*PFN_GetWrappedMethodDesc)(void); class MethodDesc { public: void Reset(void) { (this->*s_pfnReset)(); } BOOL IsGenericMethodDefinition(void) { return (this->*s_pfnIsGenericMethodDefinition)(); } ULONG GetNumGenericMethodArgs(void) { return (this->*s_pfnGetNumGenericMethodArgs)(); } MethodDesc * StripMethodInstantiation(void) { return (this->*s_pfnStripMethodInstantiation)(); } BOOL HasClassOrMethodInstantiation(void) { return (this->*s_pfnHasClassOrMethodInstantiation)(); } BOOL ContainsGenericVariables(void) { return (this->*s_pfnContainsGenericVariables)(); } DictionaryLayout * GetDictionaryLayout(void) { return (this->*s_pfnGetDictionaryLayout)(); } Dictionary * GetMethodDictionary(void) { return (this->*s_pfnGetMethodDictionary)(); } MethodDesc * GetWrappedMethodDesc(void) { return (this->*s_pfnGetWrappedMethodDesc)(); } private: static PFN_Reset s_pfnReset; static PFN_IsGenericMethodDefinition s_pfnIsGenericMethodDefinition; static PFN_GetNumGenericMethodArgs s_pfnGetNumGenericMethodArgs; static PFN_StripMethodInstantiation s_pfnStripMethodInstantiation; static PFN_HasClassOrMethodInstantiation s_pfnHasClassOrMethodInstantiation; static PFN_ContainsGenericVariables s_pfnContainsGenericVariables; static PFN_GetDictionaryLayout s_pfnGetDictionaryLayout; static PFN_GetMethodDictionary s_pfnGetMethodDictionary; static PFN_GetWrappedMethodDesc s_pfnGetWrappedMethodDesc; }; The static variables above store the addresses of the internal methods from the MethodDesc implementation from the CLR DLL. And they are initialized when my unmanaged DLL is loaded. And the public members just call the internal method with the this pointer. Now it becomes quite easy to invoke Microsoft's internal methods. Like: MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle; pMethodDesc->Reset(); Find internal methods' addresses from the PDB Symbol file When the unmanaged DLL is loaded, it checks the environment to see which version of CLR/JIT is there. And it tries to seek the address for all the internal methods from the PDB file. If the seek fails, it will try to launch symchk.exe from Windows Debug Tools to download the corresponding PDB symbol files from Microsoft Symbol Server. This procedure requires a long time, from several seconds to several minutes. Maybe we can optimize to cache the address of the CLR/JIT DLLs by calculating their binary hash value. You can see more details in the source code, the SearchMethodAddresses and Intialize methods from the unmanaged DLL. Reset the MethodDesc to pre-JITted status Now everything is ready. The unmanaged DLL exports a method for managed codes, accepts the IL codes and MethodBase.MethodHandle.Value from the managed code. BOOL CInjection::StartUpdateILCodes( MethodTable * pMethodTable , CORINFO_METHOD_HANDLE pMethodHandle , LPBYTE pBuffer , DWORD dwSize ) { if( s_nStatus != Status_Ready || !pMethodHandle ) return FALSE; MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle; pMethodDesc->Reset(); MethodDesc * pStripMethodDesc = pMethodDesc->StripMethodInstantiation(); if( pStripMethodDesc ) pStripMethodDesc->Reset(); // this is a generic method if( pMethodDesc->HasClassOrMethodInstantiation() ) { MethodDesc * pWrappedMethodDesc = pMethodDesc->GetWrappedMethodDesc(); if( pWrappedMethodDesc ) { pWrappedMethodDesc->Reset(); } } std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = s_mpILBuffers.find(pMethodHandle); if( iter != s_mpILBuffers.end() ) { LocalFree(iter->second.pBuffer); s_mpILBuffers.erase(iter); } ILCodeBuffer tILCodeBuffer = { pBuffer, dwSize }; s_mpILBuffers[pMethodHandle] = tILCodeBuffer; return TRUE; } The code above just calls the Reset() method, and stores the IL codes in a map, which will be used by complieMethod when the method gets complied. And in complieMethod, just replace the ILCode, with code like below. CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo , CORINFO_METHOD_INFO * pCorMethodInfo , UINT nFlags , LPBYTE * pEntryAddress , ULONG * pSizeOfCode ) { ICorJitCompiler * pCorJitCompiler = (ICorJitCompiler *)this; LPBYTE pOriginalILCode = pCorMethodInfo->ILCode; unsigned int nOriginalSize = pCorMethodInfo->ILCodeSize; // find the method to be replaced std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = s_mpILBuffers.end(); if( pCorMethodInfo && GetStatus() == Status_Ready ) { MethodDesc * pMethodDesc = (MethodDesc*)pCorMethodInfo->ftn; std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = s_mpILBuffers.find((CORINFO_METHOD_HANDLE)pMethodDesc); // if the current method is not found, try to search its generic definition method if( iter == s_mpILBuffers.end() && pMethodDesc->HasClassOrMethodInstantiation() ) { pMethodDesc = pMethodDesc->StripMethodInstantiation(); iter = s_mpILBuffers.find((CORINFO_METHOD_HANDLE)pMethodDesc); } if( iter != s_mpILBuffers.end() ) { pCorMethodInfo->ILCode = iter->second.pBuffer; pCorMethodInfo->ILCodeSize = iter->second.dwSize; } } CorJitResult result = (pCorJitCompiler->*s_pComplieMethod_V4)( pJitInfo, pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode); if( iter != s_mpILBuffers.end() ) { pCorMethodInfo->ILCode = pOriginalILCode; pCorMethodInfo->ILCodeSize = nOriginalSize; LocalFree(iter->second.pBuffer); s_mpILBuffers.erase(iter); } return result; } Points of interest Compilation optimizations I found that if the method is too simple and the IL codes are only several bytes (like the attributes), the method will be complied as inline mode. And in this case, Reset MethodDesc does not help anything because the execution even doesn't reach there. Dynamic method To update the IL code of a dynamic method we need to be very careful. Filling incorrect IL code for other kinds of methods only causes an InvalidApplicationException; but incorrect IL code in a dynamic method can crash the CLR and the whole process! And IL code for a dynamic method is different from that for others. Better to generate the IL code from another dynamic method and then copy and update. Generic method I think this is the most complicated part. A generic definition method is mapped to a MethodDesc. But calling the generic method with different types of parameters will cause the CLR to create a series of instantiations of the definition method. Even more, different kinds of generic methods are implemented in different ways. shared generic method instantiations unshared generic method instantiations instance methods in shared generic classes instance methods in unshared generic classes static methods in shared generic classes static methods in unshared generic classes I didn't find a good way to get all the instantiations for a generic definition, so the code in the demo looks like: MethodInfo destMethodInfo = type.GetMethod("GenericMethodToBeReplaced", BindingFlags.NonPublic | BindingFlags.Instance); // reset the generic definition MethodInfo InjectionHelper.UpdateILCodes(destMethodInfo, ilCodes); // reset a specific instantiation generic MethodInfo destMethodInfo = destMethodInfo.MakeGenericMethod(new Type[] { typeof(string), typeof(int) }); InjectionHelper.UpdateILCodes(destMethodInfo, ilCodes); The above code only works when you know the type parameter of the generic method. A JIT-complied generic method with different type parameters won't be affected. Download binaries : binaries_demo.zip download - 2shared Download unmanaged_dll_source : unmanaged_dll_source.zip download - 2shared http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-on-Run-time
-
Asta da la toti pentru vizionarea on-line ? Ce p*** mea au astia cu megaup-ul ?
-
Introduction Multi RDP Client .NET (formerly known as Multi Remote Desktop Client .NET) comes on handy when managing your RDP connections. A simple and friendly user interface for you to work and navigate easily through tabbed RDP remote window. Allows you to Import/Export .RDP files, Disconnect all connection at one click and Connect all your servers at one click as well. You can also change the resolution while working, set the window into fullscreen mode, and as well as enable window stretching and a lot more features! Background Thanks to AxInterop.MSTSCLib (mstscax.dll) an ActiveX COM Component which you can use to connect on Remote Desktop. So, I made a GUI for it for you to connect on your servers via terminal service on multiple window view in one application. How to use MSTSCAX.DLL? You must have the ActiveX file in your system called "mstscax.dll". if not, then you can Google for the file and download it, then make sure you registered it using "RegSvr32 ". I think that's one of the IIS package if you installed the Remote Desktop Web Connection. On the UI Design mode in VS2005 or 8. go to your toolbox and Open Choose Toolbox Items and look for Microsoft RDP Client Control in COM Components tab. I'm guessing you successfully added that control in your toolbar. So I don't think I have to explain how to get that in your Form. Screenshots Server list views Server panel which lists the RDP servers in group name view. It can also be shown in different views such as: Tree, Small lists, and Large lists. Collapse/Expand Server panel can also be resize its width, expand, and collapse so you can have more space for you RDP window. Group Manager A Group manager can be used to manage the type of servers. Import RDP File One main feature of MRDC.NET is you can import the RDP files created on default Remote Desktop Connection Windows client Export RDP File Configuration Configuration window. Here you can setup your startup password and UI display About Features Command Line Parameters - requested by: Simon Capewell /sname "server name" Can open a new client window by providing a Server Name /gname "group name" Can open a multiple client window by specifying the Group Name Add, Edit, Delete Servers Those words say it all. Server Settings Window the Server Settings window has the following: General Tab Server Name You can give your connection name Server The host address or IP address Custom Port - requested by: Simon Capewell Allowing the RDP to connect to different port. Username Your RD server Username Password Your RD server Password Description Your Server description Group You can choose the group of your Server. You can also open Group Manager from there Display Tab Desktop Sizes - some resolutions were requested by: Simon Capewell 640 x 480 800 x 600 1024 x 768 1120 x 700 1152 x 864 1280 x 800 1280 x 1024 Custom Desktop Height and Width Fullscreen Colors True Color (24 bit) High Color (16 bit) High Color (15 bit) 256 Colors Configuration Window General Tab Password Your Startup Password Display Tab Hide when Minimize If Enabled, the window hides and it self and you bring it up anytime by double clicking on notification icon in system tray. If Disabled, the window just minimized and accessible in taskbar. Notification Window If Enabled, Notification window pops up everytime you hover into some controls. if Disabled, You know what that means .. Import/Export Import from RDP file format You can import your current RDP files Note: Currently, the password made by MSTSC cannot be decrypted. I Still have to work on this Export to RDP file format You can export the servers in RDP file format Group Manager - requested by: Simon Capewell Manages the server groups Database I implemented SQLite3 - ADO.NET instead of working with XML and can be very time-consuming. Notification Icon This icon sits on your system tray area and you can right click on it to show the context menu or double click to bring up the window when minimized or hidden. The context menu items contains: Servers - And under it contains the Groups and the Servers Disconnect All Configuration Lock Exit Lock Application For safety purposes, a Lock feature is added to lock the current application and the Password Window will show up after locking the application. Startup Password - requested by: shmulyeng For safety purposes, a Password feature is added before opening the application. After entering 3 invalid passwords, CAPTCHA verification will show up in Password Window Server Lists Panel You can change the views in Server Lists panel in Detailed, Tiled, and Tree Context Menu in Server Lists Panel - requested by: Simon Capewell and shmulyeng Context Menu will popup after right clicking on the items and you can Add, Edit, Delete, and Group Connect. Collapse/Expand Server Lists Panel - requested by: Simon Capewell You can collapse or expand the server lists panel. Groups - requested by: Simon Capewell Servers are arranged by Groups Different Views - requested by: Simon Capewell You can set the Server lists view by Detailed, Tiled, and Tree MDI Tabs - requested by: shmulyeng Of course, Tabs can be very helpfull when selecting client windows Disconnect All Disconnects all connected RD clients. Client Window Connect, Disconnect, and Reconnect: This Connects, Disconnects, or Reconnects your RD connection. Fullscreen: Sets the RD connection to Fullscreen. It can also ask you to resize the Resolution based on your Desktop Resolution Fit To Window: The RD resolution can change based on the RD client window Fit to Window Stretch Settings: Opens the server settings Info Popup Window This window automatically popups when hovered on the control showing a description of what the control can do. Credits I do give a lot of credits to the People who shared their Ideas, Custom Control, and Codes. Credits can be found in my About Window Code Snippets // for example, I have my AxMsRdpClient control named rdpClient. rdpClient.Server = "sever name here"; rdpClient.UserName = "your username on remote pc"; rdpClient.AdvancedSettings2.ClearTextPassword = "you password on remote pc"; // optional rdpClient.ColorDepth = 16; // int value can be 8, 15, 16, or 24 rdpClient.DesktopWidth = 1024; // int value rdpClient.DesktopHeight = 768; // int value rdpClient.FullScreen = true|false; // boolean value that can be True or False // and connect rdpClient.Connect(); The code above is the simplest way of using the component Setting full screen in runtime is fairly simple // just set the Fullsceen property to TRUE rdpClient.Fullscreen = true; // strecth the screen rdpClient.AdvanceSettings3.SmartSizing = true; Points of interest about connection and disconnection I implemented a Reconnect feature. But its not easy as calling Disconnect() and Connect(). You should wait until it properly disconnected and call Connect(). In AxMsRdpClient, there is a Connect property which acts as a Connection Status: 1 = Connected 0 = Disconnected Now reconnecting can be done by doing this: // call Disconnect() method on AxMsRdpClient rdpClient.Disconnect(); // wait for the server to properly disconnect while (rdpClient.Connected != 0) { System.Threading.Thread.Sleep(1000); Application.DoEvents(); } // call Connect() method on AxMsRdpClient You can always implement your own way of reconnecting, the code above is just the very basic, I do not recommend you to use that. Here's my RDPFile Reader class /* Author: Jayson Ragasa Application Developer * --- * RDPFileReader 1.0 * * RDP File Settings - http://dev.remotenetworktechnology.com/ts/rdpfile.htm * Terminal Services Team Blog - http://blogs.msdn.com/ts/archive/2008/09/02/specifying-the-ts-client-start-location-on-the-virtual-desktop.aspx */ using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.IO; namespace RDPFileReader { public class RDPFile { #region enum public enum KeyboardHooks { ON_THE_LOCAL_COMPUTER = 0, ON_THE_REMOTE_COMPUTER = 1, IN_FULL_SCREEN_MODE_ONLY = 2 }; public enum AudioModes { BRING_TO_THIS_COMPUTER = 0, DO_NOT_PLAY = 1, LeAVE_AT_REMOTE_COMOPUTER = 2 }; public enum WindowState : int { NORMAL = 1, MAXMIZE = 3 } public enum SessionBPPs { BPP_8 = 8, BPP_15 = 15, BPP_16 = 16, BPP_24 = 24 } #endregion #region structs public struct RECT { public int Top; public int Left; public int Width; public int Height; } public struct WindowsPosition { public WindowState WinState; public RECT Rect; } #endregion #region variables private string _filename = string.Empty; #region RDP template string[] _rdpTemplate = { "screen mode id:i:{0}", "desktopwidth:i:{1}", "desktopheight:i:{2}", "session bpp:i:{3}", "winposstr:s:0,{4},{5},{6},{7},{8}", "full address:s:{9}", "compression:i:{10}", "keyboardhook:i:{11}", "audiomode:i:{12}", "redirectdrives:i:{13}", "redirectprinters:i:{14}", "redirectcomports:i:{15}", "redirectsmartcards:i:{16}", "displayconnectionbar:i:{17}", "autoreconnection enabled:i:{18}", "username:s:{19}", "domain:s:{20}", "alternate shell:s:{21}", "shell working directory:s:{22}", "password 51:b:{23}", "disable wallpaper:i:{24}", "disable full window drag:i:{25}", "disable menu anims:i:{26}", "disable themes:i:{27}", "disable cursor setting:i:{28}", "bitmapcachepersistenable:i:{29}" }; #endregion #region member fields int _screenMode = 0; int _desktopWidth = 0; int _desktopHeight = 0; SessionBPPs _sessionBPP = 0; WindowsPosition _winPosStr; string _fullAddress = string.Empty; int _compression = 0; KeyboardHooks _keyboardHook = 0; AudioModes _audiomode = 0; int _redirectDrives = 0; int _redirectPrinters = 0; int _redirectComPorts = 0; int _redirectSmartCards = 0; int _displayConnectionBar = 0; int _autoReconnectionEnabled = 0; string _username = string.Empty; string _domain = string.Empty; string _alternateShell = string.Empty; string _shellWorkingDirectory = string.Empty; string _password = string.Empty; int _disableWallpaper = 0; int _disableFullWindowDrag = 0; int _disableMenuAnims = 0; int _disableThemes = 0; int _disableCursorSettings = 0; int _bitmapCachePersistEnable = 0; #endregion #endregion #region properties public int ScreenMode { get { return this._screenMode; } set { this._screenMode = value; } } public int DesktopWidth { get { return this._desktopWidth; } set { this._desktopWidth = value; } } public int DesktopHeight { get { return this._desktopHeight; } set { this._desktopHeight = value; } } public SessionBPPs SessionBPP { get { return this._sessionBPP; } set { this._sessionBPP = value; } } public WindowsPosition WinPosStr { get { return this._winPosStr; } set { this._winPosStr = value; } } public string FullAddress { get { return this._fullAddress; } set { this._fullAddress = value; } } public int Compression { get { return this._compression; } set { this._compression = value; } } public KeyboardHooks KeyboardHook { get { return this._keyboardHook; } set { this._keyboardHook = value; } } public AudioModes AudioMode { get { return this._audiomode; } set { this._audiomode = value; } } public int RedirectDrives { get { return this._redirectDrives; } set { this._redirectDrives = value; } } public int RedirectPrinters { get { return this._redirectPrinters; } set { this._redirectPrinters = value; } } public int RedirectComPorts { get { return this._redirectComPorts; } set { this._redirectComPorts = value; } } public int RedirectSmartCards { get { return this._redirectSmartCards; } set { this._redirectSmartCards = value; } } public int DisplayConnectionBar { get { return this._displayConnectionBar; } set { this._displayConnectionBar = value; } } public int AutoReconnectionEnabled { get { return this._autoReconnectionEnabled; } set { this._autoReconnectionEnabled = value; } } public string Username { get { return this._username; } set { this._username = value; } } public string Domain { get { return this._domain; } set { this._domain = value; } } public string AlternateShell { get { return this._alternateShell; } set { this._alternateShell = value; } } public string ShellWorkingDirectory { get { return this._shellWorkingDirectory; } set { this._shellWorkingDirectory = value; } } public string Password { get { return this._password; } set { this._password = value; } } public int DisableWallpaper { get { return this._disableWallpaper; } set { this._disableWallpaper = value; } } public int DisableFullWindowDrag { get { return this._disableFullWindowDrag; } set { this._disableFullWindowDrag = value; } } public int DisableMenuAnims { get { return this._disableMenuAnims; } set { this._disableMenuAnims = value; } } public int DisableThemes { get { return this._disableThemes; } set { this._disableThemes = value; } } public int DisableCursorSettings { get { return this._disableCursorSettings; } set { this._displayConnectionBar = value; } } public int BitmapCachePersistEnable { get { return this._bitmapCachePersistEnable; } set { this._bitmapCachePersistEnable = value; } } #endregion #region methods public void Read(string filepath) { this._filename = filepath; string data = string.Empty; using (StreamReader reader = new StreamReader(filepath)) { data = reader.ReadToEnd(); } string[] settings = data.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string thisSetting in settings) { string regex = "(?<type>.*)\\?<dtype>\\w)\\?<value>.*)"; RegexOptions options = ((RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline) | RegexOptions.IgnoreCase); Regex reg = new Regex(regex, options); if (reg.IsMatch(thisSetting)) { Match m = reg.Match(thisSetting); string v = m.Groups["value"].Value; switch (m.Groups["type"].Value) { case "screen mode id": this._screenMode = int.Parse(v); break; case "desktopwidth": this._desktopWidth = int.Parse(v); break; case "desktopheight": this._desktopHeight = int.Parse(v); break; case "session bpp": this._sessionBPP = (SessionBPPs)int.Parse(v); break; case "winposstr": string[] vals = v.Split(','); this._winPosStr.WinState = (WindowState)int.Parse(vals[1]); this._winPosStr.Rect.Top = int.Parse(vals[2]); this._winPosStr.Rect.Left = int.Parse(vals[3]); this._winPosStr.Rect.Width = int.Parse(vals[4]); this._winPosStr.Rect.Height = int.Parse(vals[5]); break; case "full address": this._fullAddress = v; break; case "compression": this._compression = int.Parse(v); break; case "keyboardhook": this._keyboardHook = (KeyboardHooks)int.Parse(v); break; case "audiomode": this._audiomode = (AudioModes)int.Parse(v); break; case "redirectdrives": this._redirectDrives = int.Parse(v); break; case "redirectprinters": this._redirectPrinters = int.Parse(v); break; case "redirectcomports": this._redirectComPorts = int.Parse(v); break; case "redirectsmartcards": this._redirectSmartCards = int.Parse(v); break; case "displayconnectionbar": this._displayConnectionBar = int.Parse(v); break; case "autoreconnection enabled": this._autoReconnectionEnabled = int.Parse(v); break; case "username": this._username = v; break; case "domain": this._domain = v; break; case "alternate shell": this._alternateShell = v; break; case "shell working directory": this._shellWorkingDirectory = v; break; case "password 51": this._password = v; break; case "disable wallpaper": this._disableWallpaper = int.Parse(v); break; case "disable full window drag": this._disableFullWindowDrag = int.Parse(v); break; case "disable menu anims": this._disableMenuAnims = int.Parse(v); break; case "disable themes": this._disableThemes = int.Parse(v); break; case "disable cursor setting": this._disableCursorSettings = int.Parse(v); break; case "bitmapcachepersistenable": this._bitmapCachePersistEnable = int.Parse(v); break; } } } } public void Update() { Save(this._filename); } public void Save(string filepath) { this._filename = filepath; string template = string.Empty; foreach (string temp in this._rdpTemplate) { template += temp + "\r\n"; } string data = string.Format(template, this._screenMode, this._desktopWidth, this._desktopHeight, (int)this._sessionBPP, (int)this._winPosStr.WinState, this._winPosStr.Rect.Top, this._winPosStr.Rect.Left, this._winPosStr.Rect.Width, this._winPosStr.Rect.Height, this._fullAddress, this._compression, (int)this._keyboardHook, (int)this._audiomode, this._redirectDrives, this._redirectPrinters, this._redirectComPorts, this._redirectSmartCards, this._displayConnectionBar, this._autoReconnectionEnabled, this._username, this._domain, this._alternateShell, this._shellWorkingDirectory, this._password, this._disableWallpaper, this._disableFullWindowDrag, this._disableMenuAnims, this._disableThemes, this._disableCursorSettings, this._bitmapCachePersistEnable ); using (StreamWriter writer = new StreamWriter(filepath)) { writer.Write(data); } } #endregion } } We also have DataProtection class which was provided by Microsoft and a little bit of modification and some methods to implement such as converting Byte[] < to > Hex blob. We could create a MSTSC valid password. Reference: Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication http://msdn.microsoft.com/en-us/library/aa302402.aspx#secnetht07_topic4 Implementing CryptProtectData and CryptUnprotectData from Crypt32.DLL Remko Weijnen - "psw" descriptor on CryptProtectData http://www.remkoweijnen.nl/blog/2007/10/18/how-rdp-passwords-are-encrypted/ Now I have created a wrapper called DataProtectionForRDPWrapper to easily Encrypt and Decrypt RDP password CREATED in DataProtection class. Why did I emphasize the "created" word. Some limitation on DataProtection class is, we can't decrypt the password created by MSTSC and still under research. DataProtectionForRDPWrapper class /* * Author: Jayson Ragasa * Application Developer * * -- * Made a wrapper for DataProtector so I could * Encrypt/Decrypt valid password for RDP * * TAKE NOTE: * This can't Decrypt MSTSC Password! */ using System; using System.Collections.Generic; using System.Text; namespace DataProtection { public class DataProtectionForRDPWrapper { static DataProtection.DataProtector dp = new DataProtector(DataProtector.Store.USE_USER_STORE); public static string Encrypt(string text_password) { byte[] e = dp.Encrypt(GetBytes(text_password), null, "psw"); return GetHex(e); } public static string Decrypt(string enc_password) { byte[] b = ToByteArray(enc_password); byte[] d = dp.Decrypt(b, null, "psw"); return GetString(d); } static byte[] GetBytes(string text) { return UnicodeEncoding.Unicode.GetBytes(text); } static string GetString(byte[] byt) { System.Text.Encoding enc = System.Text.Encoding.Unicode; return enc.GetString(byt); } static string GetHex(byte[] byt_text) { string ret = string.Empty; for (int i = 0; i < byt_text.Length; i++) { ret += Convert.ToString(byt_text[i], 16).PadLeft(2, '0').ToUpper(); } return ret; } static byte[] ToByteArray(String HexString) { try { int NumberChars = HexString.Length; byte[] bytes = new byte[NumberChars / 2]; for (int i = 0; i < NumberChars; i += 2) { bytes[i / 2] = Convert.ToByte(HexString.Substring(i, 2), 16); } return bytes; } catch (Exception ex) { // this occures everytime we decrypt MSTSC generated password. // so let's just throw an exception for now throw new Exception("Problem converting Hex to Bytes", ex); } } } } Using RDPFile reader // RDP Key descriptions found at // http://dev.remotenetworktechnology.com/ts/rdpfile.htm RDPFile rdp = new RDPFile(); // we wan't the window to be on a Maximize state // 1 - windowed // 2 - fullscreen rdp.ScreenMode = 1; // remote desktop resolution rdp.DesktopWidth = 1024; rdp.DesktopHeight = 768; /* remote desktop color depth public enum SessionBPPs { BPP_8 = 8, BPP_15 = 15, BPP_16 = 16, BPP_24 = 24 } */ rdp.SessionBPP = SessionBPPs.BPP_16 /* how the window will look? Terminal Services Team Blog explained the "winposstr" key! The location on the virtual desktop where the TS Client initially positions itself can be controlled via the winposstr setting in the RDP file winposstr:s:0,ShowCmd,Left,Top,Right,Bottom */ RDPFile.WindowsPosition winpos = new RDPFile.WindowsPosition(); { RDPFile.RECT r = new RDPFile.RECT() { Top = 0; Left = 0; Width = ss.DesktopWidth; Height = ss.DesktopHeight; } winpos.Rect = r; } /* this is equal to ShowCmd from Terminal Services Team Blog public enum WindowState : int { NORMAL = 1, MAXMIZE = 3 } */ winpos.WinState = RDPFile.WindowState.MAXMIZE; rdp.WinPosStr = winpos; // set all our winposstr from the obove configuration rdp.FullAddress = "192.168.1.1" // your server name or ip address; rdp.Compression = 1; // RemoteNetworkTechnology didn't fully explanied this // but looks like this is needed for faster data transfer /* For applying standard key combinations public enum KeyboardHooks { ON_THE_LOCAL_COMPUTER = 0, ON_THE_REMOTE_COMPUTER = 1, IN_FULL_SCREEN_MODE_ONLY = 2 }; */ rdp.KeyboardHook = RDPFile.KeyboardHooks.ON_THE_REMOTE_COMPUTER; /* How will the audio from the remote pc be played public enum AudioModes { BRING_TO_THIS_COMPUTER = 0, DO_NOT_PLAY = 1, LeAVE_AT_REMOTE_COMOPUTER = 2 }; */ rdp.AudioMode = RDPFile.AudioModes.BRING_TO_THIS_COMPUTER; rdp.RedirectDrives = 0; // should we share our local drives in the remote pc? rdp.RedirectPrinters = 0; // should we share our printers in the remote pc? rdp.RedirectComPorts = 0; // should we share our com ports in the remoe pc? rdp.RedirectSmartCards = 0; // should we share our smart cards in the remote pc? rdp.DisplayConnectionBar = 1; // will the Connection bar visible when in Fullscreen mode? rdp.AutoReconnectionEnabled = 1; // do we need to automatically connect? rdp.Username = "Admin"; // remote pc Username rdp.Domain = "DomainName"; // remote pc Domain rdp.AlternateShell = string.Empty; // are we going to use different shell other than C:\Windows\Explorer.exe? rdp.ShellWorkingDirectory = string.Empty; // Working directory if an alternate shell was specified. // here's the password implementing our DataProtection and the wrapper rdp.Password = (ss.Password == string.Empty ? string.Empty : DataProtectionForRDPWrapper.Encrypt(ss.Password)); rdp.DisableWallpaper = 1; // should we disable wallpaper in the remote pc? rdp.DisableFullWindowDrag = 1; // should we disable the full window drag in the remote pc and just show the box while dragging? rdp.DisableMenuAnims = 1; // should we disable animations? rdp.DisableThemes = 1; // should we disable Windows Visual Themes? rdp.DisableCursorSettings = 1; // should we disable mouse cursor effects? rdp.BitmapCachePersistEnable = 1; // This setting determines whether bitmaps are cached on the local computer #region try exporting the file { try { rdp.Save(@"D:\My Documents\MyRDPConnection.RDP"); } catch (Exception ex) { MessageBox.Show("An error occured while saving the configuration for '" + rdp.FullAddress + "'.\r\n\r\nError Message: " + ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error); System.Diagnostics.Debug.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } } #endregion Reading .RDP files string thisFile = @"D:\My Documents\MyRDPConnection.RDP"; #region Read RDP File RDPFile rdpfile; { try { rdpfile = new RDPFile(); rdpfile.Read(thisFile); } catch (Exception ex) { MessageBox.Show("An error occured while reading '" + Path.GetFileName(thisFile) + "' and it will be skipped.\r\n\r\nError Message: " + ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error); System.Diagnostics.Debug.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } } #endregion Console.Writeline("RDP Username: " + rdpfile.Username); #region Try decrypting the password from RDP file { try { System.Diagnostics.Debug.WriteLine("reading password " + thisFile); Console.Writeline("RDP Password: " + DataProtectionForRDPWrapper.Decrypt(rdpfile.Password)); System.Diagnostics.Debug.WriteLine("reading password done"); } catch (Exception Ex) { ss.Password = string.Empty; if (Ex.Message == "Problem converting Hex to Bytes") { MessageBox.Show("This RDP File '" + Path.GetFileNameWithoutExtension(thisFile) + "' contains a secured password which is currently unsported by this " + "application.\r\nThe importing can still continue but without the password.\r\nYou " + "can edit the password later by selecting a server in 'All Listed Servers' " + "and click 'Edit Settings' button on the toolbar", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Asterisk); } else if (Ex.Message.Contains("Exception decrypting")) { MessageBox.Show("Failed to decrypt the password from '" + Path.GetFileNameWithoutExtension(thisFile) + "'", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } else { MessageBox.Show("An unknown error occured while decrypting the password from '" + Path.GetFileNameWithoutExtension(thisFile) + "'", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } #endregion Console.Writeline("RDP Desktop Width: " + rdpfile.DesktopWidth); Console.Writeline("RDP Desktop Height: " + rdpfile.DesktopHeight); Updating an .RDP file //RDP File Reader test RDPFileReader.RDPFile rdp = new RDPFileReader.RDPFile(); rdp.Read(@"D:\My Documents\RDP\Application Server (1120x700-16bitc).rdp"); Console.WriteLine(rdp.WinPosStr.WinState); // set new Window State // make window mode maxmize RDPFileReader.RDP.WindowsPosition wpos = new RDPFileReader.RDP.WindowsPosition() { Rect = rdp.WinPosStr.Rect; WinState = RDPFileReader.RDP.WindowState.MAXMIZE; // change the window state when in window mode. }; rdp.WinPosStr = wpos; rdp.SessionBPP = RDPFileReader.RDP.SessionBPPs.BPP_8; // change the color depth // and call Update rdp.Update(); Release History Full Release - April 11, 2009 ADD: - Added Hide/Show settings for Information popup window UPDATE: - Fixed Server Lists collapse bug when no MDI Child - Fixed LiveInfoWindow from stealing the focus. - Fixed database exception in Import Window. - Group Manager window, redesigned to fit on MRDC.NET layout. - Updated database schema. ---- Groups table uid is set to auto-increment. Full Release - April 4, 2009 UPDATE: - Fixed error when no password is set - Fixed database scheme - MSTSC Generated password can now be decrypted! - Fixed domain name, not saving Full Release / April 3 UPDATE: - Fixed stupid RDP Control Focus issue Full Release / April 2 NEW: - From the requests of Simon Capewell and shmulyeng - Simon Capewell Requests: --- DONE(03222009_1254)1. Allow connection to servers on a specific port using AdvancedSettings2.RDPPort --- DONE(03192009_0916)2. More screen resolutions on the slider - most obviously 1152x864 and 1280x1024. --- DONE(03202009_1341)3. Context menu on the list of servers. --- DONE(03222009_1254)4. Options for different icon sizes for the list of servers. --- DONE(03227009_0138)5. Groupings for servers - handy if you have lots of clients each of whom have multiple servers. You could launch sessions for all the items in the group. --- DONE(03212009_1321)6. Auto hide or view menu option to show/hide the sidebar (similar to Visual Studio). --- DONE(03192009_0200)7. Clicking Delete Server or Edit Settings when there are no servers throws an exception. --- Here's another one that's just occurred. I use Launchy to index a directory with a load of .rdp files in it, which means I can just type Ctrl+space then a portion of the server name and launch it. To do that with this tool it'd need to: --- DONE(03272009_1129)1. Be able to launch a connection from the built in server list from the command line. --- DONE(03272009_1129)2. Have a command line switch to open the connection in an existing instance of the app. --- DONE(03272009_1129)3. Perhaps even be able to launch connections from the command line using rdp files rather than the built in server list. - shmulyeng requests: --- DONE(03272009_0252)Ability to password protect the server list. --- DONE(03202009_1341)Ability to right click on a connection item in the list to edit connection details. --- (WILL TRY) Ability to log out of a connection rather than disconnect. --- DONE(03212009_1321)One thing I like about Palantir is the tabs of open connections. - and I added some - CAPTCHA verfication on startup password. --- When an incorrrect password is entered 3x, CAPTCH verification panel will show up. - SQLite3 --- scrap XML!! ADD: UPDATE: - The last release was pretty much stable so there's a small updates on the code and some bug fix. Alpha Release - 6, & 7 / March 19, 2009 NEW: - Application can now run in Windows Vista. - Fixed AX MSRDPClient reference ---- Thanks to Simon Capewell (http://www.codeproject.com/script/Membership/Profiles.aspx?mid=215) -------- RemoteDesktopClient.aspx?fid=1537072&df=90&mpp=25&noise=3&sort=Position&view=Quick&select=2969383#xx2969383xx MARCH 21 - LiveInformationBox 1.0 --- Is a class library that pops up whenever you hover on a specified control and shows some details about what the controls can do. --- The information shown on information box is an xml base. --- this is better than showing some information on a status strip - ListViewEx --- hooks some control (not just common control) to disable/enable if listview has no item or when a selectio is made on listview items. --- good for automating controls instead of doing tons of lines just to disable/enable different controls. MARCH 25 - Am using SQLite3 now instead of XML! ---- it's taking too much time to maintain ADD: UPDATE: MARCH 20 - Finally it's working on both OS, thanks to that AxImp and thanks to @unruledboy MARCH 23 - LiveInformationBox -- now supports toolbar buttons - Server Lists -- now collapsable -- can change icon views -- context menu - For RDP client window -- on main form, now contains tab - Settings window -- added more resolution - ListViewEx for Server Lists -- control hooks so it can be easily Enable/Disable the controls when something's changed on the listview - crashes when clicking Edit Settings and Delete when no items on the Server Lists -- fixes the locking issue on rdp control when lossing its focus - Settings window -- Connection Port MARCH 25 - LiveInformationBox ---- fixed the UI ---- still having some problem on window focus. Alpha Release - 5 / March 17, 2009 NEW: - Import / Export RDP file format - Can delete selected server in "All Server List" panel. - Added new About Window ADD: UPDATE: - Fixed and Improved Fit To Window feature - Fixed Saving, Importing, and Exporting password handling. Alpha Release - 2, 3, & 4 / March 7, 2009 NEW: - ServerList reader moved to a separate Class Library ---- Custom Exception Handler - TextboxRequiredWrapper moved to a separate Class Library MARCH 8 - Fixed updating settings bug! MARCH 8 - ServerSettingsWindow ---- added new button("Get client window size"): Set the Desktop Width and Height size base on rdp client window size. MARCH 9 - Fixed Icon status on Server List if multiple windows are openned. ---- not sure why I didn't noticed that. MARCH 12 - RDP File Reader - Used to Read/Create/Update RDP Files - DataProtection - Used to Encrypt/Decrypt RDP Passwords and create valid RDP Passwords. ADD: MARCH 9 - Added new button on main toolbar called "Disconnect All" ---- which disconnects all connected rdp client. UPDATED: - saving a value on "dummy" attribute is now MD5 string - duplicate check while creating a New or Updating a Connection Settings. MARCH 8 - Updated xml configuration template ---- added UID element ---- dummy and uid values are now DateTime(MMddyyhhmmss) format - Password is now encrypted using Rijndael Encryption/Decryption MARCH 8 - Current configuration on RDP Client Window now updates - Server list now update after settings are changed on RDP Client Window MARCH 8 - Settings can be applied on Server Lists or in Rdp Client Window. Alpha Release - 1 / March 6, 2009 First release in CodePlex.coma Download it : http://www.2shared.com/file/Dlwoo6pJ/release_04112009_105500.html Download RDP password encryper - decrypter : http://www.2shared.com/file/e3Wl5q_n/RDPPassword_EncryptorDecryptor.html Download source code : http://www.2shared.com/file/lxuRWjNB/SURSAmultirdpc-71632.html http://www.codeproject.com/Articles/33979/Multi-RDP-Client-NET
-
Introduction The Token Manager allows web developers to program PHP, HTML, CSS and JavaScript into tokens that can be used throughout WordPress. Tokens can be placed into themes, templates, pages, widgets, etc. The code in the token is processed and then injected into the token name. It allows web developers to package their code into components for easy reuse and maintenance while speeding up development time and distribution. The tokens are also aware of WordPress and can utilize all the common libraries and database from within the token. This allows for unlimited possibilities with web page design, third party api, WordPress api, PHP frameworks, web 2.0, other WordPress plugins, etc. The tokens can even be used within other tokens for replacement of text, making HTML, CSS, JS, fully dynamic. MultiSite The Token Manager has been built to work with WordPress MultiSite as well as single blog installations. By activating it on the network, it will add the capabilities to control tokens across WordPress sites and blogs. It will also store all tokens in custom tables that are not attached to WordPress, allowing for easy upgrades, backup and version changes. Installation WordPress Standard Installation 1. Upload the contents of 'token-manager' to '/wp-content/plugins/token-manager' within your WordPress. 2. Activate the plugin through the 'Plugins' menu in the WordPress Admin 3. Visit the Token Manager Settings Page and turn on the features you desire. 4. Add some token types in the manager. 5. Add some tokens in the manager. 6. Assign the token to a WordPress page. 7. Put {your_token_name} in the assigned page, template, widget, theme, etc. 8. Visit the page to see the token replacement. WordPress MultiSite Installation 1. Upload the contents of 'token-manager' to '/wp-content/plugins/token-manager' within your WordPress. 2. Network activate the plugin through the 'Plugins' menu in the WordPress Network Admin. 3. Visit a desired network site or blog dashboard. 4. Visit the Token Manager Settings Page and turn on the features you desire. 5. Add some token types in the manager. 6. Add some tokens in the manager. 7. Assign the token to a WordPress page. 8. Put {your_token_name} in the assigned page, template, widget, theme, etc. 9. Visit the page to see the token replacement. Token Management The management of tokens is very simple, with built in ajax controls to allow assignment of tokens to individual and all pages. It also separates out frontpage assignment, giving you the freedom to control exactly what is seen on the frontpage. The manager also keeps track of who created and last updated the tokens in WordPress. Token Parameters The Token Manager now supports token parameters. You can specify a string or integer parameter by adding them behind the token name. You can then access the parameter from within the token code, allowing for unlimited code possibilities. Error Handling PHP and token errors are handled by the Token Manager and can be displayed by turning on custom settings in the manager. This gives developers the full control to know where errors are occurring within their token code. The error setting can also be turned off for live sites. This protects you from displaying error information on public sites. All errors that occur in the tokens, unless fatal, will not stop the webpage from being displayed. So feel free to code your ideas without worring about errors halting WordPress. How to use the Token Manager plugin for Wordpress Introduction In my last article I introduced a plugin called Token Manager. It is a Wordpress plugin designed to manage HTML, PHP, CSS and JavaScript as tokens that are extremely modular and easy to use. It allows you to package your code into reusable versioned components that can be used in themes, pages, posts, comments, widgets by token name and or parameter. This separation allows you to keep your code clean and access the Wordpress Api, without having to create spaghetti code that is hard to maintain. Below, I will discuss how to create a workable token, assign it to a Wordpress page and order it for processing. I will also teach you about the general and hidden features of the plugin that you may or may not be familiar with. If you need instructions for installing the Token Manager, please visit the previous article for a detailed description. For an easy fast installation you can download it through the Wordpress plugin manager and activate it in a matter of seconds. Creating Token Types Now that you have a brief understanding of plugin's purpose, lets now introduce some code and create our first simple token. Before creating your first token, make sure you create your token types. Visit the 'Add New Type' page and create some types. What are token types? Token types are categories for separating your tokens and making it easier to understand the token's purpose. This can be named anything you desire, so feel free to be creative. Some examples of token types. i.e page, component, control, text, module, widget, third party, test, data, feed, service, etc. Once you have created your token types, they will appear under the 'Token Types' page, where you can manage them. They will also appear in a dropdown for assigning, when adding a new token. Creating your First Token Now lets visit the 'Add New Token' page. Start off by giving your token a name. Keep your token names simple and easy to remember. Assign the token type from the dropdown list you created previously under the token types. I have included a description box, that allows you to describe the purpose of the token for the hover help menus. This is a great feature for team development and gives other developers fast insight on your code. The final thing to do is add some actual code to the token value. The 'Token Value' has four tabs; HTML, PHP, CSS and JS. PHP can be used inline in HTML, CSS and JS too! It will be processed and injected into the tab at runtime. If you would like to use PHP in a tab, simply add <?php //Your Code ?>, except for the actual 'PHP Tab', where it is not required. This allows the code to be more dynamic. I will show you examples of this later in the article. For now, lets create a simple copyright token. Token Name: copyright Token Type: text Token Description: Creates a dynamic copyright span element for a Wordpress webpage. Token Value <span id="copyright" onclick="copyright_onclick();"> Copyright © 1999 - <?php echo display_current_year(); ?> YourWebsite, All Rights Reserved. </span> function display_current_year() { return date('Y'); } #copyright { color: blue; font-size: 12px; } function copyright_onclick() { alert('The copyright was clicked!'); } Once complete, click the 'Add Token' button. Your token will be added to the 'Tokens' page for managing. Notice the different tool icons on this page for managing the token. Starting from left to right. Up and Down Arrows - For processing the token in a specific order with other tokens. Edit Token - Allows you to edit the token and create an updated version. Description - Shows a description of the token when hovered. Statistics - Shows stats of the token when hovered. Author - Shows the Wordpress author that created the token. Token Page Assignment - Allows you to assign the token to be added to a specific Wordpress page or pages. Token ID - The unique id for the token. Token Name - The unique name for the token. Notice the brackets around your token '{copyright}'. Token Type - The token type for the token. Version History - Allows you to see what version the token is on when hovered. Delete Token - Allows you to delete the token from the manager. Assigning a Token to a Wordpress Page In order for our new token to be displayed on our webpage, we must assign it with the token page assignment tool. On the 'Tokens' page, click on the icon that looks like pages. A popup box will appear. Type in a Wordpress page name or id to search for, then click the search button. Once a page is found, assign it by clicking on it, then click the arrow button to move it to the attached pages. There are also two hidden page assignments in the search: Frontpage - Assigns the token to be added on the frontpage. All Pages - Assigns the token to be added to all pages. Be careful using this assignment, resource intensive, when creating many tokens. Since our 'copyright' token will most likely be displayed on every page, lets assign it to 'All Pages'. The last and final thing, to get our token to display, add '{copyright}' to our page using the Wordpress page editor or add it to the Wordpress theme files. The text will be replaced at runtime with the processed token value on the assigned page. Navigate to the actual page and it should display. It will show a blue 12px copyright message with dynamic current year and an onclick JavaScript message, if you click on the span. Congrats you have created your first token! Token Process Order In this section I will describe the processing order for tokens with the plugin. Tokens are processed in the order you specify on the 'Tokens' page. By using the up and down arrows you can change the processing order of the tokens. The token process order is lowest to highest in that order. This allows you to use token names in other tokens for replacement of things like paths for links, controls, toolbars, widgets, etc. Each individual token is processed on it's own. This allows the token to pass in different parameters and get different results. If the parameters are the same, the token will be cached and replaced with code that was previously processed. Tokens are processed in the following order. Page Initialize Wordpress Initialize Global Tokens Initialized Token Manager Page Buffering Initialized Wordpress Third Party Plugins Initialized Token Parameters Initialized Sort Tokens By Process ID ASC Token Parameters Injected Token Value PHP, HTML, CSS and JS Initialized Tokens Cached in Key Array Token Replace on Buffered Data Global Token Replace on Buffered Data Output Wordpress Page Globals Tokens There are a few global tokens built into the Token Manager automatically. They are mostly used for paths, filenames, urls, etc. Globals usually begin with 'REQUEST_'. A list of globals are below with descriptions of what they contain. REQUEST_HASERRORS - A boolean value of whether the tokens had errors processing. REQUEST_HOST - The URL host for the domain. REQUEST_SCHEME - The scheme for the URL, HTTP or HTTPS. REQUEST_SITEURL - The scheme and host combined for an accurate URL. REQUEST_FULLURL - The full URL without anything parsed. REQUEST_QUERY - The querystring for the URL. REQUEST_URLPATH - The directory or file path for the URL. REQUEST_FULLFILENAME - The URL filename and filext combined. REQUEST_FILENAME - The URL filename. REQUEST_FILEXT - The URL file extension. REQUEST_TEMPLATESPATH - The Wordpress templates absolute file path. REQUEST_TEMPLATESURL - The Wordpress templates relative URL path. REQUEST_SITEID - The Wordpress multi-site id. REQUEST_BLOGID - The Wordpress multi-site blog id. REQUEST_PAGEID - The Wordpress page id. REQUEST_ISFRONTPAGE - A boolean value of whether the page is the homepage. Token Parameters Optionally, tokens can pass parameters from page to token value for dynamic processing. The format to pass a parameter is fairly simple. {tokenname,"param1",param2}. The token supports string and integer parameters. The parameter can be accessed in the token value through PHP by $GLOBALS["ARGS"][0]. Change the index id to access the other specified parameters. Accessing Wordpress API In this section, I will show you how to access the Wordpress API from your PHP token value, to make something worth wild. Lets start by creating a new token. Token Name: randomlatestposts Token Type: control Token Description: Grabs latest post ids and adds them to an array, then shuffles the ids and pulls in a random specified amount. Two parameters, listsize and returnlimit. Token Value: <?php echo $randomlatestposts->get($GLOBALS["ARGS"][0], $GLOBALS["ARGS"][1]); ?> class randomlatestposts { public function get($_listsize, $_returnlimit) { global $wpdb; $posts = "SELECT id from $wpdb->posts " . "WHERE post_type='post' AND post_status='publish' " . "ORDER BY post_date DESC LIMIT $_listsize"; $postarray = array(); $postresult = mysql_query($posts) or die(mysql_error()); while ($row = mysql_fetch_array($postresult)) { $postarray[] = $row[0]; } shuffle($postarray); array_splice($postarray, $_returnlimit); $ids = implode(',', $postarray); $args = array( 'showposts'=> $_returnlimit, 'post__in' => explode(',', $ids), 'orderby' => 'rand'); query_posts($args); $html = ''; while (have_posts()) { the_post(); $html .= '<li style="margin-bottom: 5px;">' . '<a href="' . get_permalink() . '" rel="bookmark">' . get_the_title() . '</a></li>'; } return '<ol>' . $html . '</ol>'; } } $randomlatestposts = new randomlatestposts(); Token History, Versioning, Backup and Restore I have integrated a scrum process for managing versions with the Tokens. It is only available in the professional version of the Token Manager. Although, the standard free version does contain pieces of the code to keep your code safe from overwrite. Each time you update a token, the full token will be moved into a separate table and the version will be increased. If you would like to access the token versions you will need to do it directly from the database in the standard version. Download it : http://www.2shared.com/file/n8hAb3YV/token-manager103.html http://www.codeproject.com/Articles/332347/Token-Manager
-
Pff ... man ai mare dreptate , doar ca eu l-am pus sa stie lumea , nu toti au iPhone - S.Galaxy , iar cei care vor sa isi achizitioneze unu de ce nu ar trebuii sa stie ? P.S - Poti sterge topicu' daca il consideri inutil .
-
reUP last 3 please !
-
A fost batuta , schinjuita , batjocorita etc , iar la sfarsit nu a primit atentie . Mai femeie dute tare in pustiu strigati ofu si intoarcete normala , daca se mai poate , daca nu fa-ti o cabana acolo si ramai .
-
Download : zeus.rar download - 2shared Password : Zeus
-
reUP , enjoy !!! - BBDroidUnlocker.apk download - 2shared
-
OK , il folosesc pe win , am creeat un boot , dar cand incerc sa il rulez = 0 = nimic nu se deschide nimic . De ce ?
-
Curat de steeler's si keylogger's :
-
Acest film mi-a placut foarte multe : Enjoy !!! Watch Exam online - Watch Movies Online, Full Movies, Download