Link to home
Start Free TrialLog in
Avatar of Xtreem
XtreemFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Trying to wrap IntPtr in an unmanaged nested class using gcroot<IntPtr*> fails miserably

I have created this nested class in my managed C++ class:

    __nogc class IntPtrWrapper
    {
    public:
        IntPtrWrapper(const int size) { ip = new IntPtr(0); }
        ~IntPtrWrapper(void) { }

        gcroot<IntPtr*> ip;
    };

which fails with this error:

    error C2716: 'operator new' allocates value types on the C++ heap; use '__nogc new System::IntPtr'

If I then do as it says and replace 'ip = new IntPtr(0)' with 'ip = __nogc new IntPtr(0)' giving me:

    __nogc class IntPtrWrapper
    {
    public:
        IntPtrWrapper(const int size) { ip = __nogc new IntPtr(0); }
        ~IntPtrWrapper(void) { }

        gcroot<IntPtr*> ip;
    };

I get this error:

    gcroot.h(98): error C2664: 'System::Runtime::InteropServices::GCHandle::set_Target' : cannot convert parameter 1 from 'System::IntPtr __gc *' to 'System::Object __gc *'

If I try to referene ip, for example by adding the following line to the constructor:

    *ip = Marshal::AllocHGlobal(size);

I get this error:

    gcroot.h(111): error C2440: 'static_cast' : cannot convert from 'System::Object __gc *' to 'System::IntPtr __gc *'


I've followed the instructions given on MSDN which uses a String for the example (as opposed to IntPtr):
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmxspec/html/vcManagedExtensionsSpec_16_3.asp

Even if I change my declaration of ip to gcroot<IntPtr> and change the references to it accordingly so I have the following code:

    __nogc class IntPtrWrapper
    {
    public:
        IntPtrWrapper(const int size)
        {
            ip = __nogc new IntPtr(0);
            ip = Marshal::AllocHGlobal(size);
        }
        ~IntPtrWrapper(void) { }

        gcroot<IntPtr> ip;
    };

I still get error C2664, same as above:

    gcroot.h(98): error C2664: 'System::Runtime::InteropServices::GCHandle::set_Target' : cannot convert parameter 1 from 'System::IntPtr' to 'System::Object __gc *'


I've tried all sorts, even using __box, but to no avail. Can anyone help me or is IntPtr just not supposed to be wrapped by gcroot? All I'm trying to do essentially is wrap IntPtr so I can manage the freeing of memory (through 'Marshal::FreeHGlobal(*ip)' in the destructor) automatically.
Avatar of Xtreem
Xtreem
Flag of United Kingdom of Great Britain and Northern Ireland image

ASKER

Amazingly, if I change the wrapper slightly and wrap it with another wrapper, I am actually able to resolve the problem, as thus:

    class IntPtrWrapper
    {
    public:
        IntPtrWrapper(const int size)
        {
            ip = Marshal::AllocHGlobal(size);
        }
        IntPtr ip;
    };

    class IntPtrWrapperWrapper
    {
    public:
        IntPtrWrapperWrapper(const int size)
        {
            ipw = new IntPtrWrapper(size);
        }
        gcroot<IntPtrWrapper*> ipw;
    };


Then I just use IntPtrWrapperWrapper and access the IntPtr through ipw->ip. Now am I going crazy or is this really absolutely necessary??? It seems overkill to me.
Avatar of AlexFM
AlexFM

Reading your first post I thought that MS article shows reference class wrapper, and you try to make value type wrapper. Wrapping value type to reference type - this can help. This is exactly what you did.
However, I wonder why do you need this. IntPtr is used by C# or VB .NET to work with unmanaged memory. In C++ you can work with unmanaged memoty directly:
ip = Marshal::AllocHGlobal(size);

What about this:
BYTE* ip;
ip = new BYTE(size);

When you need to make conversion between managed and unmanaged memory using marshal function, you can create IntPtr and initialize it with unmanaged pointer.
Avatar of Xtreem

ASKER

"I thought that MS article shows reference class wrapper, and you try to make value type wrapper"

Are you sure? MS article shows unmanaged class (I assume reference class wrapper means managed?) and I'm using unmanaged class defined with __nogc (which is not __value, but nonetheless unmanaged).


Anyway, the reason I need to do this is stated in my very last sentence in my first post:
"All I'm trying to do essentially is wrap IntPtr so I can manage the freeing of memory (through 'Marshal::FreeHGlobal(*ip)' in the destructor) automatically."

The reason for this is so that I can automate the clean-up of allocated memory when my IntPtr wrapper goes out of scope, rather than calling Marshal::FreeHGlobal myself - this is especially useful for when exceptions are thrown, and is one reason I can't used a managed wrapper because it uses undeterministic destruction.
I mean: MS article shows using gcroot with reference type String, and you want to do the same with value type IntPtr.
I still think that class can be written using pure unmanaged pointer which allocates memory with new, releases it with delete, and creates IntPtr on the fly when necessary. But to describe what I mean I need to see how do you want to use this class: some additional functions and client code.
Avatar of Xtreem

ASKER

Gotcha!

The reason I'm using IntPtr is to call methods on a COM API. The client code is in the following Question, which is really how this Question came about in the first place:
https://www.experts-exchange.com/questions/21535482/Managing-unmanaged-memory-assigned-to-IntPtr-by-Marshal-AllocHGlobal-when-an-exception-is-thrown.html


Funnily enough, the Accepted Answer from AlexFM on that Question suggests the same as you although he does then say that using pure managed code (IntPtr, Marshal::AllocHGlobal, etc.) is consistent and can be translated to any other .NET language like C#.

