I am basically doing the same work as described in extern alias with same assembly file name: I am writing a converter for classes between different versions of a software, so that the XML settings of these classes can be converted.
The conversion is ready and working ("extern alias" are set where needed).
Now I want to change my small test project into a full program. The conversion uses the DLLs of V22, V23 and V24, each with respective alias, and the program should use the latest version of the DLLs (currently V24, without alias, thus global) for its own operation. The problem is, that the program does not find any types from the referenced global DLLs.
Is Visual Studio (I'm using v.2015 U3) maybe not able to distinguish between the DLLs if the same DLL is used with and without alias?
The project references:
basics, path=..\v24.., no alias (#1)
basics v=22, path=..\v22.., alias=V22
basics v=23, path=..\v23.., alias=V23
basics v=24, path=..\v24.., alias=V24 (#1)
imaging v=22, path=..\v22.., alias=V22
imaging v=23, path=..\v23.., alias=V23
imaging v=24, path=..\v24.., alias=V24
...
I supspect that the marked (#1) assemblies collide somehow.
Is this correct?
Any solution or workaround?
I could add "extern alias V24" in every file of the general part of the program, but then I'd have to change that to "extern alias V25" when the next version of the DLLs is released. I'd like to avoid that extra work.
I found an acceptable workaround.
Instead of adding the alias to every "using" when I switch to another version, e.g. replace "/* v24 */" by "swcore_v_0_22"
using CImageObject_V_0_22 = swcore_v_0_22.SwCore.CImageObject;
using CImageObject_V_0_24 = /* v24 */ SwCore.CImageObject;
using CImageObjectStandard_V_0_22 = swcore_v_0_22.SwCore.CImageObjectStandard;
using CImageObjectStandard_V_0_24 = /* v24 */ SwCore.CImageObjectStandard;
using CImageObjectCombi_V_0_22 = swcore_v_0_22.SwCore.CImageObjectCombination;
using CImageObjectCombi_V_0_24 = /* v24 */ SwCore.CImageObjectCombination;
I can simply add a nested using, which is not possible by default but possible when placed inside a different namespace, see https://stackoverflow.com/a/35921944/2505186. The namespace exists anyway.
using SwCore_v_0_22 = swcore_v_0_22.SwCore;
using SwCore_v_0_24 = global::SwCore;
namespace ConfigEditor
{
using CImageObject_V_0_22 /**/= SwCore_v_0_22.CImageObject;
using CImageObject_V_0_24 /**/= SwCore_v_0_24.CImageObject;
using CImageObjectStandard_V_0_22 /**/= SwCore_v_0_22.CImageObjectStandard;
using CImageObjectStandard_V_0_24 /**/= SwCore_v_0_24.CImageObjectStandard;
using CImageObjectCombi_V_0_22 /**/= SwCore_v_0_22.CImageObjectCombination;
using CImageObjectCombi_V_0_24 /**/= SwCore_v_0_24.CImageObjectCombination;
Later I will replace "global" by "swcore_v_0_24" in all files, which still is some work, but much less than before. And since it is replacing instead of adding, it can do it automatically.
I could theoretically also replace "/* v24 */", but that could break the nice vertical alignment depencing of the length of the replacement. ;-)
Related
I have a Foo.csproj with a class in it: Utils.cs. In my Foo.Tests.csproj, I have a UtilsTest.cs. In Utils.cs, I have a global alias:
global using ProfileDataCollection =
System.Collections.Generic.IDictionary<string, TrashLib.Sonarr.ReleaseProfile.ProfileData>;
I was expecting ProfileDataCollection to now be usable everywhere, sort of like C++ typedefs. However, I'm not able to resolve this symbol from UtilsTest.cs. Do global usings not cross project-dependency boundaries?
I'm using .NET 6 + C# 10 with <ImplicitUsings> set to enable.
I've exhausted every resource possible and can not figure out what the issue is. Button images won't show & keep getting this message when I try to use the command.
Failed to initialize the [add-in name] because the assembly [path to an add-in DLL file] does not exist
when launching Revit. Here's my code that I'm using.
#region Namespaces
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CSharp;
using System.Media;
using System.Reflection;
using System.IO.Packaging;
using System.Windows.Media.Imaging;
using System.Drawing.Imaging;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.DB.Architecture;
#endregion
namespace TpMechanical
{
internal class App : IExternalApplication
{
public Result OnStartup(UIControlledApplication a)
{
String tabname = "TpMechanical";
String panelname = "Tools";
//Option 1
BitmapImage b1Image = (System.Windows.Media.Imaging.BitmapImage)TpMechanical.Properties.Resources.ResourceManager.GetObject("_design3_fhY_icon.ico");
BitmapImage b2Image = (System.Windows.Media.Imaging.BitmapImage)TpMechanical.Properties.Resources.ResourceManager.GetObject("_design3_fhY_icon.ico");
BitmapImage b3Image = (System.Windows.Media.Imaging.BitmapImage)TpMechanical.Properties.Resources.ResourceManager.GetObject("_design3_fhY_icon.ico");
//Option 2
//Bitmap b1Image = (System.Drawing.Bitmap)(TpMechanical.Properties.Resources.ResourceManager.GetObject("Icon1.ico"));
//Bitmap b2Image = (System.Drawing.Bitmap)(TpMechanical.Properties.Resources.ResourceManager.GetObject("Image1.jpg"));
//Bitmap b3Image = (System.Drawing.Bitmap)(TpMechanical.Properties.Resources.ResourceManager.GetObject("Image2.bmp"));
//Option 3
//BitmapImage b1Image = new BitmapImage(new Uri("pack:application:,,,/TpMechanical/Resources/Icon1.ico"));
//BitmapImage b2Image = new BitmapImage(new Uri("pack:application:,,,/TpMechanical/Resources/Image1.jpg"));
//BitmapImage b3Image = new BitmapImage(new Uri("pack:application:,,,/TpMechanical/Resources/Image2.bmp"));
a.CreateRibbonTab(tabname);
var Tools = a.CreateRibbonPanel(tabname, panelname);
var button1 = new PushButtonData("TpButton1", "Button1", Assembly.GetExecutingAssembly().Location, "TpMechanical.command");
button1.ToolTip = " This is a short description";
button1.LongDescription = "This is a long description \n " +
"this is the second line";
var btn1 = Tools.AddItem(button1);
button1.Image = b1Image;
var button2 = new PushButtonData("TpButton2", "Button2", Assembly.GetExecutingAssembly().Location, "TpMechanical.command2");
button2.ToolTip = " This is a short description";
button2.LongDescription = "This is a long description \n " +
"this is the second line";
button2.Image = b2Image;
var button3 = new PushButtonData("TpButton3", "Button3", Assembly.GetExecutingAssembly().Location, "TpMechanical.command3");
button3.ToolTip = " This is a short description";
button3.LongDescription = "This is a long description \n " +
"this is the second line";
button3.Image = b3Image;
Tools.AddStackedItems(button2, button3);
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication a)
{
return Result.Succeeded;
}
}
}
I also have my manifest code below.
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
<AddIn Type="Command">
<Text>Command TpMechanical</Text>
<Description>Some description for TpMechanical</Description>
<VisibilityMode>AlwaysVisible</VisibilityMode>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.Command</FullClassName>
<ClientId>9EDCBEA6-942A-4D9A-932D-612B5E02DC9C</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
<AddIn Type="Command">
<Text>Command TpMechanical</Text>
<Description>Some description for TpMechanical</Description>
<VisibilityMode>AlwaysVisible</VisibilityMode>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.Command2</FullClassName>
<ClientId>1A164A1B-8B02-499A-8ADB-94A75557CD66</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
<AddIn Type="Command">
<Text>Command TpMechanical</Text>
<Description>Some description for TpMechanical</Description>
<VisibilityMode>AlwaysVisible</VisibilityMode>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.Command3</FullClassName>
<ClientId>C5CEC594-E407-40A8-B1B0-163DAA179CDD</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
<AddIn Type="Application">
<Name>Application TpMechanical</Name>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.App</FullClassName>
<ClientId>C12635D2-96E2-4DF4-B172-7BD9487F7AE9</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
</RevitAddIns>
enter image description here
Rereading your question a third time over, it sounds as if your add-in is trying to reference a .NET assembly DLL that cannot be found when Revit tries to load it. Looking at the list of namespaces that you reference in your source code using statements, I see nothing but standard Autodesk Revit, Microsoft and .NET assemblies listed. So, they should all be present and accessible. Are you using anything else elsewhere in your code that is not obvious from that list? You might be able to use tools like fuslogv to analyse your add-in dependencies during load time, as suggested in the note on Exploring Assembly Reference DLL Hell with Fuslogvw.
I suggest you try again with a minimal one-liner external command and a minimal one-liner add-in manifest.
Follow these steps: Revit developers guide add-in registration.
Ensure that Revit has read access to its AddIns folder.
Look at the Hello world walkthrough.
Do not say you exhausted all resources. That would take too long and probably exceed your life span. New resources are being added faster than you can consume them, so any attempt is doomed to fail.
The error message is telling you that the problem is not in the internal implementation code, but just in the basic registration.
Why do you add internal to the IExternalApplication implementation? Isn't that a contradiction? What does that mean?
Why do you use The Building Coder VendorId? That is incorrect. You are not The Building Coder.
Your Assembly path is complex and littered with spaces. In general, I try to avoid such complex paths and all spaces in folder names. I also prefer forward slashes to backward ones. You can omit the folder name entirely if you place the DLL in the same place as the add-in manifest in the AddIns folder.
I am being inundated with similar questions these days. Here is another similar one, a summary of a recent email thread:
[Q] I have dived into the Getting Started with Revit platform API, following the DevTV tutorial by Augusto Goncalves. None of my commands appear on the Revit UI > Add Ins > external commands.
[A] One thing you ought to read is the introductory section of the Revit API developers guide. It tells you exactly what to do to install and launch your add-in. It is shocking of that information is not clear and does not work in the tutorial, though. Thank you for bringing it up!
Installing a Revit add-in is really simple, but people run into difficulties like you describe anyway.
There are only two relevant components:
Add-in manifest file *.addin
.NET class library assembly DLL
These are the important steps:
The DLL must implement IExternalCommand; that means, it must implement the Execute method.
The add-in manifest must point to the DLL and must be placed in the Revit Add-Ins folder for Revit to find and load it.
If the DLL and add-in manifest both reside in the Revit AddIns folder, the full DLL path can be omitted; otherwise it must be specified.
That is really all.
There are thousands of places explaining it; they all say the same thing.
Good luck and lots of fun with the Revit API :-)
[R] I have not had any luck since yesterday about my add-in not appearing in the Revit external commands.
I have carefully structured my code correctly. The add-in manifest file is pointing to my project .dll file. My project class explicitly implements the IExternalCommand interface and fires up the Execute method just fine.
I don't understand what the issue could be, not sure it could be the revit version am using am trying to figure out all possibilities.
[R2] I managed to debug my code. Kindly, ignore previous message.
The location of my manifest add-in file was locked. I guess that was done when my account was set up. The location needed permission to be accessed. This path:
C:\ProgramData\Autodesk\Revit\Addins\2022\
I utilised the try and catch exception to see the issue.
Once I gave access permission, the add-in file is now visible; it worked!
Okay this is driving me crazy. I got one almost finished project (which works perfectly) and I wanted to make another one in the same way. The thing is there is a solution with two layers DataAccessLayer and BusinessLogicLayer. Both of these layers have a Model library with all models in the project. I need to convert the model from the first layer to a model of a second layer in the manager library. This works in the finished project I received but I can not manage to make it on mine.
The thing is I can't make the necessary references to work like they do on the finished project. The structure is:
BusinessLogicLayer
--Managers
----Users
--Models
----User
DataAccessLayer
--Models
----User
In the Managers project I have a reference added to DataAccessLayer.Models.
And inside the Users class I got:
using Library.BusinessLogicLayer.Models;
Now in my project this line is red underlined:
Error CS0234 The type or namespace name 'Models' does not exist in the
namespace 'Library.BusinessLogicLayer' (are you missing an assembly
reference?)
I am not even sure how and why this works on that original project. But I can't figure it out so it's working right on my project and the structure is the exact same. Anyone have an idea about this?
EDIT:
Dunno why I didn't upload this earlier. Here is the structure.
https://i.imgur.com/srnySFJ.jpg
EDIT2:
Since it is not quite understandable I uploaded the whole project on github so you can take a closer look at it.
https://github.com/Morsusy2k/Library
And here is the problem:
https://i.imgur.com/DvCvnMA.jpg
From what you described above and from my understanding, it seems that Managers and Models are two different projects. If that is the case, make sure that you add a reference to BusinessLogicLayer.Models in your BusinessLogicLayer.Managers.
If, on the other hand, you have only two projects BusinessLogicLayer and DataAccessLayer then it could very well mean that Library.BusinessLogicLayer.Models is not the name of the namespace.
UPDATE
From the picture that you added, you might need to add a reference to Library.BusinessLogicLayer.Models.Models. You have a folder named Models and a project named Models. Visual Studio automatically generates namespaces based on the Solution name, Solution folders, project name, folders within project.
There were three issues with your code. The first one is that you are supposed to add a reference to Library.DataAccessLayer.Models and not to Library.BusinessLogicLayer.Models. This is due to the fact that you have User in DataAccessLayer.Models and User2 in BusinessLogicLayer.Models.
The other two issues were with the Map method where you are sending incorrect number of arguments to the constructor (you are missing UserId) and the other issues is with your DateOfBirth and DateJoined being in the wrong order in the same method.
using System;
using System.Collections.Generic;
using System.Linq;
using global::Library.BusinessLogicLayer.Models;
using Library.BusinessLogicLayer.Managers.Properties;
using Library.DataAccessLayer.Models; // <-- Add reference to this
namespace Library.BusinessLogicLayer.Managers
{
public class Users2
{
public IEnumerable<User> GetAll()
{
using(DataAccessLayer.DBAccess.Library library = new DataAccessLayer.DBAccess.Library(Settings.Default.LibraryDbConnection))
{
return library.Users.GetAll().Select(user => Map(user));
}
}
private User Map(DataAccessLayer.Models.User dbUser)
{
if (dbUser == null)
return null;
// TODO: Constructor is missing a paremeter. I'll add a temporary one
int tempUserId = 0;
User user = new User(tempUserId, dbUser.Name, dbUser.UserName, dbUser.Password, dbUser.Email, dbUser.DateJoined, dbUser.DateOfBirth) // <-- The last two params are in the wrong order
{
Id = dbUser.Id
};
return user;
}
private Library.DataAccessLayer.Models.User Map(User2 user)
{
if (user == null)
throw new ArgumentNullException("user","Valid user is mandatory!");
return new DataAccessLayer.Models.User(user.Id,user.Name, user.UserName, user.Password, user.Email, user.DateJoined, user.DateOfBirth);
}
}
}
Also, regarding the last screenshot that you provided, you do not have Library.BusinessLogicLayer.Models2 namespace. Remove number 2 to get it to work.
As I don't have permission to update your repo with the fixed code, you'll have to fix it manually based on my answer. Otherwise, let me know so that we see how I can push the code back.
There are a few things you could try:
This error could be appearing within BusinessLogicLayer because DataAccessLayer failed to build. Try building DataAccessLayer by itself to see if you get a different error.
The references might be "added", but you might not be referencing the correct DLL or version for some reason. Check your .csproj files manually to ensure all references and versions are correct, have the right hint paths, etc. If you have any config files, you should also review them to ensure there are no version conflicts.
In addition to checking the references, it is possible to add if-then and switch case logic inside of the .csproj files. This is an MSBuild feature that Visual Studio doesn't support through its GUI, so you may need to copy/update this logic manually in your current .csproj files if any existed.
Check your default namespaces in project properties to see if they are the same as your old project. If you added new files since you moved to this project, they may have been inadvertently added with the wrong namespace and that namespace may be causing a conflict. You could also try using global::Library.BusinessLogicLayer.Models; to see if that fixes or changes the error message or at least if Intellisense is picking the namespace up.
If that doesn't work, review all of your namespaces in all .cs files to see if you have any that have gone rogue.
Since your Models namespace has the problem and you have 2 of them with the same name, try temporarily renaming one of them (yes, every .cs file in one of the projects) to Models2 to see if it provides a clue (such as the error going away or changing).
I'm looking to replicate the following in IronPython and searching has so far been fruitless and/or disappointing.
namespace Groceries
{
public class ChocolateMilk : Milk
{
// Other stuff here
}
}
The idea would be that the compiled Python DLL will be loaded into a C# program through System.Reflection.Assembly.Load and a GetType("Groceries.ChocolateMilk") on the loaded DLL would not return null.
The most recent answer I was able to find was in 2008 and said that it was impossible without using the Hosting API - http://lists.ironpython.com/pipermail/users-ironpython.com/2008-October/008684.html.
Any suggestions on how to accomplish this would be greatly appreciated. Any conclusions that this is currently impossible to do via IronPython will also be appreciated, but less so.
I'm a bit confused on what you're asking here. Are you trying to instantiate that C# code in your IronPython modules? Or do you have the equivalent classes written in IronPython and you want to instantiate them in your C# code?
Based on the link you posted, I suppose you're going for the latter and have IronPython classes that you want instantiated in your C# code. The answer is, you cannot directly instantiate them. When you compile IronPython code to an assembly, you cannot use the types defined there with your regular .NET code since there is not a one-to-one mapping between IronPython classes and .NET classes. You would have to host the assembly in your C# project and instantiate it that way.
Consider this module, Groceries.py compiled to Groceries.dll residing in the working directory:
class Milk(object):
def __repr__(self):
return 'Milk()'
class ChocolateMilk(Milk):
def __repr__(self):
return 'ChocolateMilk()'
To host the module in your C# code:
using System;
using IronPython.Hosting;
using System.IO;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var engine = Python.CreateEngine();
var groceriesPath = Path.GetFullPath(#"Groceries.dll");
var groceriesAsm = Assembly.LoadFile(groceriesPath);
engine.Runtime.LoadAssembly(groceriesAsm);
dynamic groceries = engine.ImportModule("Groceries");
dynamic milk = groceries.ChocolateMilk();
Console.WriteLine(milk.__repr__()); // "ChocolateMilk()"
}
}
Otherwise to go the other way and create an instance of your .NET type in your IronPython code (as your title suggests). You'd need to add the path to your assembly, reference it, then you could instantiate it as needed.
# add to path
import sys
sys.path.append(r'C:\path\to\assembly\dir')
# reference the assembly
import clr
clr.AddReferenceToFile(r'Groceries.dll')
from Groceries import *
chocolate = ChocolateMilk()
print(chocolate)
I am not able to create a functioning ActiveX control in C#; I have tried following tutorials to do so without success.
I create a sample Class Library project which includes this code:
namespace AACWCSurvey
{
[ProgId("Prisoner.PrisonerControl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Class1
{
public Class1()
{
MessageBox.Show("FIRETRUCK!!!");
}
}
}
I then did the following steps:
Properties => Application => Assembly Information => Make Assembly COM-visible
Build => Register for COM interop TRUE (checked)
Make Strong name for assembly (signing)
Build the project
regasm MyDll.dll /tlb /codebase
Can't see Prisoner.PrisonerControl in tstcon32 =(
My OS is WinXP x86.
UPD: it works from VBScript:
Dim objJava
Set objJava = WScript.CreateObject("Prisoner.PrisonerControl")
but it is not visible in tstcon32.
If you read the actual article using the Prisoner.PrisonerControl control a sub key named Control is created inside the key with your control GUID.
On my machine with the guid {9DEA5F06-E324-31A7-837B-D0F3BDE91423} creating the key
HKEY_CLASSES_ROOT\CLSID\{9DEA5F06-E324-31A7-837B-D0F3BDE91423}\Control
Make the control appears in tstcon32. And with or without it the ActiveX is usable for javascript
var x = new ActiveXControl("Prisoner.PrisonerControl");
Actually i had to fight windows on both the javascript execution and registry path to test it on my system because it's an x64 machine but that's another story.
You have created a COM server but not an ActiveX control, which is a far more intricate COM object, the kind that you can exercise with tstcon32.exe.
It must implement a bunch of interfaces, key ones are IOleObject and IOleWindow. The kind of interfaces that allows it to do the required negotiations with an ActiveX host and create a visible window. The Winforms Control class is your best bet to create one.
Here are the relevant steps as documented externally. This is summarized leaving out some exposition but not any necessary steps.
This example is also very similar to the article Using Managed Controls as ActiveX Controls by Garry Trinder, November 25, 2008 and I've included some notes from this article as well.
Exposing Windows Forms Controls as ActiveX controls
This article will describe how to utilise Windows Forms controls
outside of .NET.
Writing the control
Create a new control project from within Visual Studio - my examples are all in C# but VB.NET could also be used.
[Here Garry's article suggests, "First, create a managed usercontrol project – either a Windows Forms class library or control library project. Use the usercontrol designer to design your custom usercontrol the way you want it (using any standard controls you like)."]
Add controls etc to the form, put in the code etc.
Add in the following using clauses...
using System.Runtime.InteropServices;
using System.Text;
using System.Reflection;
using Microsoft.Win32;
Attribute your class so that it gets a ProgID. This isn't strictly necessary as one will be generated, but it's almost always best to be
explicit.
[ProgId("Prisoner.PrisonerControl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
This assigns the ProgID, and also defines that the interface
exposed should be 'AutoDual' - this crufts up a default interface for
you from all public, non-static members of the class. If this isn't
what you want, use one of the other options.
Update the project properties so that your assembly is registered for COM interop.
If you're using VB.NET, you also need a strong named assembly.
Curiously in C# you don't - and it seems to be a feature of the
environment rather than a feature of the compiler or CLR.
Add the following two methods into your class.
[ComRegisterFunction()]
public static void RegisterClass ( string key )
{
// Strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
StringBuilder sb = new StringBuilder ( key ) ;
sb.Replace(#"HKEY_CLASSES_ROOT\","") ;
// Open the CLSID\{guid} key for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
// And create the 'Control' key - this allows it to show up in
// the ActiveX control container
RegistryKey ctrl = k.CreateSubKey ( "Control" ) ;
ctrl.Close ( ) ;
// Next create the CodeBase entry - needed if not string named and GACced.
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;
inprocServer32.SetValue ( "CodeBase" , Assembly.GetExecutingAssembly().CodeBase ) ;
inprocServer32.Close ( ) ;
// Finally close the main key
k.Close ( ) ;
}
The RegisterClass function is attributed with ComRegisterFunction -
this static method will be called when the assembly is registered for
COM Interop. All I do here is add the 'Control' keyword to the
registry, plus add in the CodeBase entry.
CodeBase is interesting - not only for .NET controls. It defines a URL
path to where the code can be found, which could be an assembly on
disk as in this instance, or a remote assembly on a web server
somewhere. When the runtime attempts to create the control, it will
probe this URL and download the control as necessary. This is very
useful when testing .NET components, as the usual caveat of residing
in the same directory (etc) as the .EXE does not apply.
[ComUnregisterFunction()]
public static void UnregisterClass ( string key )
{
StringBuilder sb = new StringBuilder ( key ) ;
sb.Replace(#"HKEY_CLASSES_ROOT\","") ;
// Open HKCR\CLSID\{guid} for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
// Delete the 'Control' key, but don't throw an exception if it does not exist
k.DeleteSubKey ( "Control" , false ) ;
// Next open up InprocServer32
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;
// And delete the CodeBase key, again not throwing if missing
k.DeleteSubKey ( "CodeBase" , false ) ;
// Finally close the main key
k.Close ( ) ;
}
The second function will remove the registry entries added when (if)
the class is unregistered - it's always a good suggestion to tidy up
as you go.
Now you are ready to compile & test your control.
Additional notes from Garry's blog:
[The] additional registry entries: Control, MiscStatus, TypeLib and
Version [can be created] with a .REG script, but it’s generally better
to write functions that will be called on registration/unregistration
He describes the registry keys in some detail:
Control is an empty subkey. TypeLib is mapped to the GUID of the
TypeLib (this is the assembly-level GUID in the assemblyinfo.cs).
Version is the major and minor version numbers from the assembly
version. The only mildly interesting subkey is MiscStatus. This needs
to be set to a value composed of the (bitwise) values in the OLEMISC
enumeration, documented here. To make this enum available, add a
reference to Microsoft.VisualStudio.OLE.Interop (and a suitable
‘using’ statement for the namespace).
His final note is a warning:
Note: this seems to work OK for Excel (with the very limited testing
I've done), partly works with PowerPoint, but fails miserably with
Word. Possibly, some more of the OLEMISC values might improve this;
possibly there are some messages we need to hook; possibly there are
some more interfaces we need to implement ... The fact that I’ve only
barely got it to work in a very limited way should tell you that this
is probably not a technique you want to use in any serious way.