Hooking IDataObject.GetData in .Net

  • Thread starter Thread starter Roßert G. Schaffrath
  • Start date Start date
R

Roßert G. Schaffrath

Sorry for the cross-posting. I am having a hard time trying to classify
exactly what group this question would apply to.

I had posted an earlier message to
microsoft.public.dotnet.framework.windowsforms trying to find a way to
access the lindex member of the FORMATETC structure for a
CFSTR_FILECONTENTS callback request under C#. After some experimenting
I was able to come up with the following code snippet that receives the
callback with a filled in FORMATETC and allows me to save the lindex
value. Unfortunately, I am now stumped at how to forward the callback
to the .Net framework copy of IDataObject.GetData so that the request
can be processed by the framework and ultimately forwarded to my
override of DataObject.GetData. The location that I would want to
forward the request is marked as "TODO:". Can this be done or do have I
have fully implement my own IDataObject.GetData routine?

internal class DataObjectEx : DataObject,
System.Runtime.InteropServices.ComTypes.IDataObject
{
private Int32 m_lindex;

void
System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref
System.Runtime.InteropServices.ComTypes.FORMATETC formatetc, out
System.Runtime.InteropServices.ComTypes.STGMEDIUM medium)
{
if (formatetc.cfFormat ==
DataFormats.GetFormat(NativeMethods.CFSTR_FILECONTENTS).Id)
m_lindex = formatetc.lindex;
// TODO: Forward request on to .Net framework
IDataObject.GetData
}

public override object GetData(string format, bool autoConvert)
{
// Handle requests for certain formats and then...
return base.GetData(format, autoConvert);
}
}
 
Robert,

You won't be able to do this, I'm afraid. The reason for this is
because of the C# language. Because the interface was explicitly
implemented, you can't cast yourself to that interface to call the base
implementation.

Also, the implementers of the interface did not create another method
which is virtual which you could override which provides the actual
implementation. Because of this, you don't have access to the base
implementation either.

I can't say if it was an intentional move or not, but you don't have
access to the base implementation here.

You can still get around this though. Instead of deriving from
DataObject, you should create a new class which encapsulates it, and then
forward the calls on the implementations of IDataObject (both versions, the
one from the System.Windows.Forms namespace, and the one from the
System.Runtime.InteropServices.ComTypes namespace) to the instance you
create internally. You can cast that as appropriate to get the "base"
implementation, after (or before) you have done your processing.
 
Thanks for insight. I'll try the encapsulation approach and see how
that goes. Working in C# is great but there are times you get to 99% of
what you want to do and then run into a brick wall. An override would
have been nice but as you point out, whether or not it was intentionally
omitted, it does not exist and I have to cope as best I can.

Thanx!
 
I tried working this out with encapsulation but I was not having much
success. For fun, I decided to take apart the IDataObject.GetData
method in the .NET Framework using Lutz Roeder's .NET Reflector and see
what was going on under the hood. It turns out the method is not
terribly complicated and I was able to lift it and add it to my code.
The heavy work is being done by the IDataObject.GetDataHere method that
I did not replace so the actual solution is trivial.

The net (no pun intended) result follows:

private static TYMED[] ALLOWED_TYMEDS =
(TYMED[])Enum.GetValues(typeof(TYMED));
private Int32 m_lindex;

