Developer's Dump

Reviving Visual Basic 6 Enterprise Edition

leave a comment »

As time goes old software simply stop working. In most cases you can find a newer version to use, but when it comes to old tools like Visual Basic 6 Enterprise Edition, they are not so easy to replace.

I was asked to do some minor changes to an old (5-10 years) calculation program. By installing Visual Basic 6 ended up getting my Windows XP SP2 all messed up, so I eventually used more time to setup the IDE than for actually doing the code change! As the code changes I was asked to do were minor, it was difficult to justify to the customer why the complex application should be rewritten using modern tools.

After some struggling I got the old VB installed on a virtual machine. To help other facing the same problem, here is how:

1. Visual Basic 6 Enterprise Edition requires that Microsoft Java Virtual Machine 4 or later is installed. The setup might try to install the old version but fail. As Microsoft ended the support for MSJVM in June 2009 the only place to get the setup files are from some unofficial sites like the DMT homepage. Note that you might find the build 3810 from many places, but you will first need to install the older 3805 build!

2. Before running the setup first copy all the files from the CD on to your local hard drive. I don’t know the reason to why this is required.

3. The setup won’t start if you are running XP with a 32 bit color mode, so create a shortcut pointing to the setup executable and from the compatibility options turn on the Windows 95 compatibility mode and set the display settings to 256 colors.

4. When the setup starts, go through the wizard pages and make sure to uncheck every component (e.g. DCOM 98) you do not explicitly need!

You will most likely get some ActiveX and ADO errors during the installation, but eventually Visual Basic 6 should appear in your start menu and work.

If these steps did not do the trick, try disabling the Windows DEP  (Data Execution Prevention).  The easiest way to do this is by editing your C:\boot.ini and setting the /noexecute value to alwaysoff. If your firewall has a ISP (Intrustion Prevention) functionality, disable it.

Written by Tom

September 2, 2009 at 17:20

Posted in Windows

Tagged with ,

Quick Guide to Installing Windows XP Mode

with one comment

Microsoft provides an virtual Windows XP environment for Windows 7 called Windows XP Mode to help with migrating old applications to work on the new operating system.

By completing these steps you’ll have Windows XP Mode up and running in a few seconds:

  1. First you need to make sure your CPU has the required support for virtualization. The easiest way to do this is by running a small application called SecurAble. You might need to enable the virtualization support from your BIOS.  After swtiching it on you might need to completely shut down your computer as a simple reboot might just not do it.
  2. Get the latest files for Windows XP Mode and Virtual PC from Microsoft and install them
  3. Select Windows XP Mode from you start menu under Windows Virtual PC and enter some logon credentials for the virtual environment

You’re done!

Note that while in Windows XP Mode you might run into the following error if you try to start some setup located on a external USB or network drive:

“The Windows Installer does not permit installation from a Remote Desktop Connection”

This is because Windows XP Mode sees you as a remote desktop user and for some security reason the Windows Installer can’t permit you to execute a “remote” setup. To solve this problem, first copy all the setup files into the virtual environment and then run the setup!

Written by Tom

August 28, 2009 at 16:25

Posted in Uncategorized, Windows

Tagged with ,

Sending Data to Océ Printer Using .NET

leave a comment »

I was asked to turn a old Inventor printing macro into a .NET add-in. This was a fairly simple task and only translating the SendKeys and Shell commands to code required some thinking.

To print using a Océ 9600 large format printer the macro called the following shell command to send the custom settings along with the plot data (generated using Inventors PrintManager.PrintToFile function) to the printer:

"cmd /c copy settings.txt /a + drawing.plt /b "

This told me I needed to first read the custom settings from a file and turn it into ASCII, then combine that with the data in the binary plot file and finally send it to the printer.

I did not know how to send the data to the printer, so I just tried opening a file stream using the printer’s address (without expecting it to work). This gave me the following exceptionally informative error message:

"FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:', call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr."

The message told me exactly what I needed to do. I needed to use the CreateFile Windows API call to get a handle I could pass the the FileStream class. As this was the final piece of the puzzle I quite quickly produced the following code:

