Please Note that I have changed address
Go to
Baking Ways / Productive Bytes



Search This Blog

Pages

Wednesday, May 19, 2010

Developing COM exposed classes in C#

Press  here to download the template. CSharp_Com_Class.zip will be downloaded.
For some code example see this  post

Here you can find a series of notes I took as reminders to develpod a COM Class in C#.

I will put it here just as a reference, I hope I will have more time in the future to show you a full example.
You can also find here a C# template you can use to start develop C# COM exposed class. You can delete all the comments out of it. I just add them there for my reference.



To install the template in your VS2008 you need first to find out where they are stored.
To do this, go to File, Export Template. After a few click you should find out where your exported templates
are stored
On my PC for example, they are store here
C:\Users\PP\Documents\Visual Studio 2008\My Exported Templates\WFA01.zip

Once you know this path, just copy the CSharp_Com_Class.zip in the following directory.
You must copy the .zip file. Do not unzip them.

C:\Users\PP\Documents\Visual Studio 2008\Templates\ItemTemplates\Visual C#.

If things go well (and it took me sometime to figure out how to do it) you should have a new template in your
Add New Item, Visual C# Item





 C# COM Rules:

 To expose properties and methods to COM, you must declare them on the class
 interface and mark them with a DispId attribute, and implement them in the class.
 The order in which the members are declared in the interface is the
 order used for the COM vtable.
 ex:
    [DispId(1)] void Init(string userid , string password);
    [DispId(2)] bool ExecuteSelectCommand(string selCommand);

1)The Class must be public
2) Properties, methods and events that need to be exposed:
    a) must be public
    b) Properties and methods must be declared in the class interface. The class must implement this interface
    c) Event must be declared on the Event Interface. The class should not implement this interface
3)  Other Class member that are not declared in the class interface, are not visible to COM but are
      visible to .NET classes
4)  The class must have a default parametereless constructor. Always write is down even if is empty.
     The class can have its constructors and methods overloaded.
5) COM does not support inheritance, only interface implementation
    So you can't  do Class Employees : List.
    This will not be exposed to COM
 6) C# can pass to COM zero based array using "ref" in the method signature.
     Without ref, the method will not work!
     ex:    double[] Compute(ref double[] a, double b)
     in VB 6.0 this will be like Dim a() as double
 7) Variant can be passed as type "object"
 8) Enum can be exposed.  Remeber you need to generate a unique Guid using
     C:\Programmi\Microsoft Visual Studio 9.0\Common7\Tools\guidgen.exe Registry format
    [Guid("DE23AB62-269E-4418-BCBD-193BE024E21C"),
    ComVisible(true)]
    public enum MyEnum{
          [DispId(1)] A = 0,
          [DispId(1)] B = 1,
          [DispId(1)] C = 2

    }
Please not that "public enum MyEnum : long" will compile but will cause method that have MyEnum
in their signature not to work at all
9) Collections.
Collections can be implemented using encapsulation and delegation.
We can encapsulate a SortedList and delegate to it the implementation of
Count, Item, Remove, Add methods defined in the COM Interface.
Ex:
-----------------------------------------------------------------------------------------         
[Guid("d345c3dc-825e-4be7-b129-2b3d00a7a2a7"),
ComVisible(true)]
public interface INetSortedList : IEnumerable

     [DispId(-4)] new IEnumerator GetEnumerator();  //Iterator
     [DispId(1)]  void Add(object key, object value);
     [DispId(2)]  int Count{ get; }
     [DispId(3)]  void Remove(object key);
     [DispId(0)]  object this[object key] {get ; set; } //Default Property

}
 //Events Interface
[Guid("4b1f6f84-c971-410a-8667-f5611f632b33"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
ComVisible(true)]
public interface INetSortedListEvents
{
}
 //Class Implement the Class Interface
[Guid("1ca3e210-b5e3-458a-9175-002b7ff3274c"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(INetSortedListEvents)),
ComVisible(true)]
public class NetSortedList : INetSortedList
{
     private SortedList _sortedList;
   
     public NetSortedList() {  _sortedList = new SortedList();  }
     public IEnumerator GetEnumerator() { return _sortedList.GetEnumerator(); }
     public void Add(object key, object value) {  _sortedList.Add(key, value);}
     public int Count {  get { return _sortedList.Count; }  }
     public void Remove(object key) { _sortedList.Remove(key); }
     public object this[object key] {
         get { return _sortedList[key]; }
         set { _sortedList[key] = value; }
    }
}
----------------------------------------------------------------------------------------------------------------     
Please note that

 a) the indexer   "Employee this[int index] { get; set; }"    will be seen in VB6.0 as
    a default Item property so that you can do:  list(1) or list.Item(1).
    Its DispId must be set to = [DispId(0)] to work as default Property
