I need to initialize to a variable only once in C++/CLI class accessible to managed code. Is a managed pointer initialized to nullptr when using gcroot? Specifically will the Initialize function below work as I expect? And only create the MyOtherClass once no matter how many times MyClass::Intialize is called?
class MyClass
{
public:
void Initialize();
private:
#ifdef _MANAGED
gcroot<MyOtherClass^> _myOtherClass;
#endif
};
void MyClass::Initialize()
{
if(_myOtherClass == nullptr)
{
_myOtherClass = gcnew MyOtherClass();
}
}
I'm using Visual Studio 2019. And the managed object accessing this is written in C#. I'm relatively new to CLI code. And I wasn't able to find the answer quickly by Googling it. And I'm sure someone in this community knows the answer.
Managed handles are default initialized to nullptr, per Default initialization: "when handles are declared and not explicitly initialized, they are default initialized to nullptr".
if(_myOtherClass == nullptr)
The above fails to compile with error C2088: '==': illegal for struct because gcroot wraps a GCHandle structure, which cannot be directly compared against nullptr.
To check the target handle (not the wrapper handle), use the following, which works as expected.
if(static_cast<MyOtherClass^>(_myOtherClass) == nullptr)
Related
Jumping to Visual Studio 2015 from Visual Studio 2013, I've noticed some differences in how static self-instances in managed C++ classes are accepted by the compiler. Consider these two examples:
Method 1:
public ref class CResourceManager
{
public:
static property CResourceManager^ Instance
{
CResourceManager^ get() { return %m_Instance; }
}
private:
static CResourceManager m_Instance;
};
Method 2:
public ref class CResourceManager
{
public:
static property CResourceManager^ Instance
{
CResourceManager^ get() { return m_Instance; }
}
private:
static CResourceManager^ m_Instance = gcnew CResourceManager;
};
Method 1 used to work on 2013, but it's failing to compile on 2015. I unfortunately do not have the exact compiler error handy, but it was one of those "Missing semicolon before variable name" errors, basically saying it couldn't find the type CResourceManager (pointing to the static variable declaration).
So on to my questions:
Is method 1 supposed to work or be valid in managed C++?
Why would the second method work in 2015, but not the first (i.e. what are the differences)?
Which method is the proper way to accomplish the end goal?
Method 2 is the proper way to do it. The code you have listed is the equivalent of the C# idiom.
Method 1 is a bit unusual.
The lack of a ^ on a declaration would normally mean that the variable is not allocated on the managed heap. However, since it's a static class member, I'm not sure where it actually gets created.
% is normally used for declaring tracking references, the equivalent of passing a variable by ref or out in C#. To be honest, I didn't think that applying % to a variable without either ^ or % and taking the result as a ^ was even valid. (Though considering the 2015 compiler rejects it, it may not be.)
Even if Method 1 is valid, I'd still go with Method 2: The storage location of m_Instance and how it's returned are both plain, common, and easy to understand. This beats having to think about how the code works any day.
I am trying to write a small library which will use DirectShow. This library is to be utilised by a .NET application so I thought it would be best to write it in C++/CLI.
I am having trouble with this line however:
HRESULT hr = CoCreateInstance( CLSID_FilterGraph,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
(void**)(&graphBuilder) ); //error C2440:
Where graphBuilder is declared:
public ref class VideoPlayer
{
public:
VideoPlayer();
void Load(String^ filename);
IGraphBuilder* graphBuilder;
};
If I am understanding this page correctly, I can use */& as usual to denote 'native' pointers to unmanaged memory in my C++/CLI library; ^ is used to denote a pointer to a managed object. However, this code produces:
error C2440: 'type cast' : cannot convert from 'cli::interior_ptr' to 'void **'
The error suggests that graphBuilder is considered to be a 'cli::interior_ptr<Type>'. That is a pointer/handle to managed memory, isn't it? But it is a pure native pointer. I am not trying to pass the pointer to a method expecting a handle or vice versa - I simply want to store it in my managed class) If so, how do I say graphBuilder is to be a 'traditional' pointer?
(This question is similar but the answer, to use a pin_ptr, I do not see helping me, as it cannot be a member of my class)
The error message is a bit cryptic, but the compiler is trying to remind you that you cannot pass a pointer to a member of a managed class to unmanaged code. That cannot work by design, disaster strikes when the garbage collector kicks in while the function is executing and moves the managed object. Invalidating the pointer to the member in the process and causing the native code to spray bytes into the gc heap at the wrong address.
The workaround is simple, just declare a local variable and pass a pointer to it instead. Variables on the stack can't be moved. Like this:
void init() {
IGraphBuilder* builder; // Local variable, okay to pass its address
HRESULT hr = CoCreateInstance(CLSID_FilterGraph,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
(void**)(&builder) );
if (SUCCEEDED(hr)) {
graphBuilder = builder;
// etc...
}
}
I have a method in my native dll, that I want to use. The method returns an object of a type that is also in my native dll.I am trying to write a c++/CLI wrapper.
Now,
Can I get a return value as the object using C++/CLI and how do I do that?
Can we store and pass the native C++ object?
Should I need to create my own class resembling the native C++ class?
How would I marshal a class?
For Example,My native dll has these classes,
class X
{
/* some props and methods. */
};
Class Y
{
X* someMethod();
};
I need to wrap the someMethod class using C++/CLI. Will I be able to get the return value in the CLI?
Returning pointers to C++ objects from an exported function in a DLL is a pretty bad idea. It is a nasty memory management problem, you'd expect the client code to release the object. That can only come to a good end when both DLLs use the exact same version of the DLL version of the CRT (/MD compile option). If you can't recompile the native DLL then stop right now, you cannot make it work reliably or you'll have a big maintenance problem in the future.
Anyhoo, you need a wrapper for both classes. They should resemble this:
#pragma managed(push, off)
#include "xandy.h"
#pragma managed(pop)
using namespace System;
namespace something {
public ref class XWrapper {
X* mX;
public:
XWrapper(X* obj) : mX(obj) {}
~XWrapper() { this->!XWrapper(); }
!XWrapper() {
// Trouble is here!!!
delete mX;
}
};
public ref class YWrapper {
Y* mY;
public:
YWrapper() { mY = new Y; }
~YWrapper() { this->!YWrapper(); }
!YWrapper() { delete mY; }
XWrapper^ someMethod() {
return gcnew XWrapper(mY->someMethod());
}
};
}
What does gcroot mean? I found it in code I am reading.
gcroot is a C++/cli template class that eases holding managed types in C++/cli classes.
You can for example have the following:
#include <msclr/gcroot.h>
using namespace msclr;
class Native {
public:
Native(Object ^obj) :
netstring(obj->ToString()) { // Initializing the gcroot<String ^>
}
~Native() {
}
void Print() {
array<Char> ^chars = netstring->GetChars(); // Dereferencing the gcroot<String ^>
_wprintf("netstring is:");
if (chars->Length > 0) {
pin_ptr<Char> charptr = &(chars[0]);
_wprintf("%s", (wchar_t const *)charptr);
}
}
private:
gcroot<String ^> netstring;
};
gcroot acts as a reference to the managed object or value type instance and is doing all the work when copying the object or value type instance.
Normally you need to work with GCHandle and some C functions of the .NET framework. This is all encapsulated in gcroot.
When the .NET garbage collector runs, it determines which objects are still in use by doing reachability analysis. Only the managed heap is analyzed while looking for pointers to objects, so if you have a pointer from a native object to a managed object, you need to let the garbage collector know, so it can include it in reachability analysis, and so it can update the pointer if the target moves during compaction.
As rstevens said, the .NET GCHandle class does this, and C++/CLI is a C++-oriented wrapper for GCHandle which adds type safety and convenient syntax.
I have a C++/CLI wrapper around native .lib and .h files. I use the AutoPtr class pretty extensively in the wrapper class to manage the unmanaged objects I create for wrapping. I have hit a roadblock with the copy constructor/assignment operator.
Using the AutoPtr class from Mr. Kerr: http://weblogs.asp.net/kennykerr/archive/2007/03/26/AutoPtr.aspx
He suggests the following(in the comments) to recreate the behavior of the assignment operator:
SomeManagedClass->NativePointer.Reset(new NativeType);
Which I believe is true. But when I compile my code:
ByteMessageWrap (const ByteMessageWrap% rhs)
{
AutoPtr<ByteMessage> m_NativeByteMessage(rhs.m_NativeByteMessage.GetPointer());
};
ByteMessageWrap% operator=(const ByteMessageWrap% rhs)
{
//SomeManagedClass->NativePointer.Reset(new NativeType);
if (this == %rhs) // prevent assignment to self
return *this;
this->m_NativeByteMessage.Reset(rhs.m_NativeByteMessage.GetPointer());
return *this;
};
-- I get the following errors:
error C2662:
'WrapTest::AutoPtr::GetPointer' :
cannot convert 'this' pointer from
'const WrapTest::AutoPtr' to
'WrapTest::AutoPtr %'
Has anyone experienced similar issues?
For further background on the answer, I removed the "const" keyword from the signature. I know that is not smiled upon in terms of code correctness for a copy ctor, but the CLR doesn't like it at all -- sort of belies the CLR at its core with memory management.
I wonder if it's possible to leave the const in the signature and then use GCHandle or pin_ptr to make sure memory doesn't move on you while performing the copy?
Looking at Kenny Kerr's AutoPtr, it transfers ownership in its constructor -- essentially a "move" constructor rather than a copy constructor. This is analogous with std::auto_ptr.
If you really want to transfer ownership from rhs to this (i.e. leave rhs without it NativeByteMessage), you need to change your copy ctor into a move ctor.
Also, you need to use initialization syntax;
// warning - code below doesn't work
ByteMessageWrap (ByteMessageWrap% rhs)
: m_NativeByteMessage(rhs.m_NativeByteMessage); // take ownership
{
}
ByteMessageWrap% operator=(ByteMessageWrap% rhs)
{
//SomeManagedClass->NativePointer.Reset(new NativeType);
if (this == %rhs) // prevent assignment to self
return *this;
m_NativeByteMessage.Reset(rhs.m_NativeByteMessage.Release());
return *this;
}