Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>If the struct is marshalable without custom processing, I greatly prefer the latter approach, where you declare the p/invoke function as taking a <code>ref</code> (pointer to) your type. Alternatively, you can declare your types as classes instead of structs, and then you can pass <code>null</code>, as well.</p> <pre><code>[StructLayout(LayoutKind.Sequential)] struct NativeType{ ... } [DllImport("...")] static extern bool NativeFunction(ref NativeType foo); // can't pass null to NativeFunction // unless you also include an overload that takes IntPtr [DllImport("...")] static extern bool NativeFunction(IntPtr foo); // but declaring NativeType as a class works, too [StructLayout(LayoutKind.Sequential)] class NativeType2{ ... } [DllImport("...")] static extern bool NativeFunction(NativeType2 foo); // and now you can pass null </code></pre> <blockquote> <p><code>&lt;pedantry&gt;</code></p> <p>By the way, in your example passing a pointer as an <code>IntPtr</code>, you've used the wrong <code>Alloc</code>. <code>SendMessage</code> is not a COM function, so you shouldn't be using the COM allocator. Use <code>Marshal.AllocHGlobal</code> and <code>Marshal.FreeHGlobal</code>. They're poorly named; the names only make sense if you've done Windows API programming, and maybe not even then. <code>AllocHGlobal</code> calls <code>GlobalAlloc</code> in kernel32.dll, which returns an <code>HGLOBAL</code>. This <em>used</em> to be different from an <code>HLOCAL</code>, returned by <code>LocalAlloc</code> back in the 16-bit days, but in 32-bit Windows they are the same.</p> <p>The use of the term <code>HGLOBAL</code> to refer to a block of (native) user-space memory just kind of stuck, I guess, and the people designing the <code>Marshal</code> class must not have taken the time to think about how unintuitive that would be for most .NET developers. On the other hand, most .NET developers don't need to allocate unmanaged memory, so....</p> <p><code>&lt;/pedantry&gt;</code></p> </blockquote> <hr> <p><strong>Edit</strong></p> <p>You mention you're getting a TypeLoadException when using a class instead of a struct, and ask for a sample. I did up a quick test using <code>CHARFORMAT2</code>, since it looks like that's what you're trying to use.</p> <p>First the ABC<sup>1</sup>:</p> <pre><code>[StructLayout(LayoutKind.Sequential)] abstract class NativeStruct{} // simple enough </code></pre> <p>The <code>StructLayout</code> attribute is required, or you <em>will</em> get a TypeLoadException.</p> <p>Now the <code>CHARFORMAT2</code> class:</p> <pre><code>[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)] class CHARFORMAT2 : NativeStruct{ public DWORD cbSize = (DWORD)Marshal.SizeOf(typeof(CHARFORMAT2)); public CFM dwMask; public CFE dwEffects; public int yHeight; public int yOffset; public COLORREF crTextColor; public byte bCharSet; public byte bPitchAndFamily; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string szFaceName; public WORD wWeight; public short sSpacing; public COLORREF crBackColor; public LCID lcid; public DWORD dwReserved; public short sStyle; public WORD wKerning; public byte bUnderlineType; public byte bAnimation; public byte bRevAuthor; public byte bReserved1; } </code></pre> <p>I've used <code>using</code> statements to alias <code>System.UInt32</code> as <code>DWORD</code>, <code>LCID</code>, and <code>COLORREF</code>, and alias <code>System.UInt16</code> as <code>WORD</code>. I try to keep my P/Invoke definitions as true to SDK spec as I can. <code>CFM</code> and <code>CFE</code> are <code>enums</code> that contain the flag values for these fields. I've left their definitions out for brevity, but can add them in if needed.</p> <p>I've declared <code>SendMessage</code> as:</p> <pre><code>[DllImport("user32.dll", CharSet=CharSet.Auto)] static extern IntPtr SendMessage( HWND hWnd, MSG msg, WPARAM wParam, [In, Out] NativeStruct lParam); </code></pre> <p><code>HWND</code> is an alias for <code>System.IntPtr</code>, <code>MSG</code> is <code>System.UInt32</code>, and <code>WPARAM</code> is <code>System.UIntPtr</code>.</p> <p><code>[In, Out]</code> attribute on <code>lParam</code> is required for this to work, otherwise, it doesn't seem to get marshaled both directions (before and after call to native code).</p> <p>I call it with:</p> <pre><code>CHARFORMAT2 cf = new CHARFORMAT2(); SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf); </code></pre> <p><code>EM</code> and <code>SCF</code> are <code>enum</code>s I've, again, left out for (relative) brevity.</p> <p>I check success with:</p> <pre><code>Console.WriteLine(cf.szFaceName); </code></pre> <p>and I get:</p> <pre>Microsoft Sans Serif</pre> <p>Works like a charm!</p> <hr> <p>Um, or not, depending on how much sleep you've had and how many things you're trying to do at once, I suppose.</p> <p>This <em>would</em> work if <code>CHARFORMAT2</code> were a <a href="http://en.wikipedia.org/wiki/Blittable_types" rel="noreferrer">blittable</a> type. (A blittable type is a type that has the same representation in managed memory as in unmanaged memory.) For instance, the <code>MINMAXINFO</code> type <strong>does</strong> work as described.</p> <pre><code>[StructLayout(LayoutKind.Sequential)] class MINMAXINFO : NativeStruct{ public Point ptReserved; public Point ptMaxSize; public Point ptMaxPosition; public Point ptMinTrackSize; public Point ptMaxTrackSize; } </code></pre> <p>This is because blittable types are not really marshaled. They're just pinned in memory—this keeps the GC from moving them—and the address of their location in managed memory is passed to the native function.</p> <p>Non-blittable types have to be marshaled. The CLR allocates unmanaged memory and copies the data between the managed object and its unmanaged representation, making the necessary conversions between formats as it goes.</p> <p>The <code>CHARFORMAT2</code> structure is non-blittable because of the <code>string</code> member. The CLR can't just pass a pointer to a .NET <code>string</code> object where a fixed-length character array is expected to be. So the <code>CHARFORMAT2</code> structure must be marshaled.</p> <p>As it would appear, for correct marshaling to occur, the interop function must be declared with the type to be marshaled. In other words, given the above definition, the CLR must be making some sort of determination based on the static type of <code>NativeStruct</code>. I would guess that it's correctly detecting that the object needs to be marshaled, but then only "marshaling" a zero-byte object, the size of <code>NativeStruct</code> itself.</p> <p>So in order to get your code working for <code>CHARFORMAT2</code> (and any other non-blittable types you might use), you'll have to go back to declaring <code>SendMessage</code> as taking a <code>CHARFORMAT2</code> object. Sorry I led you astray on this one.</p> <hr> <p>Captcha for the previous edit:</p> <blockquote> <p>the whippet</p> </blockquote> <p>Yeah, whip it good!</p> <hr> <p>Cory,</p> <p>This is off topic, but I notice a potential problem for you in the app it looks like you're making.</p> <p>The rich textbox control uses standard GDI text-measuring and text-drawing functions. Why is this a problem? Because, despite claims that a TrueType font looks the same on screen as on paper, GDI does not accurately place characters. The problem is rounding.</p> <p>GDI uses all-integer routines to measure text and place characters. The width of each character (and height of each line, for that matter) is rounded to the nearest whole number of pixels, with no error correction.</p> <p>The error can easily be seen in your test app. Set the font to Courier New at 12 points. This fixed-width font should space characters exactly 10 per inch, or 0.1 inches per character. This should mean that, given your starting line width of 5.5 inches, you should be able to fit 55 characters on the first line before wrap occurs.</p> <pre>ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123</pre> <p>But if you try, you'll see that wrap occurs after only 54 characters. What's more the 54<sup>th</sup> character and part of the 53<sup>rd</sup> overhang the apparent margin shown on the ruler bar.</p> <p>This assumes you have your settings at standard 96 DPI (normal fonts). If you use 120 DPI (large fonts), you won't see this problem, although it appears that you size your control incorrectly in this case. You also won't likely see this on the printed page.</p> <p>What's going on here? The problem is that 0.1 inches (the width of one character) is 9.6 pixels (again, using 96 DPI). GDI doesn't space characters using floating point numbers, so it rounds this up to 10 pixels. So 55 characters takes up 55 * 10 = 550 pixels / 96 DPI = 5.7291666... inches, whereas what we were expecting was 5.5 inches.</p> <p>While this will probably be less noticeable in the normal use case for a word processor program, there is a likelihood of instances where word wrap occurs at different places on screen versus on page, or that things don't line up the same once printed as they did on screen. This could turn out to be a problem for you if this is a commercial application you're working on.</p> <p>Unfortunately, the fix for this problem is not easy. It means you'll have to dispense with the rich textbox control, which means a huge hassle of implementing yourself everything it does for you, which is quite a lot. It also means that the text drawing code you'll have to implement becomes fairly complicated. I've got code that does it, but it's too complex to post here. You might, however, find <a href="http://www.codeguru.com/cpp/w-p/printing/article.php/c5897/" rel="noreferrer">this example</a> or <a href="http://aimm02.cse.ttu.edu.tw/class_2006_1/wp/Justify.pdf" rel="noreferrer">this one</a> helpful.</p> <p>Good luck!</p> <hr> <p><sup><sup>1</sup> Abstract Base Class</sup></p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload