Wednesday, May 17, 2006 12:41 PM
by
loufranco
Implementing WPF BitmapEffects: Tip 1 - Use C++, not C# for the managed side
I've been implementing
Atalasoft dotImage image commands as WPF Bitmap Effects. In the course of that, I've learned a lot about how to implement BitmapEffects, which I will share in a short series of blog entries.
Tip 0 with all things involving imaging and WinFX is to go over to
Robert Wlodarczyk's blog and get some sample code. For BitmapEffects, he provides
this sample.
You will notice that BitmapEffects are written in unmanaged C++ (as a COM class) and use managed code just to provide a way for the client to set properties. In Robert's example, he used C# for the managed side. This means that there needs to be two projects, one for the managed C# code, and one for the unmanaged implementation.
At
Bill's suggestion, I implemented the managed side in managed C++. There are a few advantages to doing it this way:
- You can have one assembly with the managed and unmanaged code together.
- You can shared code between the two parts more easily.
- You can eliminate COM registration if you write your own CoCreateInstance (I haven't done this yet, but I plan to and will write it up when I do)
- If you eliminate COM registration, it becomes much easier to support having multiple versions of the effect (in separate assemblies).
Read our
.NET Imaging Philosophy for more information on this.
Here's some code to help you get started. The C++ version of COMSafeHandle is
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace Microsoft::Win32::SafeHandles;
public ref class COMSafeHandle
: public SafeHandleZeroOrMinusOneIsInvalid
{
internal:
COMSafeHandle() : SafeHandleZeroOrMinusOneIsInvalid(true) { }
COMSafeHandle(IntPtr handle) :
SafeHandleZeroOrMinusOneIsInvalid(true)
{
SetHandle(handle);
}
protected:
virtual bool ReleaseHandle() override
{
Marshal::Release(handle);
return true;
}
};
And CreateUnmanagedEffect in your bitmap effect looks like this:
SafeHandle^ YOURBitmapEffect::CreateUnmanagedEffect()
{
COMSafeHandle^ wrapper = nullptr;
::CreateBitmapEffectOuter(wrapper);
LPVOID impl = NULL;
HRESULT hr = ::CoCreateInstance(
GetGUID(),
static_cast<LPUNKNOWN>(wrapper->DangerousGetHandle().ToPointer()),
CLSCTX_INPROC_SERVER,
IID_IUnknown,
&impl);
if (SUCCEEDED(hr)) {
IntPtr ipImpl(impl);
COMSafeHandle^ unmanagedEffect = gcnew COMSafeHandle(ipImpl);
::InitializeBitmapEffect(wrapper, unmanagedEffect);
}
else {
String^ s = String::Concat("Cannot initialize effect: ", hr);
throw gcnew Exception(s);
}
return wrapper;
}
Update: You declare CreateBitmapEffectOuter and InitializeBitmapEffect with this code:
[DllImport("milcore.dll", EntryPoint = "MILCreateBitmapEffectOuterPublic")]
static unsigned int CreateBitmapEffectOuter([Out] COMSafeHandle^% ppEffect);
[DllImport("milcore.dll", EntryPoint = "MILInitializeBitmapEffectPublic")]
static unsigned int InitializeBitmapEffect(COMSafeHandle^ pOuter, COMSafeHandle^ pInner);