Click to See Complete Forum and Search --> : question on style guideline
MadHatter
March 26th, 2008, 06:02 PM
I'm coming to C++ and C++/CLI pretty late in the game here, so bear w/ me. I'm still trying to figure out where the line in the sand is that separates managed extensions from cpp/cli.
So when you create a class, you put its interface in the header file, and its implementation in the cpp file.
As I'm going over stuff online for C++/CLI I'm seeing a whole lot of what looks like code that only goes in a header file (I just spent too much time trying to figure out the syntax of a property's get / set implementation and don't really like what I've come up with).
My question is more of a generally accepted practice: do you implement your C++/CLI class in the header file, or do you follow more of the traditional C++ format and separate the declaration from the implementation?
darwen
March 26th, 2008, 06:24 PM
Ah-ha ! Good question MH.
Code in the .h is called 'inline' so if you search any C++ site for this keyword you'll probably get loads of hits (maybe google "C++ inline").
Personally I always put my implementations in a separate cpp file except for generic classes. This is to make the .h files easier to read.
One thing to note : in C++/CLI, if you're writing a class library and have your implementations inline (i.e. in the .h file) if this .h file isn't #included in a cpp file somewhere in your project the class will not be exported. I think... it's a rule I keep to anyways.
There are other reasons for having inline functions in that they may be expanded in calling code i.e. if you have the following :
public ref class MyClass
{
public:
MyClass(int x)
: _myInteger(x)
{
}
int MyMethod(int a)
{
return a+_myInteger;
}
private:
int _myInteger;
};
void exampleMethod()
{
MyClass ^myClass = gcnew MyClass(20);
System::Console::WriteLine("{0}", myClass->MyMethod(10));
}
the code in example method may be expanded by the compiler thus :
void exampleMethod()
{
MyClass ^myClass = gcnew MyClass(20);
// replace the call to MyMethod with its implementation
System::Console::WriteLine("{0}", 10 + myClass->_myInteger);
}
Get the idea ? This removes the function call and so is more efficient. I'm not 100% sure if C++/CLI does this but I bet it does since native C++ definately does this.
Darwen.
MadHatter
March 26th, 2008, 07:01 PM
Last year I started a game development company, and from what I hear from my C++ developers it's generally better to not inline a bunch of code in the header (don't remember why they said that). I'm guessing that either thats not an issue when compiling an assembly, or all the samples I'm seeing for C++/CLI are just putting everything inline to keep things concise. while concise is nice, its not as easy to figure out how to separate things to the cpp file (especially when I'm lacking a real C++ background, and nobody around here knows C++/CLI).
Its definitely interesting to see the optimizations that are done by the compiler for inline stuff. It would be very interesting to see if thats done w/ the C++/CLI stuff as well. Also good to know about the header possibly being left out w/ out a cpp file including it.
thanks!
darwen
March 27th, 2008, 09:02 AM
Last year I started a game development company
Geez, lucky you. I've always dreamed of having my own company. "We're going to do this MY WAY !!!" would actually work then :D
and from what I hear from my C++ developers it's generally better to not inline a bunch of code in the header
There are quite a few reasons why not to.
(1) Compilation times. If you have the code in a cpp it only needs to get compiled once, whereas if it's inlined it needs to be expanded/compiled multiple times. When using templates you're forced to unfortunately.
(2) Dependencies. If the code is in the .h every file #include-ing the .h will have to be recompiled if there are any changes. If it's in the cpp then it only gets recompiled once.
(3) As I said before, readability. If you only have method declarations in the .h and the implementations in the cpp I find it much more readable.
Yours,
David.
MadHatter
March 27th, 2008, 12:48 PM
owning your own company isn't always as fun as it may sound. it involves a lot of very boring and tedious (non coding) things that I'd rather delegate but can't. I won't lie, doing things my way does offset some of the drudgery at times.
So if all our C++/CLI classes are doing is wrapping our native C++ to be used in our C# projects, are points 1 & 2 as important as they'd normally be? At this point I would separate things if only for consistency sake (if you can call it that, as C++/CLI looks nothing like the rest of C++), but my curiosity is peeked on whether or not the same holds true for the CLI code.
darwen
March 27th, 2008, 06:20 PM
So if all our C++/CLI classes are doing is wrapping our native C++ to be used in our C# projects, are points 1 & 2 as important as they'd normally be?
Personally I still prefer to keep the implementations in the cpp regardless.
However you have just raised an interesting point.
My preferred way of interfacing between .NET and native C++ is to put the native C++ into a native DLL (use a static library dll rather than a COM dll because of side-by-side execution) and the p/invoke into it from C#.
Then you don't get into any messy business with exactly which code is being turned into managed and which is kept as native.
I far prefer to have native completely seperate from managed - that way I can control the interface. And I've never had any problems with speed doing things this way.
If you mix 'native' (ha ha - read on) and managed code in a C++/CLI assembly and then look at the code using Reflector you'll see that what you thought was native often compiles to managed. Which can cause speed issues for instance if your code is doing a lot of iterations through arrays e.g.
int a[20];
for (int index = 0; index < 20; ++index)
{
a[index] = 100;
}
In native C++ there's no bounds checking but if the good old C++/CLI compiler decides (which it probably will) that this will compile to managed then obviously there is bounds checking which slows things down.
This is only one example - it can get worse if the compiler in its wisdom decides to translate some code to native in the middle of a managed method.
Maybe I'm just being paranoid but I prefer to keep different coding methodologies seperate and have never been bitten with any speed (or any other) problems as a result.
Darwen.
PS Export of classes in native C++ from a static library doesn't need to be difficult : you can use the old C 'context' method e.g.
// in native C++ dll
class NativeClass
{
public:
NativeClass(int x)
: m_nX(x)
{
}
void Add(int i)
{
m_nX += i;
}
} ;
// exported methods (use .def file to export them, not __declspec otherwise they get decorated)
NativeClass *__stdcall NativeClass_create(int x)
{
return new NativeClass;
}
void __stdcall NativeClass_Add(NativeClass *instance, int i)
{
instance->Add(i);
}
// do memory management inside the dll
void __stdcall NativeClass_Destroy(NativeClass *instance)
{
delete instance;
}
// In C#
// Use IntPtr for the pointer to NativeClass
public class NativeClass : IDisposable
{
[DllImport("MyDll.dll")]
private static extern IntPtr NativeClass_create(int i);
[DllImport("MyDll.dll")]
private static extern void NativeClass_Add(IntPtr nativeClass int i);
[DllImport("MyDll.dll")]
private static extern void NativeClass_Destroy(IntPtr nativeClass);
private IntPtr _nativeClass;
public NativeClass(int i)
{
_nativeClass = NativeClass_create(i);
}
~NativeClass()
{
Dispose(false);
}
public void Add(int i)
{
NativeClass_Add(_nativeClass, i);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (_nativeClass != IntPtr.Zero)
{
NativeClass_Destroy(_nativeClass);
_nativeClass = IntPtr.Zero;
}
if (disposing)
{
GC.SupressFinalize(this);
}
}
}
By doing things this way your managed code is always pure and verifyable.
I know this is more long-winded than using C++/CLI but I prefer it : all managed code is then in C# and all native code is in C++ and you never have to ask the question "is this managed or native" ?
Also a point to note : be careful of object lifetimes. The GC may come along and collect one of your wrappers not knowing that the native C++ class it contains is also being held in a member variable of another native C++ class.
Generally speaking the managed wrapper around C++ classes should also contain the same managed class member variables as the native C++ classes they wrap.
E.g.
class NativeA
{
} ;
class NativeB
{
public:
void SetA(NativeA *a)
{
m_pA = a;
}
private:
NativeA *m_pA;
} ;
ref class ManagedAWrapper
{
public:
~ManagedAWrapper()
{
delete m_pA;
}
!ManagedAWrapper()
{
delete m_pA;
}
internal:
NativeA *GetA()
{
return m_pA;
}
private:
NativeA *m_pA;
} ;
ref class ManagedBWrapper
{
public:
~ManagedBWrapper()
{
delete B;
}
!ManagedBWrapper()
{
delete B;
}
void SetA(ManagedAWrapper ^a)
{
_a = a;
m_pB->SetA(a->GetA());
}
private:
ManagedAWrapper ^_a;
NativeB *m_pB;
} ;
Get the idea ? Then ManagedAWrapper will never get GCed if ManagedBWrapper is still alive - meaning the NativeA pointer contained by NativeB will not be destroyed so long as NativeB is still alive.
MadHatter
March 27th, 2008, 07:06 PM
quite a bit of our code base is in native C++ and all of that is located in a static library because we have a client exe, and a dll that essentially loads the game up inside an XSI viewport (an artist tool) for real time content management, and to keep the internal tool and game client consistent, we just build the static lib into either. It also lets us wrap whatever we need as a managed wrapper.
I totally agree about the separation of native / managed code. I did some initial tests using C exports / C# dllimport (like your example), COM, and C++/CLI via dllexport/import, and the latter was the most efficient for what we needed. Oddly enough I did open the wrapped dll in reflector and was surprised at the amount of cpp implementation details and crt implementation details that were added in there. Made me feel like I had written something in VB for all the compiler generated code that was there ;)
For the C++/CLI wrapper do you think C style exports would be more efficient, or would it be better to __declspec(dllexport/import) the native class to be used in the wrapper? While C# dllimports were not as efficient, I was planning on wrapping the native classes to a .net equiv. class much like you would in C# class wrapper because of a lack of MI where you have a single member variable which would be the native class, and every method / property / event would wrap the native class and add only interop marshaling. while its not the same as doing it in C#, Its a considerable amount less work to make things usable in C# because of the other dependencies (physics, graphics, ai and engine interactions) that the native classes have that would take a while to wrap from in C#.
edit: good point about the lifetime management, I'm sure that will be fairly important in this application.
darwen
March 28th, 2008, 04:50 AM
C# dllimports were not as efficient
I'm surprised about this. True I've never done any explicit tests.
Watch out for array marshalling in dllimports - which is one reason why you might have been seeing a speed degredation.
If you do something like
public class XX
{
[DllImport("MyDll.dll")]
static public extern void PassArray(int [] array);
}
The array will be copied into a native array twice : once on the call to the method and again when it returns.
In this case I find it better to pin the array manually and do something like the following :
public class XX
{
[DllImport("MyDll.dll")]
static private extern void PassArray(IntPtr array);
static public void PassArray(int [] array)
{
GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
PassArray(gcHandle.AddrOfPinnedObject());
gcHandle.Free();
}
}
I.e. pinning the array and then just passing it as a pointer.
__declspec does the same thing as using a .def file only it decorates the names (turns them into __0002398548_PassArray or something like). I don't believe there's any speed differences.
Of course there's no correct solution here : I'd just use what works and what you have time for. And time is money for you... :thumb:
I've done a lot of interop like this in the past which had to be highly efficient (overlaying graphics from C# onto a video stream from DirectShow real-time has to be done very quickly otherwise the video won't play at 50fps) and am just saying my experience and how I did things.
Darwen.
MadHatter
March 28th, 2008, 12:07 PM
good point about the array marshaling (which we are doing a ton of). I thought the framework would pin that, but never tested it pinning it myself. I'll have to go back and see how that affects things.
Thanks for the insights. You've been extremely helpful!
codeguru.com
Copyright WebMediaBrands Inc., All Rights Reserved.