[SecurityPermission(SecurityAction.Demand, Flags =
SecurityPermissionFlag.UnmanagedCode)]
void
System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref
System.Runtime.InteropServices.ComTypes.FORMATETC formatetc, out
System.Runtime.InteropServices.ComTypes.STGMEDIUM medium)
{
if (formatetc.cfFormat ==
(Int16)DataFormats.GetFormat(NativeMethods.CFSTR_FILECONTENTS).Id)
m_lindex = formatetc.lindex;

medium = new
System.Runtime.InteropServices.ComTypes.STGMEDIUM();
if (this.GetTymedUseable(formatetc.tymed))
{
if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) !=
TYMED.TYMED_NULL)
{
medium.tymed = TYMED.TYMED_HGLOBAL;
medium.unionmember =
NativeMethods.GlobalAlloc(NativeMethods.GHND |
NativeMethods.GMEM_DDESHARE, 1);
if (medium.unionmember == IntPtr.Zero)
{
throw new OutOfMemoryException();
}
try
{

((System.Runtime.InteropServices.ComTypes.IDataObject)this).GetDataHere(ref
formatetc, ref medium);
return;
}
catch
{
NativeMethods.GlobalFree(new
HandleRef((STGMEDIUM)medium, medium.unionmember));
medium.unionmember = IntPtr.Zero;
throw;
}
}
medium.tymed = formatetc.tymed;

((System.Runtime.InteropServices.ComTypes.IDataObject)this).GetDataHere(ref
formatetc, ref medium);
}
else
{
Marshal.ThrowExceptionForHR(NativeMethods.DV_E_TYMED);
}
}

private Boolean GetTymedUseable(TYMED tymed)
{
for (Int32 i = 0; i < ALLOWED_TYMEDS.Length; i++)
{
if ((tymed & ALLOWED_TYMEDS) != TYMED.TYMED_NULL)
{
return true;
}
}
return false;
}

So far my code has been working like a charm. It is nice to have a
working solution after several weeks of hacking at it.

Robert
 
I tried working this out with encapsulation but I was not having much
success. For fun, I decided to take apart the IDataObject.GetData
method in the .NET Framework using Lutz Roeder's .NET Reflector and see
what was going on under the hood. It turns out the method is not
terribly complicated and I was able to lift it and add it to my code.
The heavy work is being done by the IDataObject.GetDataHere method that
I did not replace so the actual solution is trivial.

The net (no pun intended) result follows:

private static TYMED[] ALLOWED_TYMEDS =
(TYMED[])Enum.GetValues(typeof(TYMED));
private Int32 m_lindex;

[SecurityPermission(SecurityAction.Demand, Flags =
SecurityPermissionFlag.UnmanagedCode)]
void
System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref
System.Runtime.InteropServices.ComTypes.FORMATETC formatetc, out
System.Runtime.InteropServices.ComTypes.STGMEDIUM medium)
{
if (formatetc.cfFormat ==
(Int16)DataFormats.GetFormat(NativeMethods.CFSTR_FILECONTENTS).Id)
m_lindex = formatetc.lindex;

medium = new
System.Runtime.InteropServices.ComTypes.STGMEDIUM();
if (this.GetTymedUseable(formatetc.tymed))
{
if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) !=
TYMED.TYMED_NULL)
{
medium.tymed = TYMED.TYMED_HGLOBAL;
medium.unionmember =
NativeMethods.GlobalAlloc(NativeMethods.GHND |
NativeMethods.GMEM_DDESHARE, 1);
if (medium.unionmember == IntPtr.Zero)
{
throw new OutOfMemoryException();
}
try
{

((System.Runtime.InteropServices.ComTypes.IDataObject)this).GetDataHere(ref
formatetc, ref medium);
return;
}
catch
{
NativeMethods.GlobalFree(new
HandleRef((STGMEDIUM)medium, medium.unionmember));
medium.unionmember = IntPtr.Zero;
throw;
}
}
medium.tymed = formatetc.tymed;

((System.Runtime.InteropServices.ComTypes.IDataObject)this).GetDataHere(ref
formatetc, ref medium);
}
else
{
Marshal.ThrowExceptionForHR(NativeMethods.DV_E_TYMED);
}
}

private Boolean GetTymedUseable(TYMED tymed)
{
for (Int32 i = 0; i < ALLOWED_TYMEDS.Length; i++)
{
if ((tymed & ALLOWED_TYMEDS) != TYMED.TYMED_NULL)
{
return true;
}
}
return false;
}

So far my code has been working like a charm. It is nice to have a
working solution after several weeks of hacking at it.

Robert
 
Back
Top