Exposing C# struct to COM breaks for VB6 app - c#

Last Updated: 2009-08-11 2:30pm EDT
A few days ago I posted this question about some very strange problems. Well, I figured out what specifically was causing a build on one machine to not run on others and even came up with a work-around, but now it leaves me with a nice, specific question: Why?
To reproduce the problem, I create a new InteropUserControl and do the following:
Add a new public struct MyStruct:
Give it a GUID and ComVisible attributes
Add a GetMyStruct member to the _InteropUserControl interface and implement it in InteropUserControl.
MyStruct:
[Guid("49E803EC-BED9-4a08-B42B-E0499864A169")]
[ComVisible(true)]
public struct MyStruct {
public int mynumber;
}
_InteropUserControl.GetMyStruct():
[DispId(7)]
void getMyStruct( int num, ref MyStruct data );
(I have tried returning MyStruct instead of passing by reference, as well.)
InteropUserControl.GetMyStruct() implementation:
public void getMyStruct( int num, ref MyStruct data ) {
data = new MyStruct();
data.mynumber = num * 2;
}
I also sign the assembly and install it to the GAC and register with Regasm. Upon adding it to a new VB6 project and adding a call to GetMyStruct() and compiling on our build machine, it refuses to run on other machines.
To get around this, I had to expose a class to COM instead of the struct, and basically change GetMyStruct to this:
public void GetMyData( int num, MyClass data ) {
data.mynumber = num * 2;
}
In my actual project, I retrieve the struct internally, and then copy all the field values from the struct to the matching members on the instance of the class passed to the method by the client.
So why did a struct cause this behavior and a class worked fine? Is there some magic to exposing a struct to COM for using in VB6?
I think it may have something to do with OLE Automation.
Note: I also tried returning the struct rather than using a ref parameter, but that did not change the behavior.
Edit to add link to project template:
Interop Forms Toolkit 2.0 is the original VB.NET project template and dll. I don't reference the dll, so you may not need to install this.
C# Translations of templates on CodeProject is what I used to create mine (the project template, not the item template). The VB.NET version generates the __InteropUserControl event interface, the _InteropUserControl interface, and a few relevant attributes automagically. Those are explicitly coded in the C# version, and that's about all that's different between the two.

I think I found a solution to this problem.
I had the same exact problem, vb6 breaks when calling a method of an interop library by passing an structure. This is a project I created for testing a DLL interop, so all I have in my project was a form. But I had another project (the main application) with the same reference and it works fine.
After reading Joel post, I wanted to test his solution and in fact id did work (using a class instead a structure). But I have other interops where I'm using structures, so I was quite worried that at any point my application might fail. Additionally I didn't want to do the extra work of creating and exposing interface and a class to replace the structure.
So, I took the code from my form and move it to a public sub in a module. It Worked immediately. By the way, that's how i had implemented the call in the main application which was working ok.
I hope it might help others.

Is there some magic to exposing a
struct to COM for using in VB6?
The article COM Data Types* on MSDN says that structs are supported. Specifically, the MSDN article says that COM structures are defined as:
ByRef VALUETYPE< MyStruct >
There are also a couple of articles on customing your COM-callable wrappers at the bottom of the page, you may wish to review those.
Edit (2016): Original link was broken, so I fixed it to Version 3.5 of the .Net Framework.

Related

How registered COM interface is linked to twapi moniker?

I want to use c# code app in tcl using COM > twapi > tcl path.
By studying "call c# code from tcl" wiki page I understood two things.
We need to compile c# code with com interface register VS option. Then use that namespacename.classname to create object instance. But it is not clear how twapi (or tcom) will use that com (or link). Can you please explain in more details. Thanking you in advance.
C# code
using System;
namespace MyClassLib
{
public class Class1
{
public Class1() {}
public int Double (int val) { return val * 2 ; }
}
}
When the project is registered for COM Interop and built, the class is registered in the system's Registry as a provider for that class name. The tcom package knows how to use that information. When you do:
set myCom [tcom::ref createobject "ClassLibrary1.Class1"]
It goes away and asks the COM service that's built into Windows to make an instance of that object and give a reference to it back to you. That reference is what is stored in the variable. You can then invoke methods of the object; the syntax for that is Tcl-ish rather than C#-ish, but the models line up.
$myCom Double 6
Yes, there's a lot of complexity going on behind the scenes, mostly centred on IDispatch and its related interfaces.