I'm basically just following the example shown here:
http://msdn2.microsoft.com/library/f31k2c87.aspx
Avatar of Xtreem

ASKER

Oh sorry, you ARE AlexFM! Oops, my bad lol.

I've now created the following gcintptr and auto_intptr classes to resolve my problems:



#pragma once

#include "gcroot.h"

using namespace System;
using namespace System::Runtime::InteropServices;

__gc class gcintptr
{
public:
    gcintptr(void) {}
    ~gcintptr(void) {}

    IntPtr ip;
};

__nogc class auto_intptr
{
public:
    auto_intptr(const int size) :
        ipw(new gcintptr())
    {
        ipw->ip = Marshal::AllocHGlobal(size);
    }

    ~auto_intptr(void)
    {
        Marshal::FreeHGlobal(ipw->ip);
    }

    // Overloaded operators
    ////////////////////////
    operator IntPtr() const
    {
        return ipw->ip;
    }

    auto_intptr& operator=(IntPtr o)
    {
        if (ipw->ip != o)
            ipw->ip = o;
        return *this;
    }

    IntPtr* operator->() const
    {
        return &ipw->ip;
    }

private:
    gcroot<gcintptr*> ipw;
};



And my client code simply uses it like this:

void CRCWAssembly::CallCOMMethod(long __gc* status)
{
    try
    {
        auto_intptr ipStatus(sizeof(*status));
        Marshal::WriteInt32(ipStatus, *status);
        (*comLib)->COMMethod(static_cast<long*>(ipStatus.ToPointer()));

        if (Marshal::ReadInt32(ipStatus) != 0)
        {
            throw new Exception();
        }
    }
    catch(SEHException *sehEx)
    {
        throw new Exception("COMMethod", sehEx);
    }
}


This seems to work now, what do you think? Or have I really just gone round the houses here and I could've just used an unmanaged long variable?
Nice. However, I am not so good in C++ theory and think like application developer, trying just to make work done (AFAIK, other AlexFM works by the same way). But I appreciate every programmer who is able to write smart code.
Avatar of Xtreem

ASKER

Couple of mistakes in my client code example, now corrected (I copied and pasted wrong), namely '->ToPointer()' and 'Exception':

void CRCWAssembly::CallCOMMethod(long __gc* status)
{
    try
    {
        auto_intptr ipStatus(sizeof(*status));
        Marshal::WriteInt32(ipStatus, *status);
        (*comLib)->COMMethod(static_cast<long*>(ipStatus->ToPointer()));

        if (Marshal::ReadInt32(ipStatus) != 0)
        {
            throw new Exception();
        }
    }
    catch (Exception *ex)
    {
        throw new Exception("COMMethod", ex);
    }
}


Thanks for your input anyway, I've e-mailed Nish (of http://www.thecodeproject.com/managedcpp/whycppcli.asp) and Tomas (of http://msdn.microsoft.com/msdnmag/issues/02/02/managedc/default.aspx) to get their expert opinions on my class. I like investing time in writing good robust generic smart code as it can often save headaches with maintaining and finding bugs later, not to mention the good practices it instils and the theory one learns. This is all for application development, I just prefer not to do quick and dirty programming as the increased productivity it might produce in the short term may become a serious hindrance in the long term. Although at my last place of work, I remember my boss said in some cases, it can be better for business to do quick hacks and I should learn to swallow that.
ASKER CERTIFIED SOLUTION
Avatar of modulo
modulo

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Xtreem

ASKER

For the information of all viewers of this Question, here is the latest version of my auto_intptr class, useless for wrapping managed arrays and managed strings:



#pragma once

#include "gcroot.h"

using namespace System;
using namespace System::Runtime::InteropServices;

__gc class gcintptr
{
public:
      gcintptr(const int size)
      {
            ip = Marshal::AllocHGlobal(size);
      }

      gcintptr(String* s)
      {
            ip = Marshal::StringToBSTR(s);
      }

      ~gcintptr(void) {}

      void Dispose(void)
      {
            if (GlobalSize((HGLOBAL)ip.ToInt32()) > 0)
            {
                  Marshal::FreeHGlobal(ip);
            }
            else
            {
                  Marshal::FreeBSTR(ip);
            }
            ip = 0;
      }

      IntPtr ip;
};

__nogc class auto_intptr
{
public:
      auto_intptr(const int size) :
            gcip(new gcintptr(size)) {}

      auto_intptr(BYTE array __gc[] = NULL) :
            gcip(new gcintptr(array->Length))
      {
            ::Marshal::Copy(array, 0, gcip->ip, array->Length);
      }

      auto_intptr(String* s) :
            gcip(new gcintptr(s)) {}

      ~auto_intptr(void)
      {
            gcip->Dispose();
      }

      // Overloaded operators
      ////////////////////////
      operator IntPtr() const
      {
            return gcip->ip;
      }

      operator _bstr_t() const
      {
            return static_cast<BSTR>(gcip->ip.ToPointer());
      }

      template <typename T>
      operator T*() const
      {
            return static_cast<T*>(gcip->ip.ToPointer());
      }

      auto_intptr& operator=(IntPtr o)
      {
            if (gcip->ip != o)
            {
                  gcip->ip = o;
            }
            return *this;
      }

      IntPtr* operator->() const
      {
            return &gcip->ip;
      }

private:
      gcroot<gcintptr*> gcip;
};
Avatar of Xtreem

ASKER

Sorry, I meant "USEFULL for wrapping managed arrays and managed strings"