Although this is a long question the coding and testing part should be really easy to reproduce.
I have created two separate Class Libraries in C# and I think I am running into a name collision problem caused by existing registry keys from my previous projects and trials.
Here are my two classes:
using System;
using System.Runtime.InteropServices;
namespace Test
{
[InterfaceType(ComInterfaceType.InterfaceIsDual),
Guid("ED5D264B-1D80-4A5D-9C14-8297D90B7037")]
public interface ITest
{
// body
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("8B261B92-8EC5-4CDC-A551-67DEB42137FF")]
[ProgId("Test.TestClass")]
public class TestClass : ITest
{
// body
}
}
and
using System;
using System.Runtime.InteropServices;
using ADODB;
namespace Test
{
[InterfaceType(ComInterfaceType.InterfaceIsDual),
Guid("ED5D264B-1D80-4A5D-9C14-8297D90B7037")]
public interface IConnection
{
// body
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("8B261B92-8EC5-4CDC-A551-67DEB42137FF")]
[ProgId("Test.Connection")]
public class Connection : IConnection
{
// body
}
}
I have Exposed .Net Components to COM like this:
In order to access the assemblies from Excel I have added the ADODB references to the assembly, ticked make assembly COM visible and register for com interop. Also, I've added references to each *.tlb file(2 files for two projects) so I can access them using an early binding and use VBA Intellisense.
I have followed the same procedure on another machine and I can use early binding using the Connection as class.
I am thinking there are some old registry keys I haven't deleted on my original machine which will not allow me to use Connection as the class name in VBE. I've manually scanned my registry and deleted everything I could think of related to my project.
I have also deleted the project entirely and used a 3rd party software to scan registry for missing dlls however that didn't help:/
Removed all previously registered GUIDs and applied new ones each time I created a new Project (just in case)
Created new projects using different namespaces and class names (using ADODB;) I haven't been able to use early binding yet like this Test.Connection therefore I am assuming I have a name collision problem. I am suspecting the name class Connection to be causing it although I am not 100% sure.
The Test.TestClass namespace in VBA:
I can declare and use instances of the TestClass type in two ways using early binding:
Dim x as Test.TestClass
Dim x as TestClass
Now going into VBE Object Explorer F2 the TestClass is properly displayed in comparison to other libraries and general idea of using COMs.
However, when I want to use the Test.Connection library I am unable to use early binding following the same pattern as TestClass because the generated *.tlb file automatically changes(renames) the ProgId's. So, instead I have to bind it like this
Dim x As Test.Test_Connection
Dim x As Test_Connection
and the Object Explorer displays the names using _ (underscores) and not . (dots), which is easy to explain why this happens - keep reading :)
As it stands I am sure it is not the VBE environment that changes the names to avoid collisions. It is the VS' *.tlb generator.
I went to the assembly folder and opened both *.tlb files in Notepad++. I can clearly see that the *.tlb for the Test.Connection library already includes the names with the _s unlike the Test.TestClass which has .s
I have tried to manually edit the *.tlb file but as its a mixed binary file it takes some effect but also causes Excel to stop responding in some weird ways so I have to avoid this method.
I think I have explained well what the problem is and where it comes from. Now my question is: Are there any attributes to use in C# code to tell the *.tlb generator not to override my ProdIds? Are there any alternative ways of manipulating *.tlb files? Is this issue a name collision and is it avoidable without changing the name of Connection class?
I'm sorry for such long question but I have been digging and digging for almost a week now and I still cant solve this.
Note: In VBA ( or VBE Object Explorer ) using IntelliSense ctrl+space it does not seem that either Connection or Recordset have been used. Since they are not already reserved in the VBE environment I recon it has to do with my library itself.
As a reference to why this issue has been raised here, please see VBA equivalent to C# using or VB.NET imports creating aliases
Thank you very much for your time!
Do avoid focusing on the ProgId. You are not actually using it, the dialogs that you made a screenshot of show the actual class names, not the ProgId.
Getting the class name renamed to "Test_Connection" is normal behavior for the type library exporter. It will do so whenever it detects a conflict with another interface or class name that has the same name. You are certainly increasing the likelihood of this happening by also having a dependency on ADODB, it also has a Connection class. A very trivial solution is to simply rename your own type.
Your code snippet cannot reproduce this problem. But of course it is incomplete, we can't see what you are really doing in the code. You'll bring in the dependency on ADODB if any of your public methods use a type from this type library. Also note that there are non-zero odds that this will happen by accident. You might have written a method that intended to use your own Connection type but the compiler resolved it to the ADODB type.
An essential tool to debug this is Oleview.exe, run it from the Visual Studio Command Prompt. First create the type library for your C# assembly with Tlbexp.exe. Then use File + View Typelib, you'll see the content of your type library expressed in the IDL syntax. You'll have little trouble recognizing the mapping of your C# types to the IDL declarations.
Pay attention to the importlib directives at the top of the file. They should look like this:
// TLib : // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
There should only be those two. The first one imports the .NET types, defining _Object. The second one imports standard COM types, like IDispatch. If you see additional ones here then you increase the odds of a name collision.
This IDL also gives you a way to solve the problem, in case it is unsolvable, you can edit it to name the types the way you want them. Save it to a .idl file. And compile it with midl.exe /tlb to generate a type library with your preferred names. Do note that this is not something you want to have to do often.
Related
I have the absurd situation (don't blame me, it is third party software) where I need to have two references (Erp.Contracts.BO.Quote and Erp.Contracts.BO.SalesOrder), but the type Erp.Tablesets.QuoteQtyRow is defined in both assemblies!
How do I use them in code?
void Absurdity()
{
Erp.Tablesets.QuoteQtyRow qqr_Quote = null; //<-- my intention is to use the one from the quote assembly here.
Erp.Tablesets.QuoteQtyRow qqr_SO = null; //<-- my intention is to use the one from the sales order assembly here.
}
The compiler throws an error. Namely: "The type 'Erp.Tablesets.QuoteQtyRow' exists in both assemblies."
EDIT: LIMITATIONS:
I do not have the flexibility of using an extern alias as provided in this answer Class with same name in two assemblies (intentionally). I am limited by the environment supplied by the third party software. I essentially need a way to make the distinction within the body of a method.
I understand I can avoid this problem altogether by using the dynamic keyword, but I am looking for a possible strongly typed solution.
There may not be a solution, but I want to exhaust all my resources before I give up on the problem.
Epicor ERP uses a tool to put together tables from the DB into datasets, and then on into Business objects. This business object is described in the contract assembly, but as you have found when you use two business object that references the same table you run into problems. This is more commonly an issue with the SerialNumber tables.
I understand from your notes that you are providing method body code in a Method Directive or data Directive within the Epicor ERP application. This is entered on the client and stored in the database but generates code on the server in the Deployment\Server\BPM\Sources\BO folder and is compiled to the Deployment\Server\Customization\BO folder.
There is no way to specify an alias for the referenced DLL in the "Execute Custom Code" workflow item of the BPM designer. The fix is requested in SCR 148549. There is no project file for you to edit, and even if there was every time the BPM was enabled and disabled it would be regenerated.
However, if you use the "Invoke External Method" workflow item, then you can build your own dll and put it in the Deployment\Server\Customization\Externals folder. To do that:
Click Actions > Create Programming Interfaces for your method in Method Directive Maintenance for your BPM and copy the code.
Create a new Class library project in Visual Studio
Paste the copied code into the .cs file
Add Assemblies - Framework references:
System.Data.Entity
System.ServiceModel
System.Transactions
Add file references to
Bin\Epicor.ServiceModel.dll
Assemblies\Epicor.Ice.dll
Assemblies\Epicor.System.dll
Assemblies\Ice.Data.Model.dll
Assemblies\Erp.Data.910100.dll
And add a reference for the BPM's BO i.e.
Assemblies\Erp.Contracts.BO.Quote.dll
Ensure all the references have Copy Local set to false.
Inherit from Ice.ContextBoundBase<Erp.ErpContext>
Add a constructor that takes a context public MyQuote (Erp.ErpContext ctx) : base(ctx){ }
You can't quite copy and paste the "Execute Custom Code" body in as you won't have access to the tt row variables, these are all in the ds.
I registered a COM DLL using regasm.exe, and now I'm trying to write a VBA script that uses a class from the DLL. The DLL is ExcelDataReaderLibrary.dll. In the C# source, the class is described as follows (includes code from this library):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Data;
using Excel;
namespace ExcelDataReaderLibrary
{
public class ExcelDataReader
{
public void readSheet(string filePath,string sheetName,string outPath)
{
// code for method here
}
}
}
My assembly.cs file includes the following:
[assembly: ComVisible(true)]
[assembly: Guid("b1e78f8f-9ab0-46d8-beac-b843656aacdb")]
When I open the VBA Editor and go to References, I see a reference for ExcelDataReaderLibrary. Note that the file associated with this reference is ExcelDataReaderLibrary.tlb, not ExcelDataReaderLibrary.dll. After I check this reference, I want to create and use an ExcelDataReader object in VBA as follows:
Sub x()
Dim xyz As New ExcelDataReaderLibrary.ExcelDataReader
xyz.readSheet "c:\mypath\testfile.xlsx", "Sheet1", "c:\outputPath"
End Sub
The object is successfully created, but readSheet gives this error:
Automation error
The system cannot find the file specified.
Also, there is Intellisense for the ExcelDataReaderLibrary namespace, but there is no Intellisense for the ExcelDataReader object. I guess my class is registered but not its method--do I have to do something different with the Guid? How can I call the method from my VBA code?
For the first part of your question, the problem is that mscoree.dll needs to find your assembly from the location/context of the calling process, and your object's assembly is not usually in a folder that your process (technically, .NET fusion) will look in. To tell .NET where to find the assembly, you have two options:
Use the /codebase parameter of REGASM (as Hans mentioned), which leaves a hint on the registry that points to the assembly's location; or
Strong-sign your assembly and add it to the GAC, where mscoree.dll will always look and find it.
Notice that all the dependencies of your assembly must be equally "findable".
I must warn you that Microsoft very sternly discourages people from using the /codebase technique. They argue that it is designed as a development/debugging technique and that it should not be used in production mode. I'm not sure I fully understand their rationale. You should probably look into the documentation for REGASM and other references in MSDN and make up your own mind about this. I don't personally find adding my objects to the GAC any more onerous than using /codebase.
On the second part of your question, about not getting IntelliSense: your problem is that, by default, COM type libraries generated by .NET expose pure dispinterfaces (they implement only IDispatch, and the type library won't even list the dispinterface members). In VB6 terminology, your classes are exposed as objects, and all method names and parameters must be determined at run-time.
The generation of the COM class is controlled by an attribute applied to your class, called ClassInterfaceAttribute.
Here are the options:
ClassInterfaceType.AutoDispatch: This is the default and so it applies currently to your class. Your class is exposed as a pure dispinterface, and can only be late-bound. No IntelliSense. The TLB doesn't even list the dispinterface members, so a late-bound client cannot ever cache any details about what your class exposes.
This allows maximum flexibility to add, change and remove members with impunity, at least insofar as not hard-crashing the client is concerned. I call this "scripting mode".
ClassInterfaceType.None: This is the stricter mode, that asks you to expose your object more explicitly, kind of like you would have done in IDL/C++. You are expected to declare one of more interfaces with the methods that you want to expose, and make the class explicitly inherit from the interface(s). If you inherit from more than one interface, the first one will be picked as the [default] interface, but you should probably designate one explicitly via the ComDefaultInterfaceAttribute. If you don't inherit from any interface, your CoClass will inherit straight from IDispatch. (All .NET classes exposed as COM objects are exposed via IDispatch).
This is my preferred mode, but I'm more of a traditionalist when it comes to COM programming. You do get IntelliSense, as long as you do declare and inherit explicitly from an Interface. Obviously, you only get to call the members listed in the Interface. I call this "Strict mode" or "IDL mode".
ClassInterfaceType.AutoDual This produces a COM interface automatically for you, one that includes all the details of the methods you are exposing. That means you get IntelliSense and you don't have to worry about creating an explicit interface. However, versioning is a royal pain. You have to be very careful to stop all clients before recompiling and/or re-registering your object, or you will be in a world of hurt if your method signatures changed in any way. I call this "VB6 mode", because to me it looks a lot like what VB6 did for you (COM versioning on VB6 is also a royal pain).
Microsoft also strongly discourages people from using AutoDual, I suppose because changes to the generated interface can happen more easily without you noticing. I haven't actually had a chance to use it yet, but I'm not sure that it is that much more dangerous than None.
In summary: If you want to get IntelliSense, you need to apply to your class either [ClassInterface(ClassInterfaceType.None)] (and put your methods in an interface that you explicitly implement), or [ClassInterface(ClassInterfaceType.AutoDual)]. Either way, you have to be very careful to stop your clients (and maybe even "removing" and "re-adding" the references) before making changes to your assembly.
At all the companies I have worked at I end up championing a core set of libraries that do nothing more than enhance and extend the .net libraries. Usually I have the namespaces such that they start with our company name but the sub namespaces mirror those of the System namespace.
Foo.IO;
Foo.Web
What I plan to do is take this one step further and replace the company namespace with the system namespace so that you only have to have the one using statement and thus have a better enhancement to the core library.
namespace System.IO
{
public static class StreamExtensions
{
...
}
}
The actual question
Now I know that this is possible, Microsoft do it in their own libraries and I have seen it done in other third party libraries but what I want to know is what, if any, are the long term implications of doing this such as a class name conflict in later versions of .net? Has anyone done this and had to handle a complication that has broken the simplicity of just being able to add an assembly reference?
UPDATE
Unfortunately this has turned into more of a debate of whether you should or should not do this which probably belongs over on Programmers. Indecently there is another SO question which does ask this but that was not the point of the question.
I wanted to know if there is a scenario that would crop up further down the road that would cause compilation errors or a strange behavior. The only two arguments that have come up is.
Microsoft adds a method to an object that matches the signature of extension method in the library but this is a mute point as it would make no difference to what namespace the extension method lives in as the implementation on the object would take precedence.
Someone else does the same thing in their third party library and we have a name clash. This is more likely and something we already have to deal with where third party libraries ILMerge other libraries into their assembly.
Just to be clear this is a stand alone library, it is for in house use, not to be made available externally and is there to extend the existing System libraries through Extension methods.
I would suggest do not do this. System namespace is .NET Framework namespace, if you want to customize classes from that namespace, make it explicit in your code.
That means make the customized class part of you custom namespace.
Do not mess up the things.
This may be a little off-topic, but in reference to the alternative approach you mention:
Usually I have the namespaces such that they start with our company name but the sub namespaces mirror those of the System namespace.
I've had some issues with that approach.
My company name is Resolv - as such, a lot of the stuff I write ends up going into a namespace in the form of Resolv.<ProjectName> (the rest will be <ClientName>.<ProjectName>).
I started building my library of extension methods, static classes and so-on in a namespace called Resolv.System
However, that created namespace resolution issues when using "fully qualified" type names that start with System (e.g. var myVar = new System.Collections.List<int>();).
While I would never use a fully qualified name in that particular case, it's something I do on occasion if the type I'm referencing is the only one from that namespace in the entire code file (in which case adding a using isn't warranted) - or on those occasions when two namespaces imported (with using statements) contain conflicting type names. Automated code generation tools (like resharper) often add those sort of references when there isn't an appropriate using statement too.
If I'm working on code within some namespace anywhere inside Resolv (e.g. Resolv.MyInternalProject) - and I put in what should be a fully qualified name - confusion ensues because of the Resolv.System namespace. The compiler walks back up the current namespace, gets to Resolv and then finds Resolv.System. That means - for example - that new System.Collections.List<int>() will attempt to use the non-existent class Resolv.System.Collections.List<int>().
Of course, I can get around that by using the form var myVar = new global::System.Collections.List<int>() but that's ugly and sort of a pain).
I've opted instead to include a "project name" in my extensions namespace tree, so now instead of Resolv.System I have Resolv.Extensions.System. From there the child namespaces mirror the System namespace (e.g. Resolv.Extensions.System.IO). That way I can have better control over whether I want to have System.xxx.xxxx references refer to my extensions, or the .net ones from any given code file (and it's only one using statement to add to my code files when I want to "turn on extensions").
Of course, I'll still have the System.xxx.xxx namespace confusion when working on code inside the Resolv.Extensions namespace - but that won't bug me on a daily basis! :)
What I plan to do is take this one step further and replace the
company namespace with the system namespace so that you only have to
have the one using statement and thus have a better enhancement to the
core library.
I don't understand how this will enchance the core library. What happens when Microsoft adds the same method to the String class and it does something entirely different? This is the reason they should be in their own namespace.
Now I know that this is possible, Microsoft do it in their own
libraries and I have seen it done in other third party libraries but
what I want to know is what, if any, are the long term implications of
doing this such as a class name conflict in later versions of .net?
The long term implications is if Microsoft adds the same method to a class as the extension method you create.
Has anyone done this and had to handle a complication that has broken
the simplicity of just being able to add an assembly reference?
I don't understand the reason you want to reduce the amount of references. You gain nothing by doing this, having utility methods in their own namespace and class is a valid design decision, people assume they will be seperate and not part of a Microsoft namespace.
It is a valid statement but the question about what are the
implications. Other people, including myself, have shied away from
doing this because of a "gut" feeling about messing with someone
else's namespace but no one has said do not do it because of this. If
you have a specific factual reason I would love to hear it.
The implication is a developers assumptions that the System namespace is filled with only Microsoft code.
I have declared a COM visible class in C#. The code is as follows:
[ComVisible(true)]
public class AComVisibleClass : TheParentClass
{
public bool SomeFunc(string id)
{
return true;
}
}
This class is instantiated by a factory class, also COM accessible.
But if I try to access in a VB script file, a DISP_E_UNKNOWNNAME exception is thrown.
This is a new class on a pre-existent library we have here at work. All other classes are accessible through COM. The whole library is compiled into a single assembly file. I have registered the new assembly using regasm, but I still get this exception.
I've tried to debug the COM call using VS2008. The factory class seems to be able to instantiate the AComVisibleClass. The aforementioned exception is thrown only when the factory tries to execute SomeFunc.
I know this may sound a little(?) bit vague, but I cannot expose the real code here. If someone need more information, please ask me.
I can think of three possible reasons for this problem:
Reason 1: Wrong name used in CreateObject:
I suppose that your VBScript code calls
something like
this:
Set obj = CreateObject("MyLibrary.AComVisibleClass")
If this is correct, then please open the registry editor and check whether the HKEY_CLASSES_ROOT key contains a subkey called MyLibrary.AComVisibleClass.
If it does not, then your library name possibly is different than you expected. Search the registry for AComVisibleClass to find the correct library name.
Reason 2: 64-bit issues:
If the problem happens on a 64-bit operating system, the reason could be that your VBScript runs as a 32-bit process and the C# COM DLL is 64-bit or vice versa.
Reason 3: Wrong function name:
You might be using the wrong function name in the script, e.g. obj.SomeFnc(1) instead of obj.SomeFunc(1), or the function name you have chosen is a reserved keyword in VBScript or it contains unusual characters.
Sounds like you need to support IDispatch.
Check out Does C# .NET support IDispatch late binding?
edit
This answer is likely wrong, and I may yet wind up deleting it. For now, it seems to add value, so I'll let it stay.
In the Java and Python world, you look at a source file and know where all the imports come from (i.e. you know in which file the imported classes are defined). For example:
In Java:
import javafoo.Bar;
public class MyClass {
private Bar myBar = new Bar();
}
You immediately see that the Bar-class is imported from javafoo. So, Bar is declared in /javafoo/Bar.java
In Python
import pythonbaz
from pythonfoo import Bar
my_bar = Bar()
my_other = pythonbaz.Other()
Here, it is clear that Bar comes from the pythonfoo package and Other is obviously from pythonbaz.
In C# (correct me if I'm wrong):
using foo
using baz
using anothernamespace
...
public class MyClass
{
private Bar myBar = new Bar();
}
Two questions:
1) How do I know where the Bar-class is declared? Does it come from the namespace foo, or bar, or anothernamespace? (edit: without using Visual Studio)
2) In Java, the package names correspond to directory names (or, it is a very strong convention). Thus, when you see which package a class comes from, you know its directory in the file system.
In C#, there does not seem to be such a convention for namespaces, or am I missing something? So, how do I know which directory and file to look in (without Visual Studio)? (after figuring out which namespace the class came from).
Edit clarification: I am aware that Python and/or Java allow wildcard imports, but the 'culture' in those languages frowns upon them (at least in Python, in Java I'm not sure). Also, in Java IDEs usually help you create minimal imports (as Mchl. commented below)
1) Well, you can do the same thing in Java too:
import java.util.*;
import java.io.*;
...
InputStream x = ...;
Does InputStream come from java.util or java.io? Of course, you can choose not to use that feature.
Now, in theory I realise this means when you're looking with a text editor, you can't tell where the types come from in C#... but in practice, I don't find that to be a problem. How often are you actually looking at code and can't use Visual Studio?
2) You can use the same convention in .NET too, of course - and I do, although I don't have empty directories going up the chain... so if I'm creating a project with a default namespace of X.Y, then X.Y.Foo would be in Foo.cs, and X.Y.Z.Bar would be in Z\Bar.cs
That's also what Visual Studio will do by default - if you create a subfolder, it will create new classes using a namespace based on the project default and the folder structure.
Of course, you can also declare types in any old file - but mostly people will follow the normal convention of declaring a type with a corresponding filename. Before generics made delegate declarations rarer, I used to have a Delegates.cs file containing all the delegate declarations for a particular namespace (rather than having a bunch of single-declaration files) but these days that's less of an issue.
1) You're right. There is no "direct" way to know where your class comes from at first glance, but, as you said, you can jump to it in the IDE. But declaring the class this way is just the shortest way to do it. If you wanted, and assuming your Bar class comes from the Foo one, you could declare it
private foo.Bar myBar = new foo.Bar();
This way it would help knowing where your classes come from at first look.
2)When you add a reference to your class, the Add reference windows gives you the informations you are looking for.
And if you want to know where they come from after you declared it, there is a window named "Solution Explorer" where you can find these informations, under the "References" tree node.
You can set it to be always visible (which it is by default)
For Java and Python this is indeed an issue with conventions - import the class you need, not the entire package using wildcards.
In C# you can't do a using directive for the specific class you want, since it only works for namespaces (as the following error reveals). It would seem that C# remained true to the C++ concept of namespaces, and merged it with the #include directive for one easy way of referencing external classes.
using System.Net.Sockets.Socket; // Gives the following error:
// A using namespace directive can only be applied to namespaces;
// 'System.Net.Sockets.Socket' is a type not a namespace
And about the double Bar deceleration, it's simple - if the compiler has no way of knowing it will give an error:
using Foo; // Has class Bar {}
using Goo; // Has class Bar {}
Bar b = new Bar(); // Gives the following error:
// 'Bar' is an ambiguous reference between 'Foo.Bar' and 'Goo.Bar'
How do I know where the Bar-class is
declared? Does it come from the
namespace foo, or bar, or
anothernamespace? Visual Studio allows
me to jump there, of course, but what
if I am just taking a quick look at a
source file in my editor?
Essentially, you don't - but IntelliSense is helping. You cannot actually be sure by just taking a quick glance at the code, but you can hover over the symbol with your cursor, for example. But this is also possible in Python:
from foobar import *
from bazbaz import *
a_bar = Bar()
Where does Bar come from now?
In C#, there does not seem to be such
a convention for namespaces, or am I
missing something? So, how do I know
which directory and file to look in?
(after figuring out which namespace
the class came from).
No, assemblies do not correspond to directory structures, which, IMHO, is a good thing. The solution explorer is offering a view of all the references added to your project. These references being assemblies, have a concrete representation as a PE file somewhere on your machine. You can easily look at the properties of a reference to see where the physical file is located.
Edit: In order not to contradict other answers in this thread and create confusion: What I mean by saying assembly names do not correspond to directory names is that it is not actually enforced.
In C#, there does not seem to be such a convention for namespaces, or am I missing something?
I don’t know about other projects but I’m pretty sure that in every .NET project I have worked on, we used this convention, i.e. namespaces always corresponded to folder names (except for the outermost namespace(s) which correspond to the assembly from which the namespace comes).
Usually, the tooltip when you mouse-over the type name reveals some extra information. Else you could always right-click the type name, and 'Go to definition'.