Conditional compilation based on Class Library version in C#

In classic C, I may have a library at version 1.0, which defines a constant in its .h file like:
#define LIBRARY_API_VERSION_1_0
And I can do things like this in my application code:
#include "LibraryApi.h"
// ...
int success;
#ifdef LIBRARY_API_VERSION_1_0
int param = 42;
success = UseThisMethodSignature(42);
#endif
#ifdef LIBRARY_API_VERSION_2_0
float param = 42.0f;
success = UseOtherMethodSignature(param);
#endif
Now I'm working in C#. So, apparently #defines are only scoped to the file they're defined in, so I looked into the solution described here of using a static class with constants. But, that solution requires the checking to happen at runtime, which introduces a number of problems:
Potentially inefficient, if I'm running over the same code over and over again checking an extra conditional (though if it's a const, perhaps the compiler or .NET runtime is smart enough to avoid this?)
You can't do things that would throw compiler errors. In my above example, I've defined param twice with two different types. Also UseOtherMethodSignature may not exist as a function, which will not compile if both blocks are there only separated by if/else.
So, what is the accepted solution for this type of problem? My scenario is that I have multiple versions of a web service API (with varying degrees of differences depending on what you're doing with it) and I want to be able to compile against either without commenting/uncommenting a bunch of code or some other equally silly manual process.
Edit
For what it's worth, I'd prefer a compile-time solution--in my scenario I know when I compile which version I'm going to use, I don't need to figure out which version of the library is available on the system at runtime. Yes, that will work, but seems like overkill.
I would aim to abstract this into different wrapper libraries. They would be separate projects in Visual Studio and reference different versions of your framework.
// Shazaam contract.
public interface IShazaamInvoker {
Boolean Shazaam();
}
// ShazaamWrapper.v1.dll implementation
public class ShazaamInvoker : IShazaamInvoker {
public void Shazaam() {
Int32 param = 42;
return UseThisMethodSignature(param);
}
}
// ShazaamWrapper.v2.dll implementation
public class ShazaamInvoker : IShazaamInvoker {
public void Shazaam() {
Single param = 42f;
return UseOtherMethodSignature(param);
}
}
// Determine, at runtime, which wrapper to use.
var invoker = (IShazaamInvoker)(/*HereBeMagicResolving*/)
invoker.Shazaam();
I suggest using a DI framework to load the appropriate class / dll. If you can refactor your code to use interfaces then you can create an abstraction layer across different versions. See this link as to the different frameworks available.
Perhaps another solution in keeping with the compile time nature of your question is to use generated code with T4
You must define a compilation symbol at the project level. You do that in the project properties. These symbols can be referenced with the #if directive.
You could also create a project build configuration that includes one or the other compilation symbol and also check the configuration in the project file to include one or the other .dll reference based on the symbol so that you can properly build and debug both versions just by choosing the version from the dropdown in the toolbar.

VB6 COM Class Replacement With C# Rebuilt Update

I've got a particularly tricky situation here that I would like to throw out there for a bit more feedback on (I'm the only .NET dev in the company I work for so no-one to bounce off).
I've been tasked with replacing an aging VB6 authored ActiveX component that is consumed by an application that contains both VB6 useage and VB.NET usage of the component I'm replacing.
I have the complete source code for all these components so I can see the use cases.
For the sake of this discussion the components could be called:
MyVb6.dll
MyApp (with VB.NET & VB6 Components)
During the build process for MyApp they make use of TlbImp tool to then produce an interop library:
Interop.MyVb6.dll
Usage
In most instances the usage of this is as expected, making use of the CreateObject() method such as:
Private Property MyProp() As Object
Get
Try
If m_myProp Is Nothing Then
m_myProp = CreateObject("MyVb6.MyVb6Obj")
m_myProp.Initialize()
End If
Catch : End Try
Return m_myProp
End Get
However in one instance I've found they appear to have changed tactic on how to consume this interop dll and they have a static reference to it and a typed property, such as:
Private Property MyProp() As MyVb6.MyVb6ObjClass 'Whilst this is strongly typed, it is from the interop dll ...
Get
If m_myProp Is Nothing Then
m_myProp = CreateObject("MyVb6.MyVb6Obj")
m_myProp .Initialize()
End If
Return m_myProp
End Get
The expense of rebuilding and redeploying the entire application is completely out of the question so I'm left with no option other than to replace just the MyVb6.dll.
What I'm hoping to find out here is if this is a practical solution ...
Replacement
What I have done so far is written the skeleton of the replacement dll and since the object instances are created using a known string value I have added this is the ProgId, such as:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("xxx")]
[ProgId("MyVb6.MyVb6Obj")]
public class MyNewCSharpVersion : IMyNewCSharpVersion
{
...
}
Obviously for each of the objects I have maintained an identical interface so the calling application will find all the expected methods to call etc.
Then when I deploy this I'll deregister the old COM component and register my new one. Will this attempt at tricking it into thinking it's creating the same object actually work?
The Very Nasty Problem
There are also examples in the source code where they have used the VB6 dll directly, such as:
Dim myObj As MyVb6.MyVb6Obj
Unfortunately, no-one can answer the question if this particular component is still in use which in itself is slightly worrying.
If anyone has done something similar and got it working it would be great to know.
Not a problem, just use the right names:
namespace MyVb6 {
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("xxx")]
public class MyVb6Obj : _IOldVb6Version
{
...
}
}
Note the different interface name. Don't write your own. Add a reference to the old VB6 component so you can be completely sure that you implement the IOldVb6Version interface accurately with an exact match on the IID, the methods, their order and their arguments. If you don't know the old interface name then have a look with Oleview.exe, File + View Typelib.

