Udostępnij za pośrednictwem


Quiz: What's wrong with the following code?

The following C# code has the goal of enabling managed code to call CreateDC, but it's incorrect.  Calling all Interop aficionados... Can you see what's wrong?

using System;

using System.Runtime.InteropServices;

internal class DeviceContext

{

  [DllImport("gdi32.dll", CharSet=CharSet.Auto)]

  internal static extern IntPtr CreateDC(

    string lpszDriver, string lpszDevice,

    string lpszOutput, ref DEVMODE lpInitData);

}

[StructLayout(LayoutKind.Sequential)]

internal class DEVMODE

{

  [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]

  public char [] dmDeviceName;

  public short dmSpecVersion;

  public short dmDriverVersion;

  public short dmSize;

  public short dmDriverExtra;

  public int dmFields;

  public DEVMODE_UNION u;

  public short dmColor;

  public short dmDuplex;

  public short dmYResolution;

  public short dmTTOption;

  public short dmCollate;

  [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]

  public byte [] dmFormName;

  public short dmLogPixels;

  public int dmBitsPerPel;

  public int dmPelsWidth;

  public int dmPelsHeight;

  public int dmDisplayFlagsOrdmNup;

  public int dmDisplayFrequency;

  public int dmICMMethod;

  public int dmICMIntent;

  public int dmMediaType;

  public int dmDitherType;

  public int dmReserved1;

  public int dmReserved2;

  public int dmPanningWidth;

  public int dmPanningHeight;

}

[StructLayout(LayoutKind.Explicit)]

internal struct DEVMODE_UNION

{

  [FieldOffset(0)]

  public short dmOrientation;

  [FieldOffset(2)]

  public short dmPaperSize;

  [FieldOffset(4)]

  public short dmPaperLength;

  [FieldOffset(6)]

  public short dmPaperWidth;

  [FieldOffset(8)]

  public short dmScale;

  [FieldOffset(10)]

  public short dmCopies;

  [FieldOffset(12)]

  public short dmDefaultSource;

  [FieldOffset(14)]

  public short dmPrintQuality;

  [FieldOffset(0)]

  public int dmPosition_x;

  [FieldOffset(4)]

  public int dmPosition_y;

  [FieldOffset(8)]

  public int dmDisplayOrientation;

  [FieldOffset(12)]

  public int dmDisplayFixedOutput;

}