public void WriteToPrinter(string pltFile, string settings, string printer)
{
    IntPtr handle = IntPtr.Zero;
 
    try
    {
        handle = CreateFile(printer,
            (uint)EFileAccess.GenericWrite,
            (uint)(EFileShare.Read | EFileShare.Write),
            (uint)0,
            (uint)(ECreationDisposition.OpenExisting),
            (uint)(EFileAttributes.NoBuffering),
            (int)IntPtr.Zero);
 
        if (handle.ToInt32() == -1)
        {
            int hresult = Marshal.GetHRForLastWin32Error();
            Marshal.ThrowExceptionForHR(hresult);
        }
 
        using (FileStream stream = new FileStream(handle, FileAccess.Write))
        {
            using (BinaryWriter writer = new BinaryWriter(stream))
            {
                byte[] asciiArray = Encoding.Convert(Encoding.UTF8, 
                    Encoding.ASCII, File.ReadAllBytes(settings));
 
                writer.Write(asciiArray);
 
                byte[] binaryArray = File.ReadAllBytes(pltFile);
                writer.Write(binaryArray);
 
                writer.Close();
            }
        }
    }
    finally
    {
        if (handle != IntPtr.Zero && handle.ToInt32() != -1)
            CloseHandle(handle);
    }
}
 
#region Windows API calls
 
[DllImport("kernel32", SetLastError = true)]
static extern unsafe IntPtr CreateFile(
    string FileName, 
    uint DesiredAccess, 
    uint ShareMode, 
    uint SecurityAttributes, 
    uint CreationDisposition, 
    uint FlagsAndAttributes, 
    int hTemplateFile);
 
[DllImport("kernel32", SetLastError = true)]
static extern unsafe bool CloseHandle(
    IntPtr hObject);  
 
[Flags]
public enum EFileAccess : uint
{
    GenericWrite = 0x40000000
}
 
[Flags]
public enum EFileShare : uint
{
    Read = 0x00000001,
    Write = 0x00000002
}
 
public enum ECreationDisposition : uint
{
    OpenExisting = 3
}
 
[Flags]
public enum EFileAttributes : uint
{
    NoBuffering = 0x20000000
}
 
#endregion

Written by Tom

July 30, 2009 at 15:22

Posted in .NET, Inventor, Windows

Tagged with , ,

WCF and Domain Trust Failure

leave a comment »

Lately users have been getting the following error when accessing a WCF services that uses Windows authentication.

"The trust relationship between the primary domain and the trusted domain failed."

Not all users are getting this error despite that they all belong to the same domain.

Trying to find a solution I came up with three options:

  • Remove any inactive trusted domains from Active Directory
  • In the local security policy of the server change the cache value of the “Interactive Logon: Number of previous logons to cache” to 0 (zero)
  • Change the unhandled exception policy back to the default behavior of previous .NET Framework versions.

As we needed a quick fix, the tech guys picked the last option and added the following lines to the Aspnet.config in the %WINDIR%\Microsoft.NET\Framework\v2.0.50727 directory:

<configuration>
<
runtime>

<legacyUnhandledExceptionPolicy enabled="true" />

</runtime>
</
configuration>

After restarting the server the users could once again access the service.

I believe the second option of disabling the logons cache would be a better solution, but that one it yet to be tested.

Written by Tom

July 29, 2009 at 14:09

Posted in .NET, WCF, Windows

Tagged with , ,

Controlling Design-Time Resizing

leave a comment »

I needed a very simple extension of the standard combobox so I setup my own UserControl. As comboboxes have a fixes height I wanted to prevent the user from being able to resize vertically.

I remembered from the past that this was a trivial task, but could not remember how it was done. It took me one hour of googling before I found the solution, so I decided to spare everyone else from the trouble and post it here.

To control the selection rules of your control you need to first setup a custom designer:

    public class ComboboxControlDesigner : ControlDesigner
    {
        public override SelectionRules SelectionRules
        {
            get
            {
                return SelectionRules.LeftSizeable |
                    SelectionRules.RightSizeable |
                    SelectionRules.Visible |
                    SelectionRules.Moveable;
            }
        }
    }

Use the override to return any selection options you want to support.

Then finally attach the designer to your UserControl using the Designer attribute:

[Designer(typeof(ComboboxControlDesigner))]

To control resizing during run-time and design-time you can also just try overriding the SetBounds method of the control:

        protected override void SetBoundsCore(int x, int y, 
            int width, int height, BoundsSpecified specified)
        {
            if (this.DesignMode)
                base.SetBoundsCore(x, y, 100, 21, specified);
            else
                base.SetBoundsCore(x, y, width, height, specified);
        }

Written by Tom

July 16, 2009 at 16:21

Posted in .NET, Forms

Tagged with ,

SQL Server Compact Edition

with 2 comments

For so many years I have been forced to work with Microsoft Access and application using it as a local and even shared network data storage, resulting in endless problems with corrupted and bloated database files.

I was very pleased when I found out that Microsoft had finally introduced a good (and free) replacement for the light weight Access databases called SQL Server Compact Edition.  As Access it is a file based database but with many of the features of  it’s big brother SQL Server.

