Thread: Very strange error in managed code

  1. #1
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607

    Very strange error in managed code

    Since we don't have a managed C++ forum I thought I would put this here:

    Take the following code that is in one DLL:

    Code:
    #pragma once
    
    using namespace System;
    #include "NativeClass.h"
    
    namespace Managed
    {
        public interface class ITest
        {
            public:
                virtual void Foo();
                virtual void SetNativeClass(NativeClass *pClass);
                
        };
    
    
        public ref class Test abstract : public ITest
        {
            public:
                Test() : m_pNativeClass(0) { }
                virtual void Foo() { }
                virtual void SetNativeClass(NativeClass *pClass) = 0;
        
            public:
                NativeClass *m_pNativeClass;      
        };
    }
    Code:
    #pragma once
    
    
    class NativeClass
    {
        public:
            NativeClass() { }
    };

    And the following code in another DLL:
    Code:
    // ManagedObject.h
    
    #pragma once
    
    using namespace System;
    #include "NativeClass.h"
    
    namespace Managed
    {
        public ref class ManagedObject : public Test
        {
            public:
                ManagedObject() { }
    
                virtual void SetNativeClass(NativeClass *pClass)
                {
                    m_pNativeClass = pClass;
                }
         };
    }
    And the following errors from compiling the second DLL:
    1>ManagedObject.cpp
    1>e:\dev\managedtest\managedobject\ManagedObject.h (11) : warning C4570: 'Managed::ManagedObject' : is not explicitly declared as abstract but has abstract functions
    1> 'void Managed::Test::SetNativeClass(NativeClass *)' : is abstract
    1> e:\dev\managedtest\managedtest\debug\managedtest.d ll : see declaration of 'Managed::Test::SetNativeClass'
    1>e:\dev\managedtest\managedobject\ManagedObject.h (18) : error C2248: 'Managed::Test::m_pNativeClass' : cannot access private member declared in class 'Managed::Test'
    1> e:\dev\managedtest\managedobject\ManagedObject.h(1 2) : see declaration of 'Managed::Test::m_pNativeClass'
    1> e:\dev\managedtest\managedtest\debug\managedtest.d ll : see declaration of 'Managed::Test'
    Let's take the errors one by one here:
    1>e:\dev\managedtest\managedobject\ManagedObject.h (11) : warning C4570: 'Managed::ManagedObject' : is not explicitly declared as abstract but has abstract functions
    This makes no sense at all. Clearly I have implemented the abstract function in ManagedObject. This means that ManagedObject is NOT abstract. However the class it derives from...namely Test IS abstract b/c it has at least one pure virtual function. This error is bogus from what I can see.

    1> 'void Managed::Test::SetNativeClass(NativeClass *)' : is abstract
    1> e:\dev\managedtest\managedtest\debug\managedtest.d ll : see declaration of 'Managed::Test::SetNativeClass'
    First of all SetNativeClass() is abstract in Test() but not abstract in ManagedObject. When you look at the declaration of SetNativeClass() in Test you will see this. Another bogus error.

    1>e:\dev\managedtest\managedobject\ManagedObject.h (18) : error C2248: 'Managed::Test::m_pNativeClass' : cannot access private member declared in class 'Managed::Test'
    1> e:\dev\managedtest\managedobject\ManagedObject.h(1 2) : see declaration of 'Managed::Test::m_pNativeClass'
    1> e:\dev\managedtest\managedtest\debug\managedtest.d ll : see declaration of 'Managed::Test'
    I beg your pardon? Since when is m_pNativeClass inaccessible to a class that derives from a base that gives protected access to it? It's not the old problem of passing in an object of type Test * and then trying to set m_pNativeClass from that pointer. That is clearly wrong since protected applies to your own instance and not the instance of another class of the same type. But even if I set m_pNativeClass to public it will still say in the second DLL that it is private to Test even though I'm deriving from Test. Another bogus error.

    This occurs in MSVC 2005 and MSVC 2008. What is going on here?

    My .NET book specifically states that protected access means that members of the class can access the member or data and derived classes in other assemblies can also access the member or the data.
    However this does not seem to be true according to this error. The really really bad part about this is if you change the data type of m_pNativeClass to a POD type it works like a gem.

    Now if you don't implement SetNativeClass() in ManagedObject the compiler will say that you must implement the function Managed::ITest::SetNativeClass(NativeClass *). But when I have clearly implemented it, it acts as if I have not. In fact it can see Managed::Test b/c the inheritance works fine.

    If you remove the offending pointer and get everything compiling and put some assignment code in Test::SetNativeClass() and ManagedObject::SetNativeClass() so you can set breakpoints more odd things happen. So let's say you put int x= 5; in Test::SetNativeClass() and int x = 5; int ManagedObject::SetNativeClass(). The following code will surprise you.

    Code:
    ManagedObject ^object = gcnew ManagedObject();
    object.SetNativeClass(0);
    What is weird here is that the constructor for ManagedObject does get called but SetNativeClass() will call the BASE version instead of the derived version. How in the world can you construct a derived object, verify it constructed via breakpoints in the derived constructor, and yet when the call is made hit the breakpoint in the Base version of SetNativeClass()? Very odd. Whatever happened to polymorphism?
    Last edited by VirtualAce; 02-18-2010 at 07:28 PM.

  2. #2
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Ummm.....got it working but it is weird.

    First if I remove the pure virtual specifier ( = 0) from SetNativeClass() it gets rid of the abstract warnings.

    Second if I declare NativeClass like this:

    Code:
    public class NativeClass
    {
       public:
         NativeClass() { }
    };
    It works. However that is not valid native syntax. What it was telling me was that NativeClass was not visible outside of the assembly. So strange and not good at all. This mean I cannot use a pure C++ interface pointer as my parameter or my data member. So now this implies I have 2 interfaces - 1 that is pure C++ and 1 that is pure C++ except for the public keyword. How ignorant.

    Another work around using pure C++ for NativeClass

    Change data type of m_pNativeClass to System::IntPtr and all functions to use System::IntPtr.

    Code:
    void Managed::ManagedObject::SetNativeClass(System::IntPtr pClass)
    {
          m_pNativeClass = pClass;
    }
    
    void Managed::ManagedObject::SomeFunction()
    {
        NativeClass *pClass = (NativeClass *)m_pNativeClass->ToPointer();
        pClass->SomeFunc();
    }
    How nasty. I have no idea why native classes are not available outside of a managed assembly. This holds true even if the unmanaged class comes from a native DLL and header. As soon as the header is used in the managed DLL, the class in the header is no longer visible to consumers of that DLL. This seems like a serious design flaw. System::IntPtr is the equivalent of a void * which is what I initially used to get around this problem.
    Last edited by VirtualAce; 02-18-2010 at 08:34 PM.

  3. #3
    'Allo, 'Allo, Allo
    Join Date
    Apr 2008
    Posts
    639
    Quote Originally Posted by Bubba View Post
    I have no idea why native classes are not available outside of a managed assembly. This holds true even if the unmanaged class comes from a native DLL and header. As soon as the header is used in the managed DLL, the class in the header is no longer visible to consumers of that DLL.
    It's not that it's a native type, it's that it's a type in a managed assembly. The default visibility for managed types is internal (as explained here) which means 'used in this assembly only'. You'd get the same types of error if you removed the public from the ref class or interface class defined in the first dll. That's why the first error you posted complained of being abstract. The function type in the first dll is SetNativeClass(firstDLL::NativeClass *pClass) = 0; but in the implementation in ManagedObject its SetNativeClass(SecondDLL::NativeClass *pClass) which isn't the same, hence the complaint.

    To share the type between the assemblies and allow native code, there's nothing stopping you from from ifdeffing out the 'public' in the interface declaration for the native code. Passing a class defined from the 'native' version of the interface to the managed class works fine (see the attached, renamed zip).

    As for the inheritance that never was, you probably got hit by this (VC defaults to the 'new' behaviour if none are specified). Both the links are aimed at C#, but the concepts are the same for C++/CLI.

    Oh and you're not alone, nobody likes the language.
    Last edited by adeyblue; 02-18-2010 at 09:49 PM.

  4. #4
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Excellent!! I've been fighting this for a day and a half now. The #ifdef is ugly since I must put that in my native interface headers but at least it does work.

    Thanks for the example code.


    How did I know that posting on our forum would yield an answer quicker than searching all day long on MSDN and other forums for the same thing? I really cannot express how much this helps me. Now I can move forward on this instead of spinning my wheels. It is strange that my book about C++/CLI did not explain this better.

    I do not dislike C++/CLI b/c it is powerful albeit a bit confusing at first. I find it more powerful than native C++ for the pure and simple fact that it can access .NET and yet retain all the power (pointers) of native C++. It is the best and worst of both worlds.
    Last edited by VirtualAce; 02-18-2010 at 10:09 PM.

  5. #5
    'Allo, 'Allo, Allo
    Join Date
    Apr 2008
    Posts
    639
    Quote Originally Posted by Bubba
    The #ifdef is ugly since I must put that in my native interface headers but at least it does work.
    Just seen this which substitutes mangling the header for a pragma in the implementation file.

  6. #6
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Nice. Thanks for the update. Now my team is going to really love me when I show this...after we have implemented it the other way.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Obfuscated Code Contest: The Results
    By Stack Overflow in forum Contests Board
    Replies: 29
    Last Post: 02-18-2005, 05:39 PM
  2. Obfuscated Code Contest
    By Stack Overflow in forum Contests Board
    Replies: 51
    Last Post: 01-21-2005, 04:17 PM
  3. Trying to make this code faster & Cramer
    By just2peachy in forum C++ Programming
    Replies: 3
    Last Post: 12-03-2004, 10:54 AM
  4. True ASM vs. Fake ASM ????
    By DavidP in forum A Brief History of Cprogramming.com
    Replies: 7
    Last Post: 04-02-2003, 04:28 AM
  5. Interface Question
    By smog890 in forum C Programming
    Replies: 11
    Last Post: 06-03-2002, 05:06 PM