RCode

a software development blog by Bojan Resnik

Archive for May, 2009

Installing the same managed addin in 32bit and 64bit Autodesk Inventor

Posted by Bojan Resnik on May 21, 2009

I have an addin for AutoDesk Inventor 2009, written in C# and a setup project to go with it. In order to register the addin with Inventor, I needed to register the assembly with “/codebase” switch, so I made a custom installer action:

[RunInstaller(true)]
public partial class AddinInstaller : Installer
{
    public override void Install(System.Collections.IDictionary stateSaver)
    {
        base.Install(stateSaver);
        RegistrationServices regsrv = new RegistrationServices();
        regsrv.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase);
    }

    public override void Uninstall(System.Collections.IDictionary savedState)
    {
        base.Uninstall(savedState);

        RegistrationServices regsrv = new RegistrationServices();
        regsrv.UnregisterAssembly(GetType().Assembly);
    }
}

The actual registration code run by the registration service modifies some registry keys under HKEY_CLASSES_ROOT. This worked fine on Windows XP, but when run on my Vista 64, the setup completed successfully, but failed to register the addin with Inventor. The reason became apparent when I realized that my Inventor installation is also 64 bit – which means it looks for the keys using the 64bit view of the registry, while the installer action runs with the 32bit view.

The code itself is all C#, compiled as “Any CPU”, so it should have no problems running unmodified with both 64 and 32 bit Inventors. The only problem is how to run the installer as 64 bit. I also wanted to allow the same MSI to be used on all platforms, so compiling multiple versions was not an option. And searching the net, I got the impression that it would not solve the problem anyway – at least not without some other tools.

The solution I came up with is simpler: instead of using RegistrationServices object, I can manually execute the RegAsm.exe tool to register the assembly. On my Vista 64, there are two versions of this tool:

  • C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe
  • C:\Windows\Microsoft.NET\Framework64\v2.0.50727\RegAsm.exe

The directories might not be the same on other machines, of course. Fortunately, .NET provides the RuntimeEnvironment.GetRuntimeDirectory() static method which returns the folder of the common language runtime. On my machine, it returns C:\Windows\Microsoft.NET\Framework\v2.0.50727\ inside the installer action. We can use this, combined with RuntimeEnvironment.GetSystemVersion() to construct the paths to 32bit and 64bit versions of the tool:

[RunInstaller(true)]
public partial class AddInInstaller : Installer 
{
    public AddInInstaller() 
    { InitializeComponent(); }

    public override void Install(IDictionary stateSaver) 
    {
        base.Install(stateSaver);
        RegAsm("/codebase");
    }

    public override void Uninstall(IDictionary savedState) 
    {
        RegAsm("/u");
        base.Uninstall(savedState);
    }

    private static void RegAsm(string parameters)
    {
        // RuntimeEnvironment.GetRuntimeDirectory() returns something like
        //    C:\Windows\Microsoft.NET\Framework64\v2.0.50727\
        // We need to get to the
        //    C:\Windows\Microsoft.NET
        // part in order to create 32 and 64 bit paths
        var net_base = Path.GetFullPath(Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), @"..\.."));
                    
        // Create paths to 32bit and 64bit versions of regasm.exe
        var to_exec = new[]
        {
            string.Concat(net_base, "\\Framework\\", RuntimeEnvironment.GetSystemVersion(), "\\regasm.exe"), 
            string.Concat(net_base, "\\Framework64\\", RuntimeEnvironment.GetSystemVersion(), "\\regasm.exe")
        };

        var dll_path = Assembly.GetExecutingAssembly().Location;
 
        foreach (var path in to_exec)
        {
            // Skip the executables that do not exist
            // This most likely happens on a 32bit machine when processing the path to 64bit regasm
            if ( !File.Exists(path) )
                continue;
                
            var process = new Process
            {
                StartInfo =
                {
                    CreateNoWindow = true,
                    ErrorDialog = false,
                    UseShellExecute = false,
                    FileName = path,
                    Arguments = string.Format("\"{0}\" {1}", dll_path, parameters)
                }
            };
            
            using (process)
            {
                process.Start();
                process.WaitForExit();
            }
        }
    }
}
Advertisements

Posted in .NET, C#, Inventor | 8 Comments »

Passing C++/CLI delegate to native code

Posted by Bojan Resnik on May 18, 2009

Recently I had to interface a C++/CLI assembly with a native DLL written in C. This is mostly straightforward, but the C DLL could raise an internal event and provided a way to have the application notified of this event. In order to be informed, the application has to register a callback function that will be invoked by the DLL when the event is raised. The registration function is declared like this:

typedef void (__stdcall* EventCallback)();
void RegisterCallback(EventCallback callback);

Using an ordinary function for the callback would be easy, but I wanted to use a .NET delegate so that I could convert the native event into a .NET event. This scenario also turns out to be supported by .NET. All you need to take care of is to prevent the delegate from being moved or collected by the garbage collector.

public delegate void EventDelegate();

ref class NativeInterface
{
public:
    NativeInterface()
    {
        // Create the delegate from a member function
        nativeCallback_ = gcnew EventDelegate(this, &NativeInterface::Callback);

        // As long as this handle is alive, the GC will not move or collect the delegate
        // This is important, because moving or collecting invalidate the pointer
        // that is passed to the native function below
        delegateHandle_ = GCHandle::Alloc(nativeCallback_);

        // This line will actually get the pointer that can be passed to
        // native code
        IntPtr ptr = Marshal::GetFunctionPointerForDelegate(nativeCallback_);

        // Convert the pointer to the type required by the native code
        RegisterCallback( static_cast<EventCallback>(ptr.ToPointer()) );
    }

    !NativeInterface()
    {
        // Free the handle to the delegate, allowing GC to collect
        // the delegate
        if (delegateHandle_.IsAllocated)
            delegateHandle_.Free();
    }

private:
    GCHandle delegateHandle_;
    EventDelegate^ nativeCallback_;

    void Callback()
    {
        Console::WriteLine("Native event raised");
    }
};

kick it on DotNetKicks.comShout it

Posted in .NET, C++/CLI | Tagged: , , | 9 Comments »