Groups | Blog | Home
all groups > visual c > march 2007 >

visual c : Same Class, different Types


Born Bugler
3/30/2007 12:00:00 AM
What I'm actually talking about is, when you put the same class in different
assemblies, you get two different types. This is reasonable (if you would
call it) in most cases as it avoids possible misuse. However, what about if
I do want to consider classes with exactly the same definition in different
assemblies to be the same? I tried interpret_cast, but it works only for
trivial scenarios like below:

ref class ClassA
{
public:
virtual DoSomething(){ Console::WriteLine( "From ClassA" ); }
};

ref class ClassB
{
public:
virtual DoSomething(){ Console::WriteLine( "From ClassB" ); }
};

int main()
{
ClassA^ ca = gcnew ClassA();
ClassB^ cb = reinterpret_cast<ClassB^>(ca);
cb->DoSomething(); // OK, output "From ClassA"
}

However, if you make the scenario more complex by adding some base classes
to ClassA and ClassB, reinterpret_cast fails, as shown below:

interface class InfA
{
DoSomething();
};
interface class InfB
{
DoSomething();
};

ref class ClassA : public InfA
{
public:
virtual DoSomething(){ Console::WriteLine( "From ClassA" ); }
};

ref class ClassB : public InfB
{
public:
virtual DoSomething(){ Console::WriteLine( "From ClassB" ); }
};

int main()
{
InfA^ ca = gcnew ClassA();
InfB^ cb = reinterpret_cast<InfB^>(ca);
cb->DoSomething(); // Oh, an exception "Entry point not found" is thrown
here.
}

In standard C++, I don't think there will be any problem with the above
attempt. However, in C++/CLI, well, I'm waiting for your reply...

Born

Kevin Frey
3/30/2007 12:00:00 AM
Native C++ has no such problem because the types do not have a "strong
name". Types in managed assemblies, on the other hand, have a "strong name"
which means that type X in one assembly is not the same as type X in another
assembly, because their strong names are different.

You mention different assemblies. Are the two assemblies somehow related or
completely unrelated?

If they are completely unrelated then you might want to factor out the
shared types into a third assembly and have both of the other assemblies
depend on types defined within it. That way they are both using the same
strong name, hence referring to the same type.

If they are related then one of the assemblies should control the class
definition and the other assembly should use it. That way they are both
using the same type definition.

Kevin.


[quoted text, click to view]

Born Bugler
3/30/2007 12:00:00 AM
They are unrelated. I know exactly your solution. But what if I don't want a
third assembly? In many cases the third assembly would be filled with only
interfaces, and it looks silly to maintain a separate assembly for that
purpose.


[quoted text, click to view]

Jochen Kalmbach [MVP]
3/30/2007 12:00:00 AM
Hi Born!

[quoted text, click to view]

You should use "Reflection" to call the "DoSomething" method!

Greetings
Born Bugler
3/30/2007 12:00:00 AM
Oh, no, Jochen.

Please give me a detailed solution. Personally, I would say it will be much
harder than you have thought.

Born


"Jochen Kalmbach [MVP]" <nospam-Jochen.Kalmbach@holzma.de> Wrote in
message:%23FjhY5pcHHA.4888@TK2MSFTNGP06.phx.gbl...
[quoted text, click to view]

Jochen Kalmbach [MVP]
3/30/2007 12:00:00 AM
Hi Born!
[quoted text, click to view]


Regardless, that the "correct solution" is to put the "common" class in
a common-assembly, you can use refelction:

namespace Foo
{
ref class A
{
public: void DoSomething() {
System::Console::WriteLine("A::DoSomething"); }
};

ref class B
{
public: void DoSomething() {
System::Console::WriteLine("B::DoSomething"); }
};

ref class General
{
public: void static DoSomething(Object ^instance)
{
if (instance == nullptr) throw gcnew
System::NullReferenceException("'instance' must be set!");
System::Type ^t = instance->GetType();
System::Reflection::MethodInfo ^mi = t->GetMethod("DoSomething",
gcnew array<System::Type^> {});
if (mi == nullptr) throw gcnew
System::MethodAccessException("Method 'DoSomething' not found!");
mi->Invoke(instance, gcnew array<Object^> {});
}
};
}

int main()
{
Foo::A ^a = gcnew Foo::A();
Foo::B ^b = gcnew Foo::B();

Foo::General::DoSomething(a);
Foo::General::DoSomething(b);
}

Greetings
Born Bugler
3/31/2007 12:00:00 AM
So you misunderstood me.

I want to change an object from TypeA to TypeB, assuming TypeA and TypeB are
samely structured (with same methods, properties and so on. In most cases,
TypeA and TypeB will be interfaces. )

I'm almost sure this is unfeasible under .NET framework, especially if I
don't want to pay much performance penalty. If the performance is not taken
into consideration, through, there might be some ways:

1) Use wrapper objects.

The wrapper object will be derived from TypeB, and wrap the original TypeA
object.

2) Change the metadata of the original object to make it a real TypeB object
(sounds crazy).

Born


"Jochen Kalmbach [MVP]" <nospam-Jochen.Kalmbach@holzma.de> Wrote in
message:enB0ttqcHHA.2404@TK2MSFTNGP02.phx.gbl...
[quoted text, click to view]

Jochen Kalmbach [MVP]
3/31/2007 6:37:58 PM
Hi Born!
[quoted text, click to view]

No. "reinterpret_cast" ist not possible in a "strong typed system".
So the only way is to use reflection, and this works very well (but slow).

But in most cases: if you have a bad design, you mostly must use a bad
solution ;)

--
Greetings
Jochen

My blog about Win32 and .NET
Tamas Demjen
4/2/2007 10:02:02 AM
[quoted text, click to view]

For your problem, the right way to do is to have a dedicated assembly
with the interfaces only. In .NET, it's perfectly normal to have an
assembly for a group of classes that belong together. Especially so if
those classes are stable and won't be expected to change.

Without doing that, you have to live with an imperfect and awkward
solution, such as reflection, as others have pointed it out.

[quoted text, click to view]

It's not silly. The .NET framework itself is broken into 100s of assemblies.

If you want to design a plugin architecture, publish all the interfaces
in an assembly. It's self-contained, and that's *all* plugin
implementors will ever need in order to write custom modules for your
system. It's a good design, it's not silly.

If you have a small group of classes that you consider stable, it's a
very good idea to move them into an assembly, even if that DLL will only
have 3 functions. This approach fosters stability in the long run. Every
time you have to recompile code, you run the risk of breaking something,
and you must retest your entire package from sratch. A well tested,
compiled, self-described binary module that never changes is as stable
as it gets. You can even reuse that in other projects.

AddThis Social Bookmark Button