Wrapping C++ for use in C#

Ok, basically there is a large C++ project (Recast) that I want to wrap so that I can use it in my C# project.
I've been trying to do this for a while now, and this is what I have so far. I'm using C++/CLI to wrap the classes that I need so that I can use them in C#.
However, there are a ton of structs and enums that I will also need in my C# project. So how do I wrap these?
The basic method I'm using right now is adding dllexport calls to native c++ code, compiling to a dll/lib, adding this lib to my C++/CLI project and importing the c++ headers, then compiling the CLI project into a dll, finally adding this dll as a reference to my C# project. I appreciate any help.
Here is some code..I need manageable way of doing this since the C++ project is so large.
//**Native unmanaged C++ code
//**Recast.h
enum rcTimerLabel
{
A,
B,
C
};
extern "C" {
class __declspec(dllexport) rcContext
{
public:
inline rcContect(bool state);
virtual ~rcContect() {}
inline void resetLog() { if(m_logEnabled) doResetLog(); }
protected:
bool m_logEnabled;
}
struct rcConfig
{
int width;
int height;
}
} // end of extern
// **Managed CLI code
// **MyWrappers.h
#include "Recast.h"
namespace Wrappers
{
public ref class MyWrapper
{
private:
rcContect* _NativeClass;
public:
MyWrapper(bool state);
~MyWrapper();
void resetLog();
void enableLog(bool state) {_NativeClass->enableLog(state); }
};
}
//**MyWrapper.cpp
#include "MyWrappers.h"
namespace Wrappers
{
MyWrapper::MyWrapper(bool state)
{
_NativeClass = new rcContext(state);
}
MyWrapper::~MyWrapper()
{
delete _NativeClass;
}
void MyWrapper::resetLog()
{
_NativeClass->resetLog();
}
}
// **C# code
// **Program.cs
namespace recast_cs_test
{
public class Program
{
static void Main()
{
MyWrapper myWrapperTest = new MyWrapper(true);
myWrapperTest.resetLog();
myWrapperTest.enableLog(true);
}
}
}
As a rule, the C/C++ structs are used for communicating with the native code, while you create CLI classes for communicating with the .NET code. C structs are "dumb" in that they can only store data. .NET programmers, on the other hand, expect their data-structures to be "smart". For example:
If I change the "height" parameter in a struct, I know that the height of the object won't actually change until I pass that struct to an update function. However, in C#, the common idiom is that values are represented as Properties, and updating the property will immediately make those changes "live".
That way I can do things like: myshape.dimensions.height = 15 and just expect it to "work".
To a certain extent, the structures you expose to the .NET developer (as classes) actually ARE the API, with the behaviors being mapped to properties and methods on those classes. While in C, the structures are simply used as variables passed to and from the functions that do the work. In other words, .NET is usually an object-oriented paradigm, while C is not. And a lot of C++ code is actually C with a few fancy bits thrown in for spice.
If you're writing translation layer between C and .NET, then a big part of your job is to devise the objects that will make up your new API and provide the translation to your underlying functionality. The structs in the C code aren't necessarily part of your new object hierarchy; they're just part of the C API.
edit to add:
Also to Consider
Also, you may want to re-consider your choice to use C++/CLI and consider C# and p/invoke instead. For various reasons, I once wrote a wrapper for OpenSSL using C++/CLI, and while it was impressive how easy it was to build and how seamless it worked, there were a few annoyances. Specifically, the bindings were tight, so every time the the parent project (OpenSSL) revved their library, I had to re-compile my wrapper to match. Also, my wrapper was forever tied to a specific architecture (either 64-bit or 32-bit) which also had to match the build architecture of the underlying library. You still get architecture issues with p/invoke, but they're a bit easier to handle. Also, C++/CLI doesn't play well with introspection tools like Reflector. And finally, the library you build isn't portable to Mono. I didn't think that would end up being an issue. But in the end, I had to start over from scratch and re-do the entire project in C# using p/invoke instead.
On the one hand, I'm glad I did the C++/CLI project because I learned a lot about working with managed and unmanaged code and memory all in one project. But on the other hand, it sure was a lot of time I could have spent on other things.
I would look at creating a COM server using ATL. It won't be a simple port, though. You'll have to create COM compatible interfaces that expose the functionality of the library you're trying to wrap. In the end, you will have more control and a fully supported COM Interop interface.
If you are prepared to use P/Invoke, the SWIG software could maybe help you out: http://www.swig.org/

