In the VB6 world it was very common to use “Resizer” OCXs that you just put on your forms and that they handle all the proportional resizing needed if you changed your form.
It was very simple and easy.
However when I started looking for something like that in .NET it was quite hard to find. So finally I got to ComponentSource and found some alternatives and I evaluated them:
Component | Vendor | Pricing | URL |
Component One Sizer For .NET | Component ONE | I think you have to buy the Component One Studio. That’s $800 ($1000 with more support) and I think is one license per developer, runtime-free | http://www.componentone.com/SuperProducts/StudioWinForms/ |
.NET Forms Resize | Softgroup | 380$ per developer from ComponentSource if you buy directly from them you can get the component for 180$ Each copy is licensed on a per developer basis. A developer may use the license on multiple CPUs as long as they are not used concurrently nor used by another developer. Run-time royalty free. | http://www.soft-group.it/net/index.php?c=3a&id=11
|
Resize OCX/.NET | Larcom and Young | 95$-100$ per license one license per developer, runtime-free | http://www.lyoung.com/ |
All of them seem to work. So it really up to your company preferences, budget and the level of support you desire. I haven't made tests like how it behaves if you have thirdparty components or activex ??? maybe I'll review that later.
My good friend Jafet, was recently dealing with an interesting bug in Visual Studio.
He created a simple Visual Studio Project that had just an interface that he exposed thru COM. He’s intention was to compile the Class Library and use the TLB file in other projects, but after building the solution he found out that there was no TLB file generated.
After some tests he finally found out that Visual Studio does not generates a TLB if there are not classes in the Project. So your options are, add a dummy class to your project or invoke the TLB tool from the command line.
:)
Recently I had to deal with targeting an XBAP application that had some Windows Forms controls.
The problem is that those controls can only be used in a trusted environment. If you try to debug an XBAP with some Windows Forms Controls you will get an exception like:
Message: Cannot create instance of 'Page1' defined in assembly 'XBAPWithWinForms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Exception has been thrown by the target of an invocation. Error in markup file 'Page1.xaml' Line 1 Position 7.
It took me a while to found a solution, and it was thru Scott Landford Blog that I found a way around.
In short what he recommends to do is:
Change your settings to:
Start Action->Start external program = %windir%system32\PresentationHost.exe
In my case (and the case of most people that is: c:\windows\system32\PresentationHost.exe)
Start Options->Command line arguments = -debug "c:\projects\myproject\bin\debug\MyProject.xbap" -debugSecurityZoneUrl "http://localhost:2022"
Copy the value from the Start URL after the –debug argument
Very import for using WinForms components you must run in FULL TRUST
Here is some XBAP code using a WinForms WebBrowser. They designer does not like it a lot but it works:
XBAPWithWinforms.zip
My first language ever was GW-Basic. I was a teenager doing in summer work at a company that made polls about hair products and things like that. At that time I didn’t had a computer. They were tooooo expensive.
And I found a manual for GW-Basic. I stole it, to make a copy, my boss found out and he fired me :(. But then I had the power!! jeje. With my cousin Esteban we started out first programs. And we got our first sale :) :) :). It was the typical POS software for a small video rental company.
Ahhh, and that was just the beginning.
So, I really appreciate basic, with all its strengths and weaknesses. I do most of my development now in C#, I think due to my C++, java years, and know I am more used to its syntax, and I really prefer C#. But I consider Basic a real treasure.
Well recently while digging in the Microsoft Dev Labs I discovered SmallBasic. I was just browsing for some new stuff and I am glad to have found this jewel.
Artinsoft (the company I work for) is a company that has devoted a lot of time in the research of automatic software migration to other platforms.Not for nothing one of our flapship products is Visual Basic Upgrade Companion, so basic in any form is always a delight for me. (I will soon post a brief article about bringing VBScript back to life in VB.NET just wait and see)
Ok. Back to SmallBasic.
First of all. The interface is great!!!
It has a cool intellisense guide and you can even extend it :)
And they even resurrected LOGO!
I just hope to write a cool Turtle Graphics routine soon!
:)
COM
The idea is to make a class or several classes available thru COM. Then the compiled dll or the TLB is used to generate and Interop Assembly and call the desired functions.
With this solution the current C++ code base line can be kept or might require just subtle changes.
Calling a function thru com is involved in a lot of marshalling and can add an additional layer that is not really needed in the architecture of the solution.
Creating a Managed Wrapper with Managed C++
The idea with this scenario is to provide a class in Managed C++ that will be available in C#. This class is just a thin proxy that redirects calls to the Managed object.
Let’s see the following example:
If we have a couple of unmanaged classes like:
class Shape {
public:
Shape() {
nshapes++;
}
virtual ~Shape() {
nshapes--;
};
double x, y;
void move(double dx, double dy);
virtual double area(void) = 0;
virtual double perimeter(void) = 0;
static int nshapes;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) { };
virtual double area(void);
virtual double perimeter(void);
};
The first thing we can try, to expose our classes to .NET it to set the setting for managed compilation:
If your project compiles then you are just very close, and what you need is to add some managed classes to your C++ project to expose your native classes:
Let’s see the Shape class:
//We can use another namespace, to avoid name collition.
//In this way we can replicate the structure of our C++ classes.
namespace exposedToNET
{
//Shape is an abstract class so the better thing
// to do is to generate an interface
public interface class Shape : IDisposable
{
public:
//public variables must be exposed as properties
property double x
{
double get();
void set(double value);
}
property double y
{
double get();
void set(double value);
}
//method do not expose any problems
void move(double dx, double dy);
double area();
double perimeter();
//public static variables must
static property int nshapes;
};
//Static methods or variables of abstract class are added here
public ref class Shape_Methods
{
//public static variables must be exposed as static properties
public:
static property int nshapes
{
int get()
{
return ::Shape::nshapes;
}
void set(int value)
{
::Shape::nshapes = value;
}
}
};
}
And for the Circle class we will have something like this:
namespace exposedToNET
{
public ref class Circle : Shape
{
private:
::Circle* c;
public:
Circle(double radius)
{
c = new ::Circle(radius);
}
~Circle()
{
delete c;
}
//public variables must be exposed as properties
property double x
{
virtual double get()
{
return c->x;
}
virtual void set(double value)
{
c->x = value;
}
}
property double y
{
virtual double get()
{
return c->y;
}
virtual void set(double value)
{
c->y = value;
}
}
//method do not expose any problems
virtual void move(double dx, double dy)
{
return c->move(dx,dy);
}
virtual double area()
{
return c->area();
}
virtual double perimeter()
{
return c->perimeter();
}
//public static variables must be exposed as static properties
static property int nshapes
{
int get()
{
return ::Shape::nshapes;
}
void set(int value)
{
::Shape::nshapes = value;
}
}
};
}
DOWNLOAD EXAMPLE CODE
SWIG
SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages.
This is a great tool used for several languages like Python, Perl, Ruby, Scheme, and even in different platforms.
The exposure mechanism used in this scheme is platform invoke, the issues here are similar to those of COM because there is some marshaling going on. This scheme might be more efficient than the COM one but I haven’t really test it to be completely sure that it is better.
I have reviewed the SWIG code and it might also be possible to modify its code to generate wrappers using managed C++, but this is an interesting exercise that I have to leave for my readers. Sorry I just don’t have enough time.
But how is SWIG used?
In SWIG what you do is that you add a .i file to your project. This file provides directives for some code generation that specify exactly what you want to expose and how.
This can very helpful if you just want to expose some methods.
If you are lazy like me you can just add something like:
/* File : example.i */
%module example
%{
#include "example.h" ß you put here includes with the definitions for your classes
%}
/* Let's just grab the original header file here */
%include "example.h" ß add alse the include here
And SWIG will add a file like example_wrap.cxx that you have to compile with the rest of your C++ code.
It will also generate a set of C# classes that you use in your C# application, so it seams to your program that all the code is just C#.
SWIG is a great tool and has been testing in a lot of platforms.
During migratio of a simple project, we found an interesting migration details.
The solution has a project with two Forms. Form1 and Form2. Form1 has a command button and in the Click for that command button it performs a code like UnLoad Form2.
But it could happen that Form2 has not been loaded but in VB6 it is not a problem. In .NET the code will be something like form2.Close() and it could cause problems.
A possible fix is to add some flag that indicates if the form was instanciated and the call the event.
Recenlty following a post in an AS400 Java Group, someone asked about a method for signing and verifying a file with PGP.
I though, "Damn, that seems like a very common thing, it shouldn't be that difficult", and started google'ing for it.
I found as the poster said that the Bouncy Castle API can be used but it was not easy.
Well so I learned a lot about PGP and the Bouncy Castle and thanks god, Tamas Perlaky posted a great sample that signs a file, so I didn't have to spend a lot of time trying to figure it out.
I'm copying Tamas Post because, I had problems accesing the site so here is the post just as Tamas published it:
"To build this you will need to obtain the following depenencies. The Bouncy Castle versions may need to be different based on your JDK level.
bcpg-jdk15-141.jar
bcprov-jdk15-141.jar
commons-cli-1.1.jar
Then you can try something like:
java net.tamas.bcpg.DecryptAndVerifyFile -d test2_secret.asc -p secret -v test1_pub.asc -i test.txt.asc -o verify.txt
And expect to get a verify.txt that's the same as test.txt. Maybe.
Here’s the download: PgpDecryptAndVerify.zip"
And this is the original link: http://www.tamas.net/Home/PGP_Samples.html
Thanks a lot Tamas
Just more details about scripting
Using the MS Scripting Object
The MS Scripting Object can be used in .NET applications. But it has several limitations.
The main limitation it has is that all scripted objects must be exposed thru pure COM. The scripting object is a COM component that know nothing about .NET
In general you could do something like the following to expose a component thru COM:
[System.Runtime.InteropServices.ComVisible(true)]
public partial class frmTestVBScript : Form
{
//Rest of code
}
NOTE: you can use that code to do a simple exposure of the form to COM Interop. However to provide a full exposure of a graphical component like a form or user control you should use the Interop Form ToolKit from Microsoft http://msdn.microsoft.com/en-us/vbasic/bb419144.aspx
To expose an object in COM. But most of the properties and methods in a System.Windows.Forms.Form class, use native types instead of COM types.
As you could see in the Backcolor property example:
public int MyBackColor
{
get { return System.Drawing.ColorTranslator.ToOle(this.BackColor); }
set { this.BackColor = System.Drawing.ColorTranslator.FromOle(value); }
}
Issues:
- The problem with properties such as those is that System.Drawing.Color is not COM exposable.
- Your script will expect an object exposing COM-compatible properties.
- Another problem with that is that there might be some name collision.
Using Forms
In general to use your scripts without a lot of modification to your scripts you should do something like this:
- Your forms must mimic the interfaces exposed by VB6 forms. To do that you can use a tool like OLE2View and take a look at the interfaces in VB6.OLB
- Using those interfaces create an interface in C#
- Make your forms implement that interface.
- If your customers have forms that they expose thru com then if those forms add new functionality do this:
- Create a new interface, that extends the basic one you have and
I’m attaching an application showing how to to this.
Performing a CreateObject and Connecting to the Database
The CreateObject command can still be used. To allow compatibility the .NET components must expose the same ProgIds that the used.
ADODB can still be used, and probably RDO and ADO (these last two I haven’t tried a lot)
So I tried a simple script like the following to illustrate this:
Sub ConnectToDB 'declare the variable that will hold new connection object Dim Connection 'create an ADO connection object Set Connection=CreateObject("ADODB.Connection") 'declare the variable that will hold the connection string Dim ConnectionString 'define connection string, specify database driver and location of the database ConnectionString = "Driver={SQL Server};Server=MROJAS\SQLEXPRESS;Database=database1;TrustedConnection=YES" 'open the connection to the database Connection.Open ConnectionString MsgBox "Success Connect. Now lets try to get data" 'declare the variable that will hold our new object Dim Recordset 'create an ADO recordset object Set Recordset=CreateObject("ADODB.Recordset") 'declare the variable that will hold the SQL statement Dim SQL SQL="SELECT * FROM Employees" 'Open the recordset object executing the SQL statement and return records Recordset.Open SQL, Connection 'first of all determine whether there are any records If Recordset.EOF Then MsgBox "No records returned." Else 'if there are records then loop through the fields Do While NOT Recordset.Eof MsgBox Recordset("EmployeeName") & " -- " & Recordset("Salary") Recordset.MoveNext Loop
End If MsgBox "This is the END!" End Sub |
I tested this code with the sample application I’m attaching. Just paste the code, press Add Code, then type ConnectToDB and executeStatement
I’m attaching an application showing how to do this. Look at extended form. Your users will have to make their forms extend the VBForm interface to expose their methods.
Using Events
Event handling has some issues.
All events have to be renamed (at least this is my current experience, I have to investigate further, but the .NET support for COM Events does a binding with the class names I think there’s a workaround for this but I still have not the time to test it).
In general you must create an interface with all events, rename then (in my sample I just renamed them to <Event>2) and then you can use this events.
You must also add handlers for .NET events to raise the COM events.
#region "Events"
public delegate void Click2EventHandler();
public delegate void DblClick2EventHandler();
public delegate void GotFocus2EventHandler();
public event Click2EventHandler Click2;
public event DblClick2EventHandler DblClick2;
public event GotFocus2EventHandler GotFocus2;
public void HookEvents()
{
this.Click += new EventHandler(SimpleForm_Click);
this.DoubleClick += new EventHandler(SimpleForm_DoubleClick);
this.GotFocus += new EventHandler(SimpleForm_GotFocus);
}
void SimpleForm_Click(object sender, EventArgs e)
{
if (this.Click2 != null)
{
try
{
Click2();
}
catch { }
}
}
void SimpleForm_DoubleClick(object sender, EventArgs e)
{
if (this.DblClick2 != null)
{
try
{
DblClick2();
}
catch { }
}
}
void SimpleForm_GotFocus(object sender, EventArgs e)
{
if (this.GotFocus2 != null)
{
try
{
GotFocus2();
}
catch { }
}
}
#endregion
Alternative solutions
Sadly there isn’t currently a nice solution for scripting in .NET. Some people have done some work to implement something like VBScript in .NET (including myself as a personal project but not mature enough I would like your feedback there to know if you will be interesting in a managed version of VBScript) but currently the most mature solution I have seen is Script.NET. This implementation is a true interpreter. http://www.codeplex.com/scriptdotnet Also microsoft is working in a DLR (Dynamic Languages Runtime, this is the runtime that I’m using for my pet project of VBScript)
The problem with some of the other solutions is that they allow you to use a .NET language like CSharp or VB.NET or Jscript.NET and compile it. But the problem with that is that this process generates a new assembly that is then loaded in the running application domain of the .NET Virtual machine. Once an assembly is loaded it cannot be unloaded. So if you compile and load a lot of script you will consume your memory. There are some solutions for this memory consumption issues but they require other changes to your code.
Using other alternatives (unless you used a .NET implementation of VBScript which currently there isn’t a mature one) will require updating all your user scripts. Most of the new scripts are variants of the Javascript language.
Migration tools for VBScript
No. There aren’t a lot of tools for this task. But you can use http://slingfive.com/pages/code/scriptConverter/
Download the code from: http://blogs.artinsoft.net/public_img/ScriptingIssues.zip
I saw this with Francisco and this is one possible solution:
ASP Source
rs.Save Response, adPersistXML
rs is an ADODB.RecordSet variable, and its result is being written to the ASP Response
Wrong Migration
rs.Save(Response <-- The ASP.NET Response is not COM, ADODB.Recordset is a COM object, ADODB.PersistFormatEnum.adPersistXML);
So we cannot write directly to the ASP.NET response. We need a COM Stream object
Solution
ADODB.Stream s = new ADODB.Stream();
rs.Save(s, ADODB.PersistFormatEnum.adPersistXML);
Response.Write(s.ReadText(-1));
In this example an ADODB.Stream object is created, data is written into it and the it is flushed to the ASP.NET response
In VB6 you could create an OutOfProcess instance to execute some actions. But there is not a direct equivalent for that. However you can run a class in an another application domain to produce a similar effect that can be helpful in a couple of scenarios.
This example consists of two projects. One is a console application, and the other is a Class Library that holds a Class that we want to run like an "OutOfProcess" instance. In this scenario. The console application does not necessary know the type of the object before hand. This technique can be used for example for a Plugin or Addin implementation.
Code for Console Application
using System;
using System.Text;
using System.IO;
using System.Reflection;
namespace OutOfProcess
{
/// <summary>
/// This example shows how to create an object in an
/// OutOfProcess similar way.
/// In VB6 you were able to create an ActiveX-EXE, so you could create
/// objects that execute in their own process space.
/// In some scenarios this can be achieved in .NET by creating
/// instances that run in their own
/// 'ApplicationDomain'.
/// This simple class shows how to do that.
/// Disclaimer: This is a quick and dirty implementation.
/// The idea is get some comments about it.
/// </summary>
class Program
{
delegate void ReportFunction(String message);
class RemoteTextWriter : TextWriter
{
ReportFunction report;
public RemoteTextWriter(ReportFunction report)
{
this.report = report;
}
public override Encoding Encoding
{
get
{
return new UnicodeEncoding(false, false);
}
}
public override void Flush()
{
//Nothing to do here
}
public override void Write(char value)
{
//ignore
}
public override void Write(string value)
{
report(value);
}
public override void WriteLine(string value)
{
report(value);
}
//This is very important. Specially if you have a long running process
// Remoting has a concept called Lifetime Management.
//This method makes your remoting objects Inmmortals
public override object InitializeLifetimeService()
{
return null;
}
}
static void ReportOut(String message)
{
Console.WriteLine("[stdout] " + message);
}
static void ReportError(String message)
{
ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("[stderr] " + message);
Console.ForegroundColor = oldColor;
}
static void ExecuteAsOutOfProcess(String assemblyFilePath,String typeName)
{
RemoteTextWriter outWriter = new RemoteTextWriter(ReportOut);
RemoteTextWriter errorWriter = new RemoteTextWriter(ReportError);
//<-- This is my path, change it for your app
//Type superProcessType = AspUpgradeAssembly.GetType("OutOfProcessClass.SuperProcess");
AppDomain outofProcessDomain =
AppDomain.CreateDomain("outofprocess_test1",
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.BaseDirectory,
AppDomain.CurrentDomain.RelativeSearchPath,
AppDomain.CurrentDomain.ShadowCopyFiles);
//When the invoke member is called this event must return the assembly
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(outofProcessDomain_AssemblyResolve);
Object outofProcessObject =
outofProcessDomain.CreateInstanceFromAndUnwrap(
assemblyFilePath, typeName);
assemblyPath = assemblyFilePath;
outofProcessObject.
GetType().InvokeMember("SetOut",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod,
null, outofProcessObject, new object[] { outWriter });
outofProcessObject.
GetType().InvokeMember("SetError",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod,
null, outofProcessObject, new object[] { errorWriter });
outofProcessObject.
GetType().InvokeMember("Execute",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod,
null, outofProcessObject, null);
Console.ReadLine();
}
static void Main(string[] args)
{
string testAssemblyPath =
@"B:\OutOfProcess\OutOfProcess\OutOfProcessClasss\bin\Debug\OutOfProcessClasss.dll";
ExecuteAsOutOfProcess(testAssemblyPath, "OutOfProcessClass.SuperProcess");
}
static String assemblyPath = "";
static Assembly outofProcessDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
//We must load it to have the metadata and do reflection
return Assembly.LoadFrom(assemblyPath);
}
catch
{
return null;
}
}
}
}
Code for OutOfProcess Class
using System;
using System.Collections.Generic;
using System.Text;
namespace OutOfProcessClass
{
public class SuperProcess : MarshalByRefObject
{
public void SetOut(System.IO.TextWriter newOut)
{
Console.SetOut(newOut);
}
public void SetError(System.IO.TextWriter newError)
{
Console.SetError(newError);
}
public void Execute()
{
for (int i = 1; i < 5000; i++)
{
Console.WriteLine("running running running ");
if (i%100 == 0) Console.Error.Write("an error happened");
}
}
}
}