Comments

  • Anonymous
    May 06, 2003
    I'm by no means an Interop expert, but this quiz brought up a couple questions of my own...First:Why do you not specify the call as: internal static extern IntPtr CreateDC( string lpszDriver, [In] string lpszDevice, string lpszOutput, [In] ref DEVMODE lpInitData);Are the [In], [Out], [In, Out] references just used for documentation purposes, or do they do something? Perhaps you explain this in your book, and I just haven't gotten that far yet.Anyway, I'm pointing my finger at how the DEVMODE structure is sent to CreateDC call.

  • Anonymous
    May 06, 2003
    These attributes can definitely have meaning. In this case, the by-value string parameters marshal as "in-only" anyway, so explicitly marking them with [In] has no effect. But you can use them to restrict marshaling in some cases. Marking a by-ref parameter with [In] can be a nice optimization to override the default [In, Out] behavior if you don't care about the contents of the parameter after the call.But even when you use these attributes to specify a non-default behavior, they still might not have any effect! [In] and [Out] only matter when the marshaler has some interesting work to do (copying a parameter from a managed representation to a separate unmanaged representation, or vice versa). For blittable types, where the managed and unmanaged memory representations are identical, the marshaler may directly hand out a pointer to the original memory location to unmanaged code. In such cases, it doesn't matter what attributes you use; if unmanaged code modifies that memory, you'll see the change after the call!But the lack of attributes is not the source of any grief in this example. Keep looking - it sounds like you're on the right track!

  • Anonymous
    May 06, 2003
    The fact that DEVMODE is a class and you pass it as a ref parameter will cause a DEVMODE** to be passed to the API - one level of indirection too much.For anyone who didn't catch that, I'd recommend watching Sonja's latest MSDN TV episode at http://msdn.microsoft.com/msdntv/.

  • Anonymous
    May 06, 2003
    Yes, and a nice tie-in to MSDN TV! That's one error in the code, but there's still another one!To elaborate on this first mistake, we have two options to fix it and make it marshal as a DEVMODE*: 1) Change the class to a struct 2) Remove the "ref" to pass the class reference by-valueOption 2 is better in this case because the documentation for CreateDC says, "The lpInitData parameter must be NULL if the device driver is to use the default initialization (if any) specified by the user." With option 2 you can pass null. With option 1, you can't.Can anyone find the other mistake?

  • Anonymous
    May 06, 2003
    I count two more. :-)1) DEVMODE should have CharSet.Auto.2) dmFormName should be a char[], not byte[]. That is according to Wingdi.h, the Platform SDK docs seem to be incorrect.

  • Anonymous
    May 06, 2003
    The comment has been removed

  • Anonymous
    May 06, 2003
    The comment has been removed

  • Anonymous
    May 06, 2003
    Yes, another great catch! Next time I'm not going to admit how many mistakes are in the code up front! :)

  • Anonymous
    May 07, 2003
    Since we are on Interop "puzzles", Adam, would you be kind enough to look at these two baffling issues http://dotnetweblogs.com/sgentile/posts/6626.aspx in Interop that a friend of mine is reporting. More description is avaliable on his site as well under http://urbanasylum.dynu.com/JustTheFacts/archives/000108.html and http://urbanasylum.dynu.com/JustTheFacts/archives/000109.html.We would be most appreciative.

  • Anonymous
    May 07, 2003
    Whoops-) The last post was me...Since we are on Interop "puzzles", Adam, would you be kind enough to look at these two baffling issues http://dotnetweblogs.com/sgentile/posts/6626.aspx in Interop that a friend of mine is reporting. More description is avaliable on his site as well under http://urbanasylum.dynu.com/JustTheFacts/archives/000108.html and http://urbanasylum.dynu.com/JustTheFacts/archives/000109.html.We would be most appreciative.

  • Anonymous
    May 07, 2003
    I posted a response to the first issue:"I understand your desire for this to work as you describe, although when managed arrays are marshaled to SAFEARRAYs, strings always marshal to BSTRs. The rules are similar to marking non-array types with values from the UnmanagedType enumeration - you can't marshal a single string as a VARIANT, so you can't marshal an array of strings as a SAFEARRAY of VARIANTs.With an array of objects, you have a few choices for the SafeArraySubType: VT_VARIANT, VT_UNKNOWN, and VT_DISPATCH. This coincides with the fact that you can mark a single object with UnmanagedType.Struct, UnmanagedType.IUnknown, or UnmanagedType.IDispatch. VT_VARIANT is the default behavior, so in your workaround above, you could eliminate the MarshalAsAttribute altogether."As for the second issue, I don't have much to say other than I don't know why the MIDL warning isn't documented, and I personally think it should be an error, too. I'm glad he was able to get past the issue!

  • Anonymous
    May 07, 2003
    Thanks Adam! as usual, you are most kind.

  • Anonymous
    May 07, 2003
    The comment has been removed

  • Anonymous
    May 07, 2003
    TomG, if you look in the Platform SDK docs for EnumDisplaySettings, you'll see that it only populates a few of the members in the DEVMODE. dmDisplayOrientation is not one of them.

  • Anonymous
    May 07, 2003
    At the moment I am not seeing any fields populated which is what's confusing me. If I do a watch on the DEVMODE struct, the only visible thing that happens is the dmSize field changes from 220 to 0 after returning from EnumDisplaySettings. (I believe the dmDisplayOrientation field is populated when called on a Tablet PC which is what I am ultimately trying to do.)On a side note, Yiru Tang mentioned that he thought the DEVMODE_UNION struct was declared incorrectly but I don't believe it is (even though Adam confirmed it?). The docs on MSDN incorrectly show the field as [1] when it should really be [2] (from wingdi.h).[1]union { struct { short dmOrientation; short dmPaperSize; short dmPaperLength; short dmPaperWidth; short dmScale; short dmCopies; short dmDefaultSource; short dmPrintQuality; }; POINTL dmPosition; DWORD dmDisplayOrientation; DWORD dmDisplayFixedOutput; };[2]union { struct { short dmOrientation; short dmPaperSize; short dmPaperLength; short dmPaperWidth; short dmScale; short dmCopies; short dmDefaultSource; short dmPrintQuality; }; struct { POINTL dmPosition; DWORD dmDisplayOrientation; DWORD dmDisplayFixedOutput; };};

  • Anonymous
    May 11, 2003
    TomG, The following code works for me with the DEVMODE definition posted above and the suggested changes.[DllImport("user32.dll", CharSet=CharSet.Auto)] static extern bool EnumDisplaySettings(string lpszDeviceName, uint iModeNum, [In,Out] DEVMODE lpDevMode);static void Main() { DEVMODE dm = new DEVMODE(); dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE)); uint mode = 0; while ( EnumDisplaySettings(null, mode++, dm) ) Console.WriteLine("{0}x{1}, {2} bpp at {3} Hz", dm.dmPelsWidth, dm.dmPelsHeight, dm.dmBitsPerPel, dm.dmDisplayFrequency );}

  • Anonymous
    May 12, 2003
    Yes, the [In, Out] is crucial on the DEVMODE parameter, and I neglected to put that on the signature earlier. Unlike the managed world where a by-value reference type parameter has [In, Out] semantics (the callee can change the type's fields), passing a by-value reference type parameter to unmanaged code has [In] semantics by default. This can bite you when passing arrays or classes-as-structures to unmanaged code. This situation is probably worth a separate blog entry!

  • Anonymous
    May 12, 2003
    Hi Adam,I am trying to call a COM method from managed code; the method has a parameter which is declared in IDL as:[in] SAFEARRAY(IDispatch*) *ObjectsIn VB6 this was the way the parameter was created thusly:

    Dim obs() As ObjectReDim obs(1 To mitems.Count)Dim i As LongFor i = 1 To mitems.Count    Set obs(i) = mitems(i)Next i
    I am having trouble creating an array to pass to this parameter in VB.Net.I have imported the typelib with /sysarray because the method expects a 1-based SAFEARRAY, and I create the parameter as:
        Dim obs As System.Array    Dim rgLengths() As Integer = {mitems.Count}    Dim rgBounds() As Integer = {1}    obs = System.Array.CreateInstance(GetType(Object), rgLengths, rgBounds)    Dim i As Integer    For i = 1 To mitems.Count        obs.SetValue(mitems(i), i)    Next i
    But when I try to make the call, I get an exception:SafeArrayTypeMismatchException: Specified array was not of the expected type.What am I doing wrong?

  • Anonymous
    May 13, 2003
    The COM object must be expecting a VT_DISPATCH SafeArray, but when you call it from managed code you're actually passing a VT_VARIANT SafeArray (in which the type of each VARIANT element happens to be a VT_DISPATCH). To make the array marshal as a VT_DISPATCH SafeArray instead, you'll need to mark the managed signature's array parameter with [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_DISPATCH)]. If you used the type library importer to create an Interop assembly for your type library, you could ILDASM it to a text file, change the signature, then ILASM it. If you happen to have my Interop book, chapter 7 shows you the steps and IL Assembler syntax for doing this.

  • Anonymous
    May 13, 2003
    The comment has been removed

  • Anonymous
    May 13, 2003
    Yup: [In, Out] was the culprit, thanks Mattias. I guess that means there's a bug in your book for the P/Invoke EnumDisplaySettings definition Adam?One more thing if I set the iModeNum param to -1 (ENUM_CURRENT_SETTINGS), it won't compile because -1 is not a uint. How does one correctly handle things like this in C#?

  • Anonymous
    May 13, 2003
    I worked with EnumDisplaySettings and ChangeDisplaySettings PInvoke calls way back. The code is quite different, but worked for Beta 1 and 2 of .NET 1.0. Interesting to see the changes.The original code (looking at it now is almost painful ... I don't know why I put the GPL license there :*) ... I have learned a lot!). It can be found here (PInvoke stuff is at the bottom): http://groups.google.ca/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=OK3BNzv4AHA.1428%40tkmsftngp03&rnum=8

  • Anonymous
    May 15, 2003
    TomG, if you're referring to my definition of EnumDisplaySettings in Appendix E, that doesn't need [In, Out] because it's being passed by reference, which exhibits [In, Out] behavior by default. This assumes that EnumDisplaySettings is defined as a struct (not a class), as with all the signatures in the appendix. Of course, this means that you would be unable to pass null for this parameter. If there's somewhere else I'm defining it, please let me know!As for passing -1, the simplest thing to do is define the parameter as an int instead of a uint, as you'd have to do in VB.NET. Or you could leave it as a uint and pass 0xFFFFFFFF. Or you could cast -1 to a uint using C#'s unchecked syntax.

  • Anonymous
    June 27, 2003
    Having as similar problem to the above. Have a COM object I work with. I use the IDE to create the reference. Several COM methods return SAFEARRAYs which are mapped to System.Array. Whenever I call the methods that return values other then strings, I get the SafeArrayTypeMismatchException exception. Any guidance? thanks.Jim

  • Anonymous
    July 20, 2003
    I used this very simple Devmode -- It seems to work for me.// Dev Mode String Sizesprivate const int CCDEVICENAME = 32;private const int CCFORMNAME = 32;public struct DEVMODE{ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=CCDEVICENAME)] public string dmDeviceName; public short dmSpecVersion; public short dmDriverVersion; public short dmSize; public short dmDriverExtra; public int dmFields; public short dmOrientation; public short dmPaperSize; public short dmPaperLength; public short dmPaperWidth; public short dmScale; public short dmCopies; public short sdmDefaultSource; public short dmPrintQuality; public short dmColor; public short dmDuplex; public short dmYResolution; public short dmTTOption; public short dmCollate;

     [MarshalAs(UnmanagedType.ByValTStr, SizeConst=CCFORMNAME)] public string dmFormName; public short dmUnusedPadding; public short dmBitsPerPel; public int dmPelsWidth; public int dmPelsHeight; public int dmDisplayFlags; public int dmDisplayFrequency;
    }

  • Anonymous
    August 01, 2003
    Help,I seem to have fallen into whitewater.!!I am trying to use the AddPrinter API -- does that mean I will have to also build a DevMode stuct and populate it? --( Hello Mattias Sjögren)P.S. I am not lazy just slow to adjust from VB to C#. I am still trying to add those printer. (They are network printers).Any help is appreciated,Ken

  • Anonymous
    November 07, 2003
    There seem to be a lot of people here with experience using DLLImport. I am having trouble with a Declare Function statement in VB.net that passes a ByRef sArray() As Integer as one of its parameters. When the reference comes back, it is empty except for the first index of the array. Is there something i can do to make this marshall correctly?Thanks in advance

  • Anonymous
    July 27, 2004
    The comment has been removed

  • Anonymous
    May 09, 2005
    Hi Adam, this is Srinath here. I am basically a C++ programmer, with little knowledge of .NET.

    When i try to access a COM method which returns SAFEARRAY(VT_VARIANT), i am getting
    "Specified array was not of the expected type." exception. Can you please help me to get out of this problem. If possible, can you send me the code snippet to overcome this excetpion.

    Srinath B.T.

  • Anonymous
    February 13, 2006
    <a href="http://september.journalspace.com">Free XXX movie</a>

  • Anonymous
    March 21, 2006
    very informative site with lots of useful info

  • Anonymous
    August 17, 2006
    Would have risen above Edmund use over with  54 concerta dream. One soon  54 concerta as fourteen often in man's  54 concerta eye  54 concerta night of Miss Grey finish that copy for  54 concerta Mr. And how long does

    <a href=" http://concertaonline.acb.pl/ ">54 concerta</a> 54 concerta
    <a href=" http://concertaonline.acb.pl/ ">concerta online</a> concerta online
    <a href=" http://concertaritalinvs.acb.pl/ ">concerta effects side</a> concerta effects side

  • Anonymous
    August 17, 2006
    Would have risen above Edmund use over with  54 concerta dream. One soon  54 concerta as fourteen often in man's  54 concerta eye  54 concerta night of Miss Grey finish that copy for  54 concerta Mr. And how long does

    <a href=" http://concertaonline.acb.pl/ ">54 concerta</a> 54 concerta
    <a href=" http://concertaonline.acb.pl/ ">concerta online</a> concerta online
    <a href=" http://concertaritalinvs.acb.pl/ ">concerta effects side</a> concerta effects side

  • Anonymous
    June 01, 2009
    PingBack from http://woodtvstand.info/story.php?id=10751

  • Anonymous
    June 17, 2009
    PingBack from http://pooltoysite.info/story.php?id=7541

  • Anonymous
    June 19, 2009
    PingBack from http://debtsolutionsnow.info/story.php?id=4377