Replacing a VB6 DLL called by a CreateDispatch call with a C# Equivalent

An existing Visual C++ application makes the following call;
BOOL bRet = pMyClass.CreateDispatch("BlahBlah.MyClass");
if ( !bRet )
{
// Error handling snipped
}
else
{
pMyClass.MyMethod();
pMyClass.ReleaseDispatch();
}
pMyClass is a class which was apparently auto-generated by ClassWizard, and it inherits from COleDispatchDriver.
The actual DLL to which it refers is a VB6 one, and this is being migrated to C# as part of an effort to move away from VB in general.
My question is... is there anything special I need to do to make sure that the C# assembly will work in the same way as the original VB6 module did? Currently, the C# looks like this;
[ComVisible(true)]
[ProgId("BlahBlah.MyClass")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class MyClass
{
...
public void MyMethod()
{
...
}
}
Is this sufficient? Are there any gotchas to be aware of when setting public string fields (not shown in code) on MyClass?
Note that I'm not the original author of this code - it's from a legacy system and I'm just doing the migration.
The CreateDispatch call uses late binding to talk to the COM server. ClassInterfaceType.AutoDispatch. Using AutoDual is fine, that also includes late binding support. With the significant advantage that you can make it a lot faster some day. Late binding isn't cheap.

Categories

Resources