I am writing a managed wrapper in C++/CLI (VS2010) for a third-party unmanaged library. In the code, I have a method that looks like this:
if(oldState != _state && UnitStateChanged != nullptr)
UnitStateChanged(this, gcnew UnitStateChangedEventArgs(oldState, _state));
The "nullptr" generates the following error:
error C2446: '!=' : no conversion from 'int' to 'UnitStateChangedEventHandler ^'
The compiler seems to treat any use of "nullptr" as an "int", even on something as simple as this:
Object^ temp = nullptr;
Everything i've read indicates that the compiler will figure it out on it's own, but that doesn't seem to be the case. Is there a setting that I'm missing (other than /clr or #pragma managed)?
Someone without a C++11 compiler wrote
#define nullptr (0)
somewhere in a header file?
(The usual macro is #define NULL (0))
This is, of course, forbidden.
It looks like you're trying to fire an event.
In C#, there's a standard idiom for firing an event while protecting against null reference exceptions and multithreading modification:
// C#
EventHandler handler = this.MyEvent;
if(handler != null)
handler(this, new EventArgs(foo));
It looks like you're trying to re-create that in C++/CLI, but it's unnecessary. In C++/CLI, an event will emit three 'inner methods' (is that the proper name for them?) called add, remove, and raise. In C#, it creates add and remove only, no explicit raise, which is why we have to write that block of code over and over.
Here's an event defined in C++/CLI, and what .NET Reflector sees when it decompiles it:
// C++/CLI:
public ref class Test
{
public:
event EventHandler^ MyEvent;
};
// Decompiled to C#:
public class Test
{
// Fields
private EventHandler <backing_store>MyEvent;
// Events
public event EventHandler MyEvent
{
[MethodImpl(MethodImplOptions.Synchronized)] add
{
this.<backing_store>MyEvent = (EventHandler) Delegate.Combine(this.<backing_store>MyEvent, value);
}
[MethodImpl(MethodImplOptions.Synchronized)] remove
{
this.<backing_store>MyEvent = (EventHandler) Delegate.Remove(this.<backing_store>MyEvent, value);
}
raise
{
EventHandler <tmp> = null;
<tmp> = this.<backing_store>MyEvent;
if (<tmp> != null)
{
<tmp>(value0, value1);
}
}
}
}
The raise inner method is doing the null check for you, so you can skip doing it, and just fire the event without checking.
if(oldState != _state)
this->UnitStateChanged(this, gcnew UnitStateChangedEventArgs(oldState, _state));
Related
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)
It seems commonly thought that C++/CLI's initonly is the equivalent of C#'s readonly keyword. However, the following:
ref class C {
C();
void Method();
initonly array<int>^ m_array;
};
C::C() {
m_array = gcnew array<int>(10);
}
void C::Method() {
m_array[0] = 5; // Fails with C3893
}
The full error is "'C::m_array': l-value use of initonly data member is only allowed in an instance constructor of class 'C'".
The error message seems strange as I'm not using m_array as the target of an assignment, this is the equivalent of writing
m_array->SetValue(5, 0);
which incidentally compiles fine and does the same thing.
Is this bugged in C++/CLI or by design? By the way, is there any performance penalty to using Array::SetValue vs using the accessor?
A similar (but not identical) case was reported and apparently filed as a bug for VS2008: http://bytes.com/topic/net/answers/847520-initonly-but-not-bug-vc-2008-clr . I'm using Visual Studio 2012.
Yes, that's a bug. It's enforcing something which is not implied by the .NET type system, and the enforcement is ineffective.
But don't use Array::SetValue, which involves boxing and is not type safe. You can just do:
auto array = m_array; // another handle to same array
array[0] = 5;
Well, I haven't yet found something that says this is impossible, though I'm starting to think it might be. Can you make this work?
using namespace System;
template <typename T>
void unset(Nullable<T>& var) { var = Nullable<T>(); }
void unset(String^% var) { var=nullptr; }
//this is really a C# class in my situation, so I can't change its types
public ref class Foo
{
public:
property Nullable<Decimal> Dec;
property Nullable<int> Num;
property String^ Str;
};
int main()
{
Foo^ foo = gcnew Foo;
foo->Dec = Decimal(1.2);
foo->Num = 3;
foo->Str = "hi";
unset(foo->Dec);
unset(foo->Num);
unset(foo->Str);
Console::WriteLine(foo->Dec);
Console::WriteLine(foo->Num);
Console::WriteLine(foo->Str);
}
Update: unset is called from a code-generating macro which is called on about 50 params. I'd prefer not to have to go make varieties of the macro for each type.
It isn't possible. Setting a property requires calling the property setter function. There is no way to guess for the called method that it needs to call a function vs can assign the passed variable pointer. If you really want to do this then pass a delegate.
There is actually one .NET language that supports it, VB.NET generates code like this:
T temp = obj->prop;
func(temp)
obj->prop = temp;
There is however a dreadful aliasing problem with that, quite undebuggable. This goes belly up in the (rare) case where func() also uses the property. This is otherwise the way you'd work around the limitation, explicitly in your own code.
Beware that your code is wrong, possibly intentional, you are passing a C++ & reference, not a managed % interior pointer. The compiler is going to bitch about that, you can't create references or pointers to managed objects. They move. Unless the reference is to a variable on the stack. It doesn't otherwise change the answer.
For those who may end up here wondering how I got on with this, I ended up being lucky that the class I was working with was an LLBLGen Entity, so I was able to replace
unset(re->var);
with
{ SD::LLBLGen::Pro::ORMSupportClasses::IEntityField2^ f = re->Fields[#var]; \
if (f->IsNullable) \
f->CurrentValue = nullptr; }
I would like to check the left button is the pressed one.
I red on Msdna:
if(e->Button == MouseButtons.Left) {...}
//or
if(e->Button == ::MouseButtons.Left) {...}
But no one of them compiles.
This is an annoyance of the C++ language, inherited by C++/CLI. It puts the names of types and the names of class members in the same symbol table. This is something you'll battle often when you write Winforms code in C++/CLI instead of C# or VB.NET, languages that keep type identifiers separate.
There's an ambiguity between the MouseButtons enum type and the Form class' MouseButtons property, they are both in scope here. IntelliSense stops helping you to get the syntax right which is probably how you ended up with . instead of :: Leaving no odds anymore to get a decent compiler error message. You resolve the ambiguity by writing the enum type name in full:
if (e->Button == System::Windows::Forms::MouseButtons::Left) {
// etc...
}
Problems like these are probably one reason that C++/CLI never got very popular. Then again, C# is rather a class act. Recommended.
You need to hook the event on control you want:
this->control->MouseDown += new System::Windows::Forms::MouseEventHandler(this, &Form1::control_MouseDown);
and handle it like this:
void control_MouseDown(Object* sender, System::Windows::Forms::MouseEventArgs* e) {
if(e->Button == MouseButtons::Left) {
//code here
}
}
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;
}