Finally testing the new database I quite quickly got more and more dissapointed, not at the database engine but the tools.  When building on my first compact database using Visual Studio 2008 I almost immediately started getting errors like “The parameter is incorrect” and “The path is not of a legal form” for no particular reason. After googling for help I found out  that the only solution is to restart Visual Studio, every time! Also the useability leaves much to hope for  as now simple stuff like setting up relations and indexes requires switching between numerous dialogs.

Hoping the SQL Server Management Studio would provide better tools I found out the old 2005 version does not support the latest 3.5 CE database. After installing the new 2008 version, I was not surprised to see the same tools and dialogs as in Visual Studio. I guess a visual tool for defining the schema is out of the question.

I’m planning to use SQL Server Compact Edition in my next projects, hoping the engine will prove to compensate for the weak tools. You can be sure to read more about this subject in my later posts!

Written by Tom

May 29, 2009 at 21:13

Posted in Databases

Tagged with

Uninstalling by Force

leave a comment »

Sometimes an application fails to uninstall and you are stuck in a situation where you can’t repair, remove or update it. Fortunately Microsoft provides a tool called msizap that removes all the necessary MSI metadata enabling you to again reinstall the application.

Msizap is distributed as a part of the Windows SDK available at the Microsoft download center.

To remove a installation do the following:

  1. Start the SDK command line (CMD) through the Windows SDK menu in your start menu
  2. Type in the command “msizap TP <path to the msi package>”

Even if the msizap fails to remove some data, the Windows installer should again allow you to reinstall. Note that msizap will only remove the MSI metadata, leaving all files and registry keys installed.

For more information check out MSDN.

Written by Tom

May 20, 2009 at 15:30

Posted in Setup

Tagged with ,

WCF: Windows Authentication and External Users

leave a comment »

Using Windows authentication with WCF enables developers to support centralized access management for enterprise services. As default the framework uses the computer login credentials of the Windows user accessing the service.

This default behavior works for all the cases where the computer belongs to the same domain as the server, e.g. when using a company computer. But a when external users needs to access a service that uses Windows authentication, the client needs to send credentials the server can identify. For this you need to define the credentials prior to creating the channel.

factory.Credentials.Windows.ClientCredential =
new System.Net.NetworkCredential(name, password, domain);

 
Make sure not to mix the factory.Credentials.Windows.ClientCredential and factory.ClientCredential properties.

There is also a good resource about debugging Windows authentication errors at MSDN.

Written by Tom

April 29, 2009 at 19:35

Posted in WCF

Tagged with , , , ,

WCF Streams and Timeouts

leave a comment »

I’ve been working on a WCF service for distributing file updates. Recently I did some testing to see how well the system performs through slow connections and discovered some issues I’m glad I run into before the release.

My first discovery was that WCF (using a streaming net.tcp binding) does not perform very well with high ping round-trip-times. In this case my first test setup was quite extreme, as I connected to the service using VPN through a WLAN router connected to the internet by 3G. The ping was something around 750ms.

My second discovery was that the default SendTimeout for a service is 1 minute. This is not nearly enough for a service returning files! I believe the default timeout will cause problems even with fast connections as it only depends on the size of the largest file. If the operation is still returning data after 1 minute, the server closes the stream without warning and returning just some timeout or IO exception.

Fortunately fixing the issue was easy, I just needed to increase the SendTimeout of the service:

binding.SendTimeout = new TimeSpan(1, 0, 0); // 1 hour

If you change the SendTimeout, you should also consider increasing the default 10 minute ReceiveTimeout of the client binding.

There is of course a small security risk in incresing the timeouts, but in my case it’s ok as my service will only be used in a internal network. I wonder if there is a way to define an operation specific SendTimeout?

Written by Tom

March 17, 2009 at 19:59

Posted in WCF

AutoCAD and .NET

with 2 comments

Since AutoCAD 2008 Autodesk ObjectARX® has provided managed wrapper classes that enable you to use .NET to write commands and extensions for AutoCAD. These managed classes enable you to throw away the old COM type libraries and adapt a new way of interacting with AutoCAD in a way much similiar to working with a database. Unfortunately there are quite little examples available to help get started.

The assemblies you need are acdbmgd.dll and acmgd.dll located under the AutoCAD installation directory and also included with the Autodesk ObjectARX SDK. When you reference to these, make sure you change the value of the “Copy local” property of the reference to false as the libraries are already loaded when AutoCAD calls your classes.

To create a new command or extension for AutoCAD you need to setup a class library project and create the necessary classes:

using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
 
[assembly: CommandClass(typeof(AutoCadDemo.MyCommands))]
[assembly: ExtensionApplication(typeof(AutoCadDemo.MyExtensionApplication))]
 
