I have a development computer with Windows 8 and Visual Studio 2012, and I was planning on doing some tests with MSMQ. Everybody will tell you that you should just (in Visual Studio) open the references tab and add a COM reference to Microsoft Message Queue, but (yes there is always a but) the component was not present.
I looked for it in C:\Windows\System32 and C:\Windows\SysWOW64 and nothing there was nothing called mq*.tlb. So I found this thread in StackOverflow and it was pretty obvious :| I just had to go to Add Programs \ Turn on Windows Features and select it:
Figure 1. Adding MSMQ COM components
And after that I could find a file called C:\Windows\System32\mqoa30.tlb and added that reference.
Well tonight while I was deleting some spam comments from my blog and watching Dr. Who with my wife, I found a rather interesting comment.
So the story was:
First there is a VB6 DLL that had a class called Class1 with code like the following:
public type emprecord
name as string
end type
Public Sub Fn(T()as emprecord)
MsgBox "The silence is comming said Prisoner 0"
End Sub
When this little dll was called from a VB.NET big brother
Dim test as new prj.class1
Dim em(0) as prj.emprecord 'able to create it no problem
em(0).name="hello"
test.fn(em) ' here gives error
An error ocurred... well this is not very document issue with the TLBIMP tool which creates the interop assemblies. See StackOverflow Answer. The workaround is to right click on your type library, select properties, and change Embed interop Types to false.
After that you will be able to call your function.
VB6 Application where STAThread. And that is the reason that Winforms applications are
by default STAThread. Using MTAThread causes problems with some ActiveX Controls.
However STAThread has a nasty implication
see: http://social.msdn.microsoft.com/Forums/en/clr/thread/835db88e-db51-4f83-bd4f-a10d126effa6
"inside a STA thread, the finalizer thread must reenter the STA thread in order to finalize the component.
If the STA is blocked and isn't pumping, the finalizer has to wait in line until it does"
This can then cause leaks of components affecting the memory use.
"To get around this issue, you have some options (from best to worst), e.g.:"
1) Create your components in an MTA. ... Unless you have an explicit reason to use an STA, you shouldn't. I realize that Visual Studio adds these to some entrypoints automatically for you.
For example, most GUI applications have to start life inside an STA, e.g. WinForms, but Console applications" or services "certainly do not."
"2) Deterministically release your resources. If you are using components which implement IDisposable, wrap them in
a C# 'using' statement or call Dispose() on them explicitly when you're done.
RCW's done have Dispose on them. You can consider doing a Marshal.ReleaseComObject on them directly,
but realize that this can cause problems if you're not really done using the COM object."
"3) Use another form of blocking to prevent the primary thread from exiting."
"Chris Brumme writes about this" (COM Apartments)
" at http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx; caution: that's a fairly lengthy post"
In VB.NET if you want to make your interfaces available thru COM and make sure that its parameters are of a certain type you have to use the MarshalAs attribute. For the return type it is a little tricky because it has to be added after the As Keyword.
<ComVisible(True)> _
<Guid("15D492C7-CD14-4239-B98D-689F329EEDA4")>
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface MyCOMInterface
Function FooReturningShort(ByVal data As Integer, <MarshalAs(UnmanagedType.U2)> ByVal shortData As short) As <MarshalAsAttribute(UnmanagedType.U2)> Short
End Interface
What is the ROT?
“Using ROT (Running Object Table) is a great way to establish interprocess communication between two windows applications. From a purely logical aspect, one application registers a pointer to an instance of a class in the ROT, the other one gets a pointer pointing to the same instance of the registered class and therefore can use the same instance of the class via this pointer. The class that is registered has to be a COM class, otherwise it can be written in any language. The application that will retrieve the pointer from the ROT can be written in any language that can use COM, as ROT gives a pointer to a COM interface.”
Can it be implemented in .NET?
Sure a .NET application can be exposed thru COM and then its pointer can be gotten and consumed by other applications querying the ROT.
And excelent example can be found here: http://www.codeproject.com/KB/COM/ROTStuff.aspx
As always it has its caveats. Be careful.
Obvious replacement?
Well if what you want is (Interprocess Communication) IPC,there are several options in .NET :
* Classical .NET remoting which is very simple and stable to
* Named Pipes see an example here http://bartdesmet.net/blogs/bart/archive/2007/04/12/getting-started-with-named-pipes.aspx
* or WCF with Named Pipes, an example here http://www.codeproject.com/KB/WCF/WCF_CommOptions_part1.aspx
WCF can be an interesting option specially if we were doing things like DCOM and Remote monikers.
Typical ASP applications were built as a layer of simple ASP with some
COM+ components that did the heavy lifting.
Now, when you migrate your ASP application to ASP.NET and you also migrate your
COM+ components to .NET then you might encounter some issues with security.
One common issue is impersonation.
Sometimes the COM+ were created to use the current user account.
And there is a slight
difference between ASP and ASP.NET:
“Impersonation is when ASP.NET executes code in the context of an authenticated and authorized client. By default, ASP.NET does not use impersonation and instead executes all code using the same user account as the ASP.NET process, which is typically the ASPNET account. This is contrary to the default behavior of ASP, which uses impersonation by default. In Internet Information Services (IIS) 6, the default identity is the NetworkService account.”
That will cause errors in your ASP.NET application like:
To solve this issue you must use ASP.NET Impersonation, and to enable impersonation go to the web.config file and add:
<identity impersonate=”true”/>
For more info on impersonation see: http://msdn.microsoft.com/en-us/library/aa292118(v=vs.71).aspx
In VB6 when you have an ActiveX Library it was very important to use
the BinaryCompatibility setting to make sure that your applications did not break after a change.
So let’s first introduce what is binary compatibility and how to accomplish that in .NET.
Binary Compatibility allows to make changes to your components or COM classes without recompiling
every application you've made that uses the component.
And why do you need it. Why compatibility breaks.
On lets see.
An ActiveX Control or DLL expose Public interfaces.
Those interfaces have all of the properties, methods, events, etc. that you've marked as Public.
In other words, everything you've added that shows in Intellisense while working outside of your component.
Now let's say you have create a class, with two Methods Method1 and Method2
When you compile, VB generates all the COM infraestructure you need for your component.
It defines a CoClass and an interface and an entry for each method.
For a vb class with two methods:
Sub Method1()
End Sub
Sub Method2()
End Sub
It will produce a typelib like:
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>
[
uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
version(1.0)
]
library Project1
{
// TLib : // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface _Class1;
[
odl,
uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B),
version(1.0),
hidden,
dual,
nonextensible,
oleautomation
]
interface _Class1 : IDispatch {
[id(0x60030000)]
HRESULT Method1();
[id(0x60030001)]
HRESULT Method2();
};
[
uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
version(1.0)
]
coclass Class1 {
[default] interface _Class1;
};
};
As you can see in the typelib there are IDs associated to each coclass, interface and
methods. Those IDs are the ones use when you generate the .exe file for your application.
Now if you modify your Class to:
Sub Method3()
End Sub
Sub Method4()
End Sub
Sub Method1()
End Sub
Sub Method2()
End Sub
and you use No Compatibility the typelib after your changes will be:
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>
[
uuid(FE5C56C2-E03A-4DC0-994D-B68543C72A46),
version(1.0)
]
library Project1
{
// TLib : // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface _Class1;
[
odl,
uuid(A3032E1E-52FE-42E0-98FF-84A9DD4FD8C3),
version(1.0),
hidden,
dual,
nonextensible,
oleautomation
]
interface _Class1 : IDispatch {
[id(0x60030000)]
HRESULT Method3();
[id(0x60030001)]
HRESULT Method4();
[id(0x60030002)]
HRESULT Method1();
[id(0x60030003)]
HRESULT Method2();
};
[
uuid(72721504-CC56-4BB9-9447-C7193FE8C02D),
version(1.0)
]
coclass Class1 {
[default] interface _Class1;
};
};
As you can see, now the ids for the methods, CoClass are different, so your applications will return errors like: Error 430 (Automation error, the component dies horribly) or Error 429 (can't create the object at all)
But if you instead used BinaryCompatibility then the typelib for your class will be:
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>
[
uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
version(1.1)
]
library Project1
{
// TLib : // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface _Class1;
[
odl,
uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF),
version(1.1),
hidden,
dual,
nonextensible,
oleautomation
]
interface _Class1 : IDispatch {
[id(0x60030002)]
HRESULT Method3();
[id(0x60030003)]
HRESULT Method4();
[id(0x60030000)]
HRESULT Method1();
[id(0x60030001)]
HRESULT Method2();
};
[
uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
version(1.1)
]
coclass Class1 {
[default] interface _Class1;
};
typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
_Class1 Class1___v0;
};
If you compare now the two typelibs you can see the Method1 and Method2 keep the same ids.
For each version a typedef is generated that will point to the last version. For example adding a Method5 will add new entry like:
typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
_Class1 Class1___v0;
typedef [uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF), version(1.1), public]
_Class1 Class1___v1;
Well that is what binary compatibility does. Now how to achieve binary compatibility in .NET
Binary Compatibility in .NET
Achieving binary compatibility in .NET is really easy. You just need to give more information to
make explicit how your typelib information will be. I will follow an approach as the one I already explained in this post:
http://blogs.artinsoft.net/mrojas/archive/2010/06/23/exposing-c-classes-thru-interop.aspx
Lets take our previous example:
using System;
using System.Runtime.InteropServices;
namespace InteropExamples
{
public class Class1
{
public void Method3()
{
}
public void Method4()
{
}
public void Method1()
{
}
public void Method2()
{
}
public void Method5()
{
}
}
}
In previous posts I had recommended using partial classes and using interfaces to explicitly specify what you what to be seen in COM. This means you start up with something like:
public partial class Class1
{
public void Method3()
{
}
public void Method4()
{
}
public void Method1()
{
}
public void Method2()
{
}
}
[ComVisible(true)]
public interface _Class1
{
void Method3();
void Method4();
void Method1();
void Method2();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(_Class1))]
partial class Class1 : _Class1
{
#region _Class1 Members
void _Class1.Method3()
{
Method3();
}
void _Class1.Method4()
{
Method4();
}
void _Class1.Method1()
{
Method1();
}
void _Class1.Method2()
{
Method2();
}
#endregion
}
Now to make this code binary compatible then you have to make sure that the tlb file generated for your class is almost identical to that generated before. To acomplish that we must make sure that we your methods, interfaces and classes have the same guids and ids. Lets see how:
using System;
using System.Runtime.InteropServices;
namespace InteropExamples
{
public partial class Class1
{
public void Method3()
{
System.Windows.Forms.MessageBox.Show("3 N");
}
public void Method4()
{
System.Windows.Forms.MessageBox.Show("4 N");
}
public void Method5()
{
System.Windows.Forms.MessageBox.Show("5 N");
}
public void Method1()
{
System.Windows.Forms.MessageBox.Show("1 N");
}
public void Method2()
{
System.Windows.Forms.MessageBox.Show("2 N");
}
}
[ComVisible(true)] //This to make the interface Visible for COM
[TypeLibType((TypeLibTypeFlags)((short)TypeLibTypeFlags.FHidden |
(short)TypeLibTypeFlags.FDual |
(short)TypeLibTypeFlags.FNonExtensible |
(short)TypeLibTypeFlags.FOleAutomation))] //This to use the same flags as in previous tlb
[Guid("9BAFD76D-8E6B-439C-8B6D-37260BFA3317")] //This is to make the class have the guid
public interface _Class1
{
[DispId(0x60030000)]
void Method1();
[DispId(0x60030001)]
void Method2();
[DispId(0x60030002)]
void Method3();
[DispId(0x60030003)]
void Method4();
[DispId(0x60030004)]
void Method5();
}
[ComVisible(true)] //This to make the class Visible for COM
[ClassInterface(ClassInterfaceType.None)] //This is to make sure that we have control on interface generation
[ComDefaultInterface(typeof(_Class1))] //To set default interface
[ProgId("Project1.Class1")] //To set ProgId
[Guid("C71C7AB0-552A-4D5D-A9FB-AF33830A697E")] //Maintain same Guid.
partial class Class1 : _Class1, Class1___v0, Class1___v1
{
#region _Class1 Members
void _Class1.Method3()
{
Method3();
}
void _Class1.Method4()
{
Method4();
}
void _Class1.Method1()
{
Method1();
}
void _Class1.Method2()
{
Method2();
}
#endregion
#region Class1___v0 Members
void Class1___v0.Method1()
{
Method1();
}
void Class1___v0.Method2()
{
Method2();
}
void Class1___v0.Method3()
{
Method3();
}
void Class1___v0.Method4()
{
Method4();
}
void Class1___v0.Method5()
{
Method5();
}
#endregion
#region Class1___v1 Members
void Class1___v1.Method1()
{
Method1();
}
void Class1___v1.Method2()
{
Method2();
}
void Class1___v1.Method3()
{
Method3();
}
void Class1___v1.Method4()
{
Method4();
}
void Class1___v1.Method5()
{
Method5();
}
#endregion
}
//This is to keep compatibility with old versions
//we cannot generate a typedef so we will need to add all of the versions
//for BinaryCompatibility
[ComVisible(true)]
[Guid("6B86684C-B3DD-4680-BF95-8DEE2C17AF5B")]
[TypeLibType(TypeLibTypeFlags.FHidden)]
public interface Class1___v0
{
[DispId(0x60030000)]
void Method1();
[DispId(0x60030001)]
void Method2();
[DispId(0x60030002)]
void Method3();
[DispId(0x60030003)]
void Method4();
[DispId(0x60030004)]
void Method5();
}
//This is to keep compatibility with old versions
//we cannot generate a typedef so we will need to add all of the versions
//for BinaryCompatibility
[ComVisible(true)]
[Guid("4A7A3317-BF13-443E-9DB0-2C5EA21F00CA")]
[TypeLibType(TypeLibTypeFlags.FHidden)]
public interface Class1___v1
{
[DispId(0x60030000)]
void Method1();
[DispId(0x60030001)]
void Method2();
[DispId(0x60030002)]
void Method3();
[DispId(0x60030003)]
void Method4();
[DispId(0x60030004)]
void Method5();
}
}
Sadly in .NET you cannot use Interface Inheritance in COM. If there is interface inheritance YOU HAVE TO IMPLEMENT each interface. In the case of code that comes from VB6. VB6 just uses typedefs, so you really don’t know which methods belong to each version. So in the end all versions have all methods.
The other alternative to this method, is just to implement last version. And after generating the tlb, decompile it to an .IDL file add the typedefs and recompiled it. I explained something similar in this post:http://blogs.artinsoft.net/mrojas/archive/2010/05/17/interop-remove-prefix-from-c-enums-for-com.aspx
Ok. I hope this helps you to have an more clear idea of what Binary Compatibility is and how to do it in .NET. I am attaching some sample code. It show an ActiveX library that uses BinaryCompatibility and three version on an aplications that uses the different versions. And also a .NET class library that is equivalent to the VB6 one. HERE
Enjoy.
Either if you migrate your application from VB6 to C# or if you develop a new application in C# something you end up with cases where you need to use your classes in legacy apps. Some of them could have been written in VB6 or could even be VBA macros in Excel applications.
Exposing your .NET classes can be sometimes very easy (you can think is just a matter of putting a ComVisible tag) but in other occasions is not that simple. Specially if your legacy application is using a lot of Late Bound calls like in VBA, so you must make sure that the COM information that you are exposing for your class is exactly what you really want and need.
OK. So I will provide some guidelines or some steps you should follow to provide a consistent COM interface for your .NET Code.
1. First you have to add the [ComVisible(true)] attribute. Don’t think that’s all. Even if in some cases that is enough is better if you take an strict control of want is being generated for your class. Ok Let’s use the following class as an example:
using System;
using System.Runtime.InteropServices;
namespace InteropExamples
{
[ComVisible(true)]
public class MyVerySimpleClass
{
public Class2 CreateANewClass()
{ return new Class2() }
public int GetMyLuckyNumber() { return 15; }
}
public class Class2 {
}
}
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>
[
uuid(370E4AD4-073B-4984-8C7D-5ED027F7B1CA),
version(1.0)
]
library ClassLibrary1
{
// TLib : // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface _MyVerySimpleClass;
[
uuid(E03CCE68-2D55-3576-9DB6-019AAA667A5D),
version(1.0),
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")
]
coclass MyVerySimpleClass {
[default] interface _MyVerySimpleClass;
interface _Object;
};
[
odl,
uuid(D18BEEE1-4425-3AC7-891E-807EC2283731),
hidden,
dual,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")
]
interface _MyVerySimpleClass : IDispatch {
};
};
In this case your class will be expose using all defaults. That is, a progId that will be the <AssemblyName>.ClassName an interface _<ClassName> is generated and the class is exposed only for IDispatch, which would not provide class information if you add the tlb reference to a VB6 or VBA project.
And if you run this code in VB6 you will have a problem like type mismatch when you try to use the method x.CreateAClass because it is returning an object that is not exposed thru COM.
Private Sub Command1_Click()
Dim x As Object
Set x = CreateObject("InteropExamples.MyVerySimpleClass")
MsgBox x.GetMyLuckyNumber
MsgBox x.CreateAClass
End Sub
So my recommendation is to make explicit what you want to expose. Maybe you only need some of the methods to be exposed. Well that is step two.
2. Define a public, ComVisible(true) interface that will define the methods that you want to be exposed thru COM. Sometimes it is better to implement the interface explicitly. I even recommend using partial classes so you isolate the COM stuff from your normal class. If you class is very simple you can leave all COM stuff there.
//It is better to have an interface, because
//you are completely sure what you are exposing or not
[ComVisible(true)]
public interface _MyVerySimpleClass
{
int GetMyLuckyNumber();
}
3. (Recommedation) This is not an obligatory step but I recommend using partial classes.
//Using partial classes allow you to separate all the
//COM plumbing and leave your .NET implementation simple
public partial class MyVerySimpleClass
{
public Class2 CreateAClass()
{
return new Class2();
}
public int GetMyLuckyNumber() { return 15; }
}
3. Make sure your partial class has the following attributes:
[ComVisible(true)] <—This is obvious because you want to use your class in COM
[ClassInterface(ClassInterfaceType.None)] <—This is because your want to take charge or what will be generated in your Typelib (tlb)
[ComDefaultInterface(typeof(_MyVerySimpleClass))] <—This is to indicate the interface that holds your COM visible methods.
[ProgId("InteropExamples.MyVerySimpleClass")] <—To establish which will be the progId not have a generated one
[Guid("{029D468C-8BE6-498f-8A57-3B4B0306BA41}")] <—this is important specially if you are trying to accomplish binary compatibility
Optionally add this attribute [IDispatchImpl(IDispatchImplType.CompatibleImpl)] this is currently marked as an obsolete attribute but it still works and I have found scenarios, specially in some VBA applications where you need this attribute in order to make some late bound calls.
4. And Explicitly implement the interface methods. This is important because some of the return values or arguments might need convertions. For example what can you do if your method returns a DataSet and your Excel VBA script is expecting something like a Recordset (more on this on other posts).
So now you will have a class like:
//Using partial classes allow you to separate all the
//COM plumbing and leave your .NET implementation simple
public partial class MyVerySimpleClass
{
public Class2 CreateAClass()
{
return new Class2();
}
public int GetMyLuckyNumber() { return 15; }
}
//It is better to have an interface, because
//you are completely sure what you are exposing or not
[ComVisible(true)]
public interface _MyVerySimpleClass
{
int GetMyLuckyNumber();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)] //This is to make sure that no automatic generation of COM methods is done
[ComDefaultInterface(typeof(_MyVerySimpleClass))] //This to explicitly establish which is the default interface
[ProgId("InteropExamples.MyVerySimpleClass")]
[Guid("{029D468C-8BE6-498f-8A57-3B4B0306BA41}")]
[IDispatchImpl(IDispatchImplType.CompatibleImpl)]
partial class MyVerySimpleClass : _MyVerySimpleClass
{
#region _MyVerySimpleClass Members
//Explicit implementation is better because it avoids messing your .NET
//class specification. Sometimes when you expose thru COM you can have problem with
//methods overloads. For example you have to have the same method name but differente
//return type. Or you have a collition with an existing member.
int _MyVerySimpleClass.GetMyLuckyNumber()
{
return GetMyLuckyNumber();
}
#endregion
}
And your TLB is now explicit and exposes ONLY what you really really want.
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>
[
uuid(370E4AD4-073B-4984-8C7D-5ED027F7B1CA),
version(1.0)
]
library ClassLibrary1
{
// TLib : // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface _MyVerySimpleClass;
[
odl,
uuid(80D00C45-EE10-3D65-A5FF-42AB7D8F8A71),
version(1.0),
dual,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples._MyVerySimpleClass")
]
interface _MyVerySimpleClass : IDispatch {
[id(0x60020000)]
HRESULT GetMyLuckyNumber([out, retval] long* pRetVal);
};
[
uuid(029D468C-8BE6-498F-8A57-3B4B0306BA41),
version(1.0),
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")
]
coclass MyVerySimpleClass {
interface _Object;
[default] interface _MyVerySimpleClass;
};
};
For more info about BinaryCompatibility see my other posts on Interop.
When you develop applications with remoting, or in some COM + Remoting
scenarios, you could start founding very interesting exceptions.
We had a very unconfortable one. We had an ActiveX that is used in an
intranet Web Page, that uses remoting to instanciate some classes in
the local network.
When we runned outside of the IE, everything seem to work, but running in IE it produced an exception like:
Error : Return argument has an invalid type.
Type : System.InvalidCastException
Source: mscorlib
Source: at System.Runtime.Remoting.Proxies.RealProxy.ValidateReturnArg(Object arg, Type paramType)
at System.Runtime.Remoting.Proxies.RealProxy.PropagateOutParameters(IMessage msg, Object[] outArgs, Object returnValue)
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
Why??? Well what happens is simple, it is having an assembly resolution problem, it is not being able to resolve the type.
We solve the problem adding something like:
1. Find a place in your code to add an event like this (it could be in the Main of your program for example):
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
2. Add a handler like this:
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
System.Reflection.Assembly assembly = null;
try
{
assembly = System.Reflection.Assembly.Load(new System.Reflection.AssemblyName(args.Name));
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(
string.Format(“Problem with resolution of {0} : {1} {2}”, args.Name, ex.Message, ex.StackTrace));
}
return assembly;
}
Well, this worked for us, and I hope that helps you out.
Well recently Kingsley has point me to a lot of useful links to improve the ExtendedWebBrowser. However he found another detail. When in Javascript you do something like a:
window.open(‘url’,’window’,’width=200;height=300’);
Those width and height settings were not being considered in the new window. I researched for I while until I found this great link:
So basicly I follow the sugested code and added logic in my EventSink class:
public void WindowSetLeft(int Left)
{
///Should I calculate any diff?
_Browser.Parent.Left = Left;
}
public void WindowSetTop(int Top)
{
_Browser.Parent.Top = Top;
}
public void WindowSetWidth(int Width)
{
int diff = 0;
diff = _Browser.Parent.Width - _Browser.Width;
_Browser.Parent.Width = diff + Width;
}
public void WindowSetHeight(int Height)
{
int diff = 0;
diff = _Browser.Parent.Height - _Browser.Height;
_Browser.Parent.Height = diff + Height;
}
So now when the window opens it takes the specified width, heigth, left and top.
As always
HERE IS THE UPDATED CODE
As vb6 migration experts in our company we deal everyday with a lot of issues around Interop and serialization.
One important thing to note is the concept of “Bittable Types”. I’m not making up terms. Those terms actually exist. Just see this link in MSDN.
In a few words, a bittable type is a type that has the same representation in managed and unmanaged code.
Why in earth is that important at all?
Because if you are calling that great C++ DLL implemented some years ago that just works ok, you won’t be able to pass a NON-Bittable type because that DLL will expect a binary representation different from that in the .NET virtual machine.
This is also an issue in other scenarios like:
- Serializing content to files
- Sending messages through messaging mechanisms like named-pipes or sockets.
Well, we have just introduced the problem so now let’s think on a nice solution for this problem.
Well Bittable Types are:
The following types from the System namespace are blittable types:
So now let’s look at a couple of non-BITTABLE types
DateTime
To test this differences let’s make a small test in VB6 and write a Date value to a file:
Private Sub SaveDateToFile()
Open "C:\test1.bin" For Binary Access Write As #1
Dim d1 As Date
d1 = "1/1/2009"
Put #1, , d1
Close #1
End Sub
Now let’s make a quick program in Vb.NET
Sub Main()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
Dim val As Long = d.ToBinary()
fw.Write(val)
fw.Close()
Main2()
End Sub
If we compare these files we will have:
So the values are obviously different. This is because VB6 Date are stores with the OLE Automation DateFormat
So let’s change the C# code for something like:
Sub Main2()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test3.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
fw.Write(d.ToOADate())
fw.Close()
End Sub
And now when we compare the files we will have:
So to make your Date values compatible with VB6 format you must user the DateTime method .ToOADate. Now if you are calling a DLL that expects a Date value in the same format used by VB6 then you will have to do this:
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d.ToOADate(), Runtime.InteropServices.GCHandleType.Pinned)
Dim memory_address As IntPtr = handle.AddrOfPinnedObject()
Try
APICall(memory_address)
Finally
d = DateTime.FromOADate(System.Runtime.InteropServices.Marshal.ReadInt64(memory_address))
handle.Free()
End Try
String
Most of the time you wont have to deal with String marshalling because adding marshaling tags to your API call solves most of the problems, but if you arent that luckyly then you might do something like:
IntPtr ptrToStringVar = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(strVar);
try
{
APICall(ptrToStringVar);
}
finally
{
strVar = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar);
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptrToStringVar);
}
NOTE: if you have an API that might return an string with /0 characters you must call the API with System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar,size), if you do that the Framework will take in consideration the size bytes at the ptrToStringVar memory address.
Double and Singles
At least between VB6 and VB.NET the double and single types follows the same format. Well, at least, that is the result of my tests.
Try it yourself, the following shows a simple test for double variables:
VB6
Private Sub SaveDoubleToFile()
Open "C:\test1.bin" For Binary Access Write As #1
Dim d1 As Double
d1 = 1.123
Put #1, , d1
Close #1
End Sub
Sub Main()
SaveDoubleToFile
End Sub
.NET
Module Module1
Sub Main()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Double
d = 1.123
fw.Write(d)
fw.Close()
End Sub
End Module
So you could make an api call in those cases with something like:
Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d, System.Runtime.InteropServices.GCHandleType.Pinned)
Dim ptr As System.IntPtr = handle.AddrOfPinnedObject()
Try
APICall(ptr)
Finally
handle.Free()
End Try
One of our clients wanted to change the CreateObject function migration for a function of their own. So they wanted all cases like:
Dim x As Object
Set x = CreateObject("Excel.Application")
To be migrated to something like:
Excel.Application x = (Excel.Application) Utils.MyCreateObject("Excel.Application", "");
Our migratio vb6migration tool provides a new cool feature called CustomMaps. This feature allows you to provide some simple but useful changes to the way things get migrated.
For this case follow these steps:
1. Open the Visual Basic Upgrade Companion.
2. In the Tools Menu choose:
3. Create a new CustomMaps File and an an entry like the following:
Notice the Source name is VBA.Interaction.CreateObject. To find out this name you can look in your VB6 IDE, right click on the CreateObject and select goto Definition.
and for the target name just put the implementation that you what, for example you can write a function like:
class Utils
{
public static object MyCreateObject(string className,params object[] ignoreRestParams)
{
return Activator.CreateInstance(Type.GetType(className));
}
}
and set the SourceName to Utils.MyCreateObject (or NameSpace.Utils.MyCreateObject to use the fully qualified name). You just need to set the New Reference Name column because we will not change the definition of the function.
Most of our clients come from a medium-size to a big enterprise level. In these scenarios is very common to have different department using different technologies to solve their business needs.
These different technologies can be on a very homogeneous platform like .NET where you can easily interact between your VB.NET and C# assemblies, or they could be on differente technologies like ASP, Classic VB, C++, or Powerbuilder.
This post is about PowerBuilder, and in order to interact with PowerBuilder I think the easiest way is to expose your assemblies thru COM Interop.
So if there is some .NET functionality that you want to expose to PowerBuilder you just need to expose that functionality with a class in a ClassLibrary project with COM attributes.
Let’s begin with a simple program to show how to comunicate Powerbuilder with C#.
NOTE: If you don’t have Powerbuilder you can get a trial version from: http://www.sybase.com/detail?id=1052162
- Open Microsoft Visual Studio
- On the File Menu, choose the New option, and in the File submenu choose Project….
Figure 1. Visual Studio File Menu. Choosing the option for a new project
- When you choose that option a dialog window will shown with the available options for new projects. In the option for C# Projects choose “Class Library”
Figure 2. New Project dialog window
You must introduce the new project name, location and solution name. Type something like ClassLibrary1, D:\PowerBuilder, ClassLibrary1.
1. When you finish creating your project you will have a code file called Class1.cs.
2. Change that code for something like :
using System;
using System.Collections.Generic;
using System.Text;
namespace SimpleClass
{
public class Class1
{
public int AddTenToParameter(int param1)
{
return param1 + 10;
}
public void SayHi()
{
System.Windows.Forms.MessageBox.Show("Hello World!");
}
public String GiveMeDate()
{
return DateTime.Now.ToLongDateString();
}
}
}
This will allow to test things like parameter passing, using different return types like strings or integers. But Before you continue you must add a reference to System.Windows.Forms to be able to use the MessageBox.
Figure 3. Adding a reference
Figure 3. Reference to System.Windows.Forms
3. Right click on the solution file and select properties:
Figure 4. Option to change project properties
4. Select the Register for COM Interop checkbox
Figure 5. Project properties window
5. Return to Class1.cs code file
6. Add an using statement after the existing using lines on Class1.cs file:
using System.Runtime.InteropServices;
7. Add the following attributes to the class:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("ClassLibrary1.Class1")]
Note: the ProgId is very important, because these value will be use in PB to comunicate with the this code
8. Now you must edit the AssemblyInfo.cs
Figure 6. AssemblyInfo.cs File
Now make sure to establish the COM settings in this file with statements like the following:
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("69efac5b-d887-40f4-a7e9-2721ac3c1598")]
The Guid is also very important, because this is used to differentiate this component and it must be unique.
To generate a new GUID you can got to the Tools Menu and choose the option Create GUID
Figure 7. Option menu to create a GUID
In the Create Guid dialog box, choose the fourth option and press Copy to put the contents on the Clipboard. Later, copy that value in the GUID attribute but remove the “{“ y “}”.
Now we are set. You only need to build the solution and the DLL. The build process with register the COM component.
If you will use the component on another computer you need to create an instalation program.
Using your program from Powerbuilder
Using your program from Powerbuilder is very easy. You just need code like the following:
Figure 8. PB Code to call a C# class thru COM
When you execute this program you will have 3 messageboxes :
- Hello World!
- 30
- Monday, March 02, 2009 (this message will change depending of the day, locale and regional settings)
Creating an instalation program
- Right click the solution and in the context menu choose Add and then new project.
Figure 9. Context Menu to add a new project
On the dialog box for Add New projec, look for the Other Project Types section and the choose Setup Project.
Figure 10. Creating a setup project
In this dialog bos indicate the name and location of the setup project. For example Setup1 and D:\Powerbuilder\ClassLibrary1.
Later, add a project to the setup program. To do that rigth click on the setup project and select Add, and in the submenu choose Project Output.
Figure 11. Adding a project to the setup project.
A dialog box will be shown with a combo that allow you to select the proyects in the solution. Choose ClassLibrary1 and press OK.
Figure 12. Adding project output to the setup project.
When you build this instalation program two files will be produced:
Release
D:\PowerBuilder\ClassLibrary1\Setup1\Release\Setup.exe
D:\PowerBuilder\ClassLibrary1\Setup1\Release\Setup1.msi
Debug
D:\PowerBuilder\ClassLibrary1\Setup1\Debug\Setup.exe
D:\PowerBuilder\ClassLibrary1\Setup1\Debug\Setup1.msi
When you run the instalation program, this program will handle the instalation of the .NET component and the COM registration.
COM
The idea is to make a class or several classes available thru COM. Then the compiled dll or the TLB is used to generate and Interop Assembly and call the desired functions.
With this solution the current C++ code base line can be kept or might require just subtle changes.
Calling a function thru com is involved in a lot of marshalling and can add an additional layer that is not really needed in the architecture of the solution.
Creating a Managed Wrapper with Managed C++
The idea with this scenario is to provide a class in Managed C++ that will be available in C#. This class is just a thin proxy that redirects calls to the Managed object.
Let’s see the following example:
If we have a couple of unmanaged classes like:
class Shape {
public:
Shape() {
nshapes++;
}
virtual ~Shape() {
nshapes--;
};
double x, y;
void move(double dx, double dy);
virtual double area(void) = 0;
virtual double perimeter(void) = 0;
static int nshapes;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) { };
virtual double area(void);
virtual double perimeter(void);
};
The first thing we can try, to expose our classes to .NET it to set the setting for managed compilation:
If your project compiles then you are just very close, and what you need is to add some managed classes to your C++ project to expose your native classes:
Let’s see the Shape class:
//We can use another namespace, to avoid name collition.
//In this way we can replicate the structure of our C++ classes.
namespace exposedToNET
{
//Shape is an abstract class so the better thing
// to do is to generate an interface
public interface class Shape : IDisposable
{
public:
//public variables must be exposed as properties
property double x
{
double get();
void set(double value);
}
property double y
{
double get();
void set(double value);
}
//method do not expose any problems
void move(double dx, double dy);
double area();
double perimeter();
//public static variables must
static property int nshapes;
};
//Static methods or variables of abstract class are added here
public ref class Shape_Methods
{
//public static variables must be exposed as static properties
public:
static property int nshapes
{
int get()
{
return ::Shape::nshapes;
}
void set(int value)
{
::Shape::nshapes = value;
}
}
};
}
And for the Circle class we will have something like this:
namespace exposedToNET
{
public ref class Circle : Shape
{
private:
::Circle* c;
public:
Circle(double radius)
{
c = new ::Circle(radius);
}
~Circle()
{
delete c;
}
//public variables must be exposed as properties
property double x
{
virtual double get()
{
return c->x;
}
virtual void set(double value)
{
c->x = value;
}
}
property double y
{
virtual double get()
{
return c->y;
}
virtual void set(double value)
{
c->y = value;
}
}
//method do not expose any problems
virtual void move(double dx, double dy)
{
return c->move(dx,dy);
}
virtual double area()
{
return c->area();
}
virtual double perimeter()
{
return c->perimeter();
}
//public static variables must be exposed as static properties
static property int nshapes
{
int get()
{
return ::Shape::nshapes;
}
void set(int value)
{
::Shape::nshapes = value;
}
}
};
}
DOWNLOAD EXAMPLE CODE
SWIG
SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages.
This is a great tool used for several languages like Python, Perl, Ruby, Scheme, and even in different platforms.
The exposure mechanism used in this scheme is platform invoke, the issues here are similar to those of COM because there is some marshaling going on. This scheme might be more efficient than the COM one but I haven’t really test it to be completely sure that it is better.
I have reviewed the SWIG code and it might also be possible to modify its code to generate wrappers using managed C++, but this is an interesting exercise that I have to leave for my readers. Sorry I just don’t have enough time.
But how is SWIG used?
In SWIG what you do is that you add a .i file to your project. This file provides directives for some code generation that specify exactly what you want to expose and how.
This can very helpful if you just want to expose some methods.
If you are lazy like me you can just add something like:
/* File : example.i */
%module example
%{
#include "example.h" ß you put here includes with the definitions for your classes
%}
/* Let's just grab the original header file here */
%include "example.h" ß add alse the include here
And SWIG will add a file like example_wrap.cxx that you have to compile with the rest of your C++ code.
It will also generate a set of C# classes that you use in your C# application, so it seams to your program that all the code is just C#.
SWIG is a great tool and has been testing in a lot of platforms.
We found some machines that do not show the "Attach To Process" option.
This is very important for us, specially if you are testing the migration of an VB6 ActiveX EXE or ActiveX DLL to C#.
There is a bug reported by Microsoft http://support.microsoft.com/kb/929664
Just follow the Tool/Import Settings wizard to the end. Close and restart VS and the options will reapper.
Also you might find that the Configuration Manager is not available to switch between Release and Build for example.
To fix this problem just go to Tools -> Options -> Projects and Solutions -> General... make sure the option "Show advanced build configurations" is checked.
Have you ever wished to modify the way Visual Studio imported a COM Class. Well finally you can.
The Managed, Native, and COM Interop Team (wow what a name). It looks like the name of that goverment office in the Ironman movie.
Well this fine group of men, have release the source code of the TLBIMP tool. I'm more that happy for this.
I can know finally get why are some things imported the way they are.
http://www.codeplex.com/clrinterop
You can dowload also the P/Invoke assistant. This assistant has a library of signatures so you can invoke any Windows API.
As part of the VB Companion Development group, my day to day includes
migrating several project from different clients, to develop custom mappings
and custom functionality for their migration needs or to add new features
for the next VB Companion version.
A long part of the initialization in the migration process consists of the load and
analysis of the COM references indicated in the .VBP project file.
Sometimes I have notice that there are several references that are never used.
Removing these references will provide a great save in time because the migration will
not have to incur in any time for TypeLib and TypeInfo extraction.
I look for a tool that let me get rid of the VB6 unused referencences but I found none.
So I decided to create one myself. And I created the VB6 Project References Cleaner Addin
The concept of the tool is simple, someone from a group posted the idea I just implemented.
The addin goes thru all the references and one by one tries to remove it.
And then compiles the project. It the project compiles,then the reference was not neccesary.
If you mark the remove option the tool will remove the references for you.
This tool will NOT SAVE the project file. You decide if you what to save it.
I'm attaching the source code and the dll. To used it just take the VB6References.dll and run:
regsvr32 VB6References.dll
After that the tool will appear in the Addins menu in VB6
SOURCE CODE and BINARIES