b) System.Collections.IEnumerator GetEnumerator(); allows for the "for each" loop in VB 6.0. It

    must have a   [DispId(-4)]
    You cannot use in the interface a Generic enumerator like "List.Enumerator"  which is of type

    System.Collections.Generic.List.Enumerator. You must use the non generic type 
    System.Collections.IEnumerator
    The IEnumerator GetEnumerator() return in COM a IEnumVariant so it can be passed to NewEnum in

    VB  6.0
   

    Public Function NewEnun As IUnkonwn    
        NewEnum = obj.GetEnumerator()
    End Function


   In addition the "Current" propety of the Enumerator must return a type COM compatible,
   otherwise the For Each Loop will work but you will not be able to access the Item of the collection. For

   example for the code above the Current property will return a DictionaryEntry type that is not supported by
    COM. A work around to this is to do the following  
 
     ICollection keys = _sortedList.Keys;
     return (IEnumerator)keys.GetEnumerator();

     This way the "Current" property will return the Keys in the collection and not the DictionaryEntry Object.
     Another way around is to write your own Enumerator.
c) The collection is 0 based.
d) You could have the NetSortedList to implement System.Collections.Generic.ICollection


10) To make C# create and register the typelibrary go to
Project/Properties/Build tick Register for COM interop. (Reccomended Choice)
Otherwise you need to use REGASM. (asembly registration tool). The Assembly Registration tool reads
the metadata within an assembly and adds the necessary entries to the registry, which allows COM clients to create .NET Framework classes transparently. This utility is necessary when you need to expose to COM e .exe (winform application) .net assembly. You can create a type libray in this way. MyAssembly.dll can be also MyAssembly.exe


REGASM
/codebase MyAssembly.dll /tlb:MyAssembly.tlb
To unregister just do
REGASM /u MyAssembly.dll /tlb:MyAssembly.tlb


As a note. When you add a COM dll to a project, C# calls REGASM for you, create a tlb file out of the
COM dll and put it in your project folder
For more details see the  session DEPLOYMENT
 

11) It is important that you do not check the   Project/Properties/Application/Assembly Information...
Make assembly COM-Visible. This option will set
[assembly: ConVisible(true)]
in the AssemblyInfo.cs file
Which will make all the Class in the Project COM visible. This in practice will make C# to generate
new Guids for each class on which we did not specify a Guid attribute each time we recompile, causing a registry bloat.
It is much better to set
[assembly: ConVisible(false)], and use ComVisible(true) at class level to specify which class should be
visible for COM interop
12) The assembly should be given a strong name.
Go to Properties/Signing. Tick Sign the assembly check box.
Then go to Choose a stroing name key file
Choose a name  ex   "MyLibrary_COM_Key"
This will create a MyLibray_COM_Key.snk file in the project folder.
The assembly is now signed with a strong name.
-----------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------
DEPLOYMENT
To deploy the Dll we need to register the .net assembly of a computer for COM interop.
To register it manually we have two ways
01) The dll assembly will reside on a specific folder decided at the moment of the assembly registration
You need to type the following commands:
regasm /u "FullPath\MyLibray.dll" /tlb
regasm /codebase  "FullPath\MyLibray.dll" /tlb
 
  ragasm is located in  c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe
  The first line unregister MyLibrary.dll and its type library
  The second line register  MyLibrary.dll create the type libary  and register them in the COM registry
  the /codebase option saves in the regestry the path of the dll
  This is exactly what the option "Register for COM interop" Does
  If we do not put the /codebase option, VB6.0 will complain that it  cannot find the dll file
02) The dll assembly will reside in the GAC c:\WINDOWS\assembly. The assembly MUST be strongly named if you want  to install it in the GAC.
  You need to type the following commands
 
  regasm "FullPath\MyLibray.dll" /tlb
  gacutil /if "FullPath\MyLibray.dll"
 
  The first line register the assembly for COM interop. Please note that we must leave out /codebase, because
  we will put the dll in the GAC.
  The second line, registers MyLibray in the GAC.
  This mean that all the application will look at the GAC when we run the assembly
 
  To unregister we do
 
  regasm /u "FullPath\MyLibrary.dll" /tlb
  gacutil /uf MyLibrary
    
  To Remove from the GAC a specif version
 
  gacutil /uf MyLibrary , Version=1.1.0.0
   
  Please note the we do not use neither the FullPath nor the .dll/.exe to remove an assembly from the GAC
  in addition each .NET Framework has its own gacutil.exe. The one for .NET 3.5 can be found here
"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\gacutil.exe"
     
Installing the assembly in the GAC ensures that multiple versions of the component can exist side-by-side.
An additional hard requirement for COM is that you change the GUIDs of the public interfaces and classes
when you change their definitions.  Failing to do so will wipe out the registry info for the old component
and will make old client programs that have not been recompiled with the new component crash and burn.
  A problem better known as DLL Hëll.
  if We need to change the interace of our COM component we need to
  1) Change the version number of the assembly. (Do not change the strong key name of file)
  2) Change the GUIDs for both the Class and interface of the class that has been changed

Changing them all could be better to avoid trouble when they depend on each other.

No comments:

Post a Comment