namespace AutoCadDemo
{
    public class MyCommands
    {
        [CommandMethod("MYCOMMAND")]
        public void MyCommand()
        {
            // Command implemenation
        }
    }
 
    public class MyExtensionApplication : IExtensionApplication
    {
        public void Initialize()
        {
            // Extension implemenation
        }
 
        public void Terminate()
        {
            // Extension disposer
        }
    }
}

As you can see, assembly attributes are used to tell AutoCAD what classes it should load. You also need to use attributes to define each command. To load your commands and extensions in AutoCAD you need to use the netload command. There is also a way to use the registry to define what assemblies should be loaded when AutoCAD starts.

The following example shows how to start a transaction, get input from the user and create a new object:

private void DrawCircle()
{
    // Uses editor to prompt user for center point and radius
    Editor editor = Application.DocumentManager.MdiActiveDocument.Editor;
 
    PromptPointResult pointResult = editor.GetPoint("Select center point:");
    if (pointResult.Status != PromptStatus.OK)
        return;
 
    PromptDistanceOptions radiusOptions = new PromptDistanceOptions("Radius:");
    radiusOptions.BasePoint = pointResult.Value;
    radiusOptions.UseBasePoint = true;
 
    PromptDoubleResult radiusResult = editor.GetDistance(radiusOptions);
    if (radiusResult.Status != PromptStatus.OK)
        return;
 
    // Starts transaction to enable editing of drawing
    Database database = Application.DocumentManager.MdiActiveDocument.Database;
    Transaction transaction = database.TransactionManager.StartTransaction();
    {
        try
        {
            // Creates new object representing a circle
            Circle circle = new Circle(pointResult.Value, Vector3d.ZAxis, 
                radiusResult.Value);
            BlockTableRecord activeSpace = (BlockTableRecord)transaction.GetObject(
                database.CurrentSpaceId, OpenMode.ForWrite);
 
            // Adds object to active space
            activeSpace.AppendEntity(circle);
 
            // Adds newly created object to transaction
            transaction.AddNewlyCreatedDBObject(circle, true);
 
            // Commits changes
            transaction.Commit();
        }
        catch (System.Exception)
        {
            // Rollbacks changes on error
            transaction.Abort();
        }
    }
}

Things work a bit differently compared to the old COM interface used in the VBA macros and most Autodesk examples. Each object has a object id that is used to call the object. Database object has properties that provide the ids of most of the main collections e.g. blocks table.

As the drawing file can be seen as a database also access to it can be shared. This means you need to define the minimum access level (open mode) you need to complete your actions. The active transaction enables you to rollback any changes you have made in case something goes wrong.

The wrapper classes also enable you to store data into the drawing in such a way not accessable by the user through the AutoCAD user interface. For this you can either use the database specific Named Object Dictionary (NOD) or the Extenion dictionary of every object.

public void AddItemToNod(string key, string value)
{
    // Starts transaction
    Database database = Application.DocumentManager.MdiActiveDocument.Database;
    Transaction transaction = database.TransactionManager.StartTransaction();
 
    try
    {
        // Retrieves the NOD table and inserts a new record
        DBDictionary nod = (DBDictionary)transaction.GetObject(
            database.NamedObjectsDictionaryId, OpenMode.ForWrite);
 
        ResultBuffer buffer = new ResultBuffer();
        buffer.Add(new TypedValue((int)DxfCode.Text, value));
 
        Xrecord record = new Xrecord();
        record.Data = buffer;
 
        nod.SetAt(key, record);
 
        // Adds newly created record to transaction
        transaction.AddNewlyCreatedDBObject(record, true);
 
        // Commits changes
        transaction.Commit();
    }
    catch (System.Exception)
    {
        transaction.Abort();
    }
}

By using XML serialization you can basically store any object data into the drawing.

Also the classic way of storing data under the file properties (summary info) is possible:

public void AddItemToSummaryInfo(string key, string value)
{
    Database database = Application.DocumentManager.MdiActiveDocument.Database;
 
    // Uses builder to edit the summary info (file properties) of the drawing
    DatabaseSummaryInfoBuilder infoBuilder = 
        new DatabaseSummaryInfoBuilder(database.SummaryInfo);
 
    infoBuilder.CustomPropertyTable.Add(key, value);
 
    database.SummaryInfo = infoBuilder.ToDatabaseSummaryInfo();
}

You should check out Kean Walmsleys blog “Through the Interface” for good tips on developing extensions for AutoCAD using .NET.

Written by Tom

March 4, 2009 at 21:14

Posted in AutoCAD

Tagged with ,

Follow

Get every new post delivered to your Inbox.