 |

Blog Stats
Posts - 36
Stories - 0
Comments - 218
Trackbacks - 41
|
Archives
Sep, 2007 (1)
Apr, 2007 (1)
Jan, 2007 (2)
Dec, 2006 (1)
Oct, 2006 (1)
Sep, 2006 (1)
Aug, 2006 (1)
May, 2006 (1)
Mar, 2006 (1)
Feb, 2006 (1)
Dec, 2005 (2)
Nov, 2005 (2)
Oct, 2005 (6)
Aug, 2005 (3)
May, 2005 (2)
Dec, 2004 (4)
Oct, 2004 (4)
May, 2004 (2)
|
|

Tuesday, September 11, 2007
Note: Don and I go way back, and this post was just intended as good natured ribbing. But in light of the fact that at least one other colleague could not tell that from the post, I figured I should point out explicitly that (a) I like and respect Don very much, (b) I wrote the following only because I thought the “sound“ of the PLINQ acronym was worth a chuckle, (c) I was recently a bystander to an in-depth discussion on LINQ's history that Don participated in [ergo, I used his name below], and (d) my sense of humor is apparently slighly malformed. So inspite of having had another colleague of Don's and mine give my post a once-over, I may have once again demonstrated how difficult it is to convey elbow-in-the-ribs joshing via a written medium. As for the actual technical merits of LINQ or PLINQ, it should be noted that I have no opinion whatsoever, let alone a negative opinion. I've been preoccupied elsewhere and haven't had the time to develop an opinion yet. So I offer my sincere apologies to Don if he too couldn't tell I was kidding, and hereby point out for future reference to everyone else that I'm just kidding...
After hearing for months about DLINQ and XLINQ, I suppose I shouldn't have been surprised this morning to crack open the October 2007 issue of MSDN mag and find an article about PLINQ (Parallel LINQ). It reminds me of the ActiveX days, when it seemed like every technology or product that came out was somehow "Active": ActiveSync, ActiveGrid, ActiveCrash, ActiveThis, ActiveThat, ... Today, it's ThisLINQ, ThatLINQ, and TheOtherLINQ.
So after reading the PLINQ article, I was a quite disappointed to have it confirmed that Microsoft has once again over promised and under delivered. It reminds me of the difference between the COM+ that Mary Kirkland wrote about in 1993 compared to what COM+ actually looked like when it shipped in 2000; or (more infamously) the original hype surrounding the wonders of XML Web Services.
What I'm talking about, of course, is the fact that PLINQ represents only the barest of features that Microsoft originally promised those of us at last year's SDR in the Grand Caymans, where their plans for a parallel query execution framework were first described. At the SDR, they went on and on about how TIDDLY WINQS (Threaded, Interactive Dynamic Data Layer Yields for Windows(r) In Networked Query Systems) would provide fully automated, language integrated, support for GRID computing by parallelizing queries across .NET-based systems connected in a P2P environment.
At the time, Don Box, with his little rum-based, umbrella-laden drink, flowered shirt, and carefully orchestrated disheveled appearance ("Look at me, I'm too brilliant to bothering getting a haircut or combing my hair!") waxed grandiose about how TIDDLY WINQS would automate support for massive scale computing by breaking complex query expressions into discrete sub-expressions that would be farmed out over the network to other cooperating "players" (PC nodes), each of which would compute the result for their assigned sub-expression, and then later "chip" (submit) the result of their computation into the appropriate TIDDLY WINQS "cup" (SQL Server 2005 Cluster). Only when every "player's" sub-query results were harvested, would the TIDDLY WINQS framework synthesize the final result and return the aggregate (”winning”) result to your application. And since TIDDLY WINQS (via a pluggable "connectivity provider") would automatically handle the loss or reacquisition of network connectivity, Don went on and on about the virtues of using TIDDLY WINQS in ClickOnce apps using WPF and WCF, but running in a PCE (partially connected environment).
So it came as a great disappointment to me this morning to realize that after all of the TIDDLY WINQS hype, Microsoft instead delivered PLINQ, which appears to be aptly named after the soft, dull thudding sound of the impact this unimpressive technology will like have on next-generation .NET applications.
 |
Wednesday, April 04, 2007
Over the years, I've used a variety of hex editors. In recent years, I've been lazy and just used the binary editor built into Visual Studio. But I recently discovered HxD, a free hex editor that I've really enjoyed working with. It loads and runs as fast as notepad, has a nice tabbed UI for working on multiple documents, and supports a ton of really nice features; among them:
- search by text string, hex, int, or float value
- search & replace
- insert mode
- disk editor
- RAM editor (view/edit the memory of a running process)
- nice printing support
- view bytes as groups of 1, 4, 8, or 16 bytes
So if you're ever in the market for a free, fast, nicely done hex editor, be sure to check out HxD. It's been a nice little addition to my toolbox.
Monday, January 22, 2007
Following up on this post, Microsoft has confirmed this to be a bug in version 6.6.7.5 of windbg. The good news is that the fix is scheduled to appear in the next release. The bad news is that, while there's no official date for that release, ballpark estimates from Microsoft indicate that it won't be for another 3 months or so.
Monday, January 15, 2007
As a follow up to this post, it would seem that the disassembler in version 6.6.7.5 of WinDbg appears to be broken when it comes to disassembling normal JITed code. I haven't noticed any other issues, but this one is a killer for me, so I've reverted to using version 6.6.3.5, which does not suffer from disassembler bug.
Disassembling native or pre-JITed code also appears to work. The bug seems to be isolated to the disassembly of normal JITed code. So if you're doing much work in windbg with managed code that involves looking at the disassembled output of managed methods, you might consider reverting to version 6.6.3.5. If you need the new/updated features/fixes of version 6.6.7.5, be advised that the disassembly you see for normal JITed methods may be buggy.
Note that you'll see the same buggy output using the windbg u command as well as the sos extension's u command. That's because sos.u doesn't implement its own disassembler - it just defers to the active debugger's disassembler; providing CLR-aware annotations to the output. As a result, if you use the !sos.u command within the context of the Visual Studio 2005 debugger (which does not appear to have this disassembler bug), you'll see valid output.
Ironically, if you look at the what's new page for version 6.6.7.5 of windbg, you'll find this line item:
Updated disassembler with new instruction support.
Thanks, but no thanks.
My test case is a simple C# console application that looks like so:
class Program {
static void Main() {
System.Console.WriteLine(GetText());
}
static string GetText() {
return ("Hello, buggy windbg!");
}
}
Using the !sos.bpmd extension command to break when Program.Main is called shows the following disassembly in version 6.6.3.5 of windbg (using !sos.u so that we can see the CLR-aware annotations):
0:000> !u eip
Normal JIT generated code
Program.Main()
Begin 01200070, size 23
01200070 56 push esi
01200071 833dc82da20000 cmp dword ptr [00a22dc8],0x0
01200078 7405 jz 0120007f
0120007a e87f22e978 call mscorwks!JIT_DbgIsJustMyCode (7a0922fe)
0120007f 90 nop
01200080 ff152c30a200 call dword ptr [00a2302c] (Program.GetText(), mdToken: 06000002)
01200086 8bf0 mov esi,eax
01200088 8bce mov ecx,esi
0120008a e855901b78 call mscorlib_ni+0x2f90e4 (793b90e4) (System.Console.WriteLine(System.String), mdToken: 06000764)
0120008f 90 nop
01200090 90 nop
01200091 5e pop esi
01200092 c3 ret
In particular, note that the disassembled code shown above for instruction address 0120008a. The raw instruction sequence at that address is e855901b78. As I described in my previous post, the 1st byte (e8) is the Intel 'call' instruction, and the following 4 bytes (55901b78) indicate the target of the call instruction, computed relative to the instruction pointer, and not counting the 5 byte instruction itself. So we can manually compute the target of the call instruction in windbg like so:
0:000> ? 0120008a + 5 + poi(0120008a + 1)
Evaluate expression: 2033946852 = 793b90e4
The above calculation shows that the target of the call instruction is address 793b90e4, which matches windbg's output:
...
0120008a e855901b78 call mscorlib_ni+0x2f90e4 (793b90e4) (System.Console.WriteLine(System.String), mdToken: 06000764)
...
Repeating the same experiment with version 6.6.7.5 of windbg (the buggy version) shows the following:
0:000> !u eip
Normal JIT generated code
Program.Main()
Begin 01200070, size 23
01200070 56 push esi
01200071 833dc82da20000 cmp dword ptr ds:[0A22DC8h],0
01200078 7405 je 0120007f
0120007a e87f22e978 call mscorwks!JIT_DbgIsJustMyCode (7a0922fe)
0120007f 90 nop
01200080 ff152c30a200 call dword ptr ds:[0A2302Ch]
01200086 8bf0 mov esi,eax
01200088 8bce mov ecx,esi
0120008a e855901b78 call MSVCR80!_NULL_IMPORT_DESCRIPTOR+0x2cdd (781b9055) (MSVCR80!_NULL_IMPORT_DESCRIPTOR)
0120008f 90 nop
01200090 90 nop
01200091 5e pop esi
01200092 c3 ret
There are a couple of things wrong with this output:
- the sos.u command doesn't realize that the 2nd call instruction goes to Program.GetText
- it incorrectly reports that the 3rd call instruction will land somewhere in MSVCR80.DLL (at address 781b9055).
Not likely.
Performing our same call decoding math for instruction 0120008a (the supposed call to address 781b9055 in MSVCR80.DLL) yields the following:
0:000> ? 0120008a + 5 + poi(0120008a + 1)
Evaluate expression: 2033946852 = 793b90e4
Note that our manual decoding of the call instruction yields a different target address: 793b90e4; as opposed to 781b9055 as shown by the !u command. If you disassemble that address, we'll see where execution will actually transfer:
0:000> !u eip
preJIT generated code
System.Console.WriteLine(System.String)
Begin 793b90e4, size 35
793b90e4 57 push edi
793b90e5 56 push esi
793b90e6 8bf9 mov edi,ecx
793b90e8 ba86000000 mov edx,86h
793b90ed b901000000 mov ecx,1
793b90f2 e8d1a9ab00 call mscorwks!JIT_Writeable_Thunks_Buf+0x198 (79e73ac8) (JitHelp: CORINFO_HELP_GETSHARED_GCSTATIC_BASE)
793b90f7 8bf0 mov esi,eax
793b90f9 837e6800 cmp dword ptr [esi+68h],0
793b90fd 750a jne mscorlib_ni+0x2f9109 (793b9109)
793b90ff b901000000 mov ecx,1
793b9104 e80347f9ff call mscorlib_ni+0x28d80c (7934d80c) (System.Console.InitializeStdOutError(Boolean), mdToken: 0600070f)
793b9109 8b4e68 mov ecx,dword ptr [esi+68h]
793b910c 8bd7 mov edx,edi
793b910e 8b01 mov eax,dword ptr [ecx]
793b9110 ff90d8000000 call dword ptr [eax+0D8h]
793b9116 5e pop esi
793b9117 5f pop edi
793b9118 c3 ret
And, in fact, that's exactly what the C# code we're looking at does at that address - call System.Console.WriteLine.
So be advised - the disassembly included in WinDbg version 6.6.7.5 is broken when it comes to decoding normal JITed code.
 |
Thursday, December 21, 2006
[Update: I forgot to mention the observation that, in the buggy installation of windbg, the target address that windbg shows for the call instruction in question appears to be the raw operand value for the call instruction -- without treating that value as relative to the current EIP. Update appears below.]
I've had a sneaking suspicion lately that some of the disassembled code I've been looking at in WinDbg for managed code that's been JITed has been a little off kilter. But it only seems off when it comes to the disassembly for JITed code. The disassembly for normal native code looks fine. And if I step through the JITed code, it actually executes just fine. It's just that the disassembler seems to be out of whack.
So this afternoon I sat down to see if I could clear things up (prove/disprove my suspicion). Unfortunately for me, I believe I've proved my suspicion (the windbg disassembler is out of whack), but I haven't been able to correct it. So I thought I'd post my results here on the off chance that someone out there's had a similar experience and can explain what's going on.
I happened to be poking around at the managed exception handling mechanism, so the code I'm looking at looks like this:
class Program {
...
static void ThrowException( Exception ex ) {
throw ex;
}
}
In WinDbg, I used the SOS !bpmd command to set a breakpoint on the ThrowException method. When the breakpoint hits, here's what the disassembly looks like on a machine (XP Pro SP2) where windbg seems to be working just fine:
Breakpoint 1 hit
...
0:000> u eip
01200420 56 push esi
01200421 8bf1 mov esi,ecx
01200423 833dc82da20000 cmp dword ptr ds:[0A22DC8h],0
0120042a 7405 je 01200431
0120042c e8cd1ee978 call mscorwks!JIT_DbgIsJustMyCode (7a0922fe)
01200431 90 nop
01200432 8bce mov ecx,esi
01200434 e89340e978 call mscorwks!JIT_Throw (7a0944cc)
01200439 cc int 3
The line of code in question is the one at address 01200434 (the call to mscorwks!JIT_Throw). WinDbg is kind enough to disassemble the instruction and its operand (e89340e978), and show us that the target address for that call instruction is 7a0944cc. But if you wanted to do it yourself (which will be come germane in a moment), WinDbg figures that out by parsing the instruction at address 001200434 like so…
The first byte (e8) is the x86 'call' instruction, which in this case is followed by a 32bit operand indicating the target address of the call relative to the current instruction. That value (9340e978) is displayed as a sequence of bytes above, so to see what the actual relative offset as a 32bit value, we can do this:
0:000> dd eip+1 l1
01200435 78e94093
So the target address is at 'offset' 78e94093 relative to the current instruction pointer (after the opcode and its operand have been consumed, which is 5 bytes). So we can do the math ourselves in windbg like so:
0:000> ? eip + 5 + poi(eip+1)
Evaluate expression: 2047427788 = 7a0944cc
Note that this results in the same target address (7a0944cc) that windbg had shown us was the call to mscorwks!JIT_Throw. Just for completeness, we can confirm this to be the case like so:
0:000> u 7a0944cc
mscorwks!JIT_Throw:
7a0944cc 6894000000 push 94h
7a0944d1 b8d497317a mov eax,offset mscorwks!GetManagedNameForTypeInfo+0x1b355 (7a3197d4)
7a0944d6 e848f3ddff call mscorwks!_EH_prolog3_catch (79e73823)
7a0944db 8bf9 mov edi,ecx
7a0944dd e8761bf0ff call mscorwks!ResetCurrentContext (79f96058)
7a0944e2 8d4d84 lea ecx,[ebp-7Ch]
7a0944e5 e81a09deff call mscorwks!LazyMachStateCaptureState (79e74e04)
7a0944ea 85c0 test eax,eax
So that's the reference case on the machine where windbg seems to be in working order. Repeating the same exercise on the buggy installation of windbg (on the same app, using the same version of windbg, XP, and the CLR), here's what I see when the same breakpoint hits:
Breakpoint 1 hit
...
0:000> u eip
00d20420 56 push esi
00d20421 8bf1 mov esi,ecx
00d20423 833d102f910000 cmp dword ptr ds:[912F10h],0
00d2042a 7405 je 00d20431
*** WARNING: Unable to verify checksum for C:\WINDOWS\assembly\NativeImages_v2.0.50727_32\mscorlib\4918a15d4f2a914781651294adafd2ce\mscorlib.ni.dll
00d2042c e8cd1e3779 call mscorlib_ni+0x2b1ecd (79371ecd)
00d20431 90 nop
00d20432 8bce mov ecx,esi
00d20434 e893403779 call mscorlib_ni+0x2b4093 (79374093)
00d20439 cc int 3
Right off the bat, it's apparent that windbg is trying to locate symbols for mscorlib for some reason, even though this code doesn't call mscorlib. Also, windbg isn't able to convert what it believes is the target address in mscorlib into anything useful in terms of symbols. Furthermore, disassembling what windbg indicates is the target function being called (79374093) yields what I refer to in technical jargon as gobbletygook:
0:000> u 79374093
mscorlib_ni+0x2b4093:
79374093 2900 sub dword ptr [eax],eax
79374095 b801000000 mov eax,1
7937409a 8986880a0000 mov dword ptr [esi+0A88h],eax
793740a0 83be880a000001 cmp dword ptr [esi+0A88h],1
793740a7 0f94c0 sete al
793740aa 0fb6c0 movzx eax,al
793740ad 5e pop esi
793740ae 5f pop edi
Note, BTW, that the address windbg's disassembler computes (79374093) is actually the raw operand value in the call instruction:
0:000> dd eip+1 l1
00d20435 79374093
So it would seem that windbg, on this installation, isn't computing the target of the call relative to the current instruction pointer - it's just displaying the operand for the call instruction as-is.
To top it all off, if I single step (using windbg's 't' command) into the target method, I correctly 'land' at mscorwks!JIT_Throw:
0:000> u eip
00d20434 e893403779 call mscorlib_ni+0x2b4093 (79374093)
00d20439 cc int 3
0:000> t
eax=00913148 ebx=0012f4ac ecx=0128a084 edx=00000000 esi=0128a084 edi=00000000
eip=7a0944cc esp=0012f448 ebp=0012f478 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mscorwks!JIT_Throw:
7a0944cc 6894000000 push 94h
When I first saw this, I thought my symbol cache was corrupted or otherwise out of date. But doing .reload /f /o (even after manually deleting my local symbol file cache) didn't change the output. Since the Pentium doesn't seem to have any issues actually making the method call, I decided to manually decode the call instruction; comparing the same code on a working installation of windbg and this buggy one. So with the instruction pointer sitting at the address of the call instruction in question, I performed the same computation of the call target as shown above:
0:000> ? eip + 5 + poi(eip+1)
Evaluate expression: 2047427788 = 7a0944cc
THAT looks a lot better, since it corresponds to where the Pentium actually landed when the 't' command was used above. But just to round off the operation, here's confirmation of where that call instruction is going to land:
0:000> u 7a0944cc
mscorwks!JIT_Throw:
7a0944cc 6894000000 push 94h
7a0944d1 b8d497317a mov eax,offset mscorwks!GetManagedNameForTypeInfo+0x1b355 (7a3197d4)
7a0944d6 e848f3ddff call mscorwks!_EH_prolog3_catch (79e73823)
7a0944db 8bf9 mov edi,ecx
7a0944dd e8761bf0ff call mscorwks!ResetCurrentContext (79f96058)
7a0944e2 8d4d84 lea ecx,[ebp-7Ch]
7a0944e5 e81a09deff call mscorwks!LazyMachStateCaptureState (79e74e04)
7a0944ea 85c0 test eax,eax
This little exercise would seem to prove that the windbg disassembler is screwed up on one of my machines. The really frustrating thing (other than the time I've lost on this issue) is that I can't seem to repair windbg. I've blown away my symbol cache as mentioned earlier, removed the Debugging Tools for Windows, and downloaded (again, just to be sure) and then reinstalled DTW from scratch - all with no change in behavior. I've also compared the help/about screenshots of windbg's about box on the good/bad installations, and verified that they're supposedly identical.
The only difference between the two machines I've been comparing is that one (the working one) is a regular WinXP machine, while the other (the buggy one) is the same setup running in Virtual PC 2004. But the OS, CLR, and tools on the host and guest OS are (as near as I can tell) identical.
Basically, I'm stumped.
Suggestions, anyone?
 |
Thursday, October 05, 2006
I just read a notice about Google's search facility specifically targeting searches of source code: Google Code Search. Have only given it the once over, but it looks interesting (primarily because it supports regular expressions and source language specific filtering, as well as other filtering capabilities).
Monday, September 25, 2006
Towards the end of a course I was teaching last week, I gave my students an obfuscated programming challenge designed to test their understanding of several areas of both the CLR and C# that we'd been discussing throughout the course. In typical fashion, students had to predict the output of the program without the aid of any compilers - and they had to explain their answer to receive the prize.
However, I kept the code small enough to fit on a single projector screen, which prevented me from including a few things I had originally planned to test them on.
For your mental noodling enjoyment, the code below is the enhanced version of that challenge - unrestricted by screen space dimensions. Enjoy! (And remember - you can only use your eyes, brain and, if you like, a pencil and some paper.) // Obfuscated CLR/C# 2.0 programming challenge. Predict the output of this program without
// using any compiler to build & run the code. Explain your answer.
//
// Mike Woodring
// http://www.bearcanyon.com
// Bear Canyon Consulting LLC
//
using System;
using IAR = System.IAsyncResult;
using IE1 = System.Collections.IEnumerable;
using IE2 = System.Collections.IEnumerator;
using MRE = System.Threading.ManualResetEvent;
using PTS = System.Threading.ParameterizedThreadStart;
class P
{
static void Main()
{
MRE fe = new MRE(false);
MRE pe = fe;
foreach( char c in new MSG() )
{
MRE ce = new MRE(false);
PTS m = delegate(object s) { P p = (P)s; p.W.WaitOne(); Console.Write(p.C); p.G.Set(); };
m.BeginInvoke(new P(c, pe, ce), delegate(IAR ar) { ((PTS)ar.AsyncState).EndInvoke(ar); }, m);
pe = ce;
}
fe.Set();
pe.WaitOne();
Console.WriteLine();
}
public P(char c, MRE w, MRE g) { C = c; W = w; G = g; }
char C;
MRE W;
MRE G;
class MSG : IE1
{
public IE2 GetEnumerator()
{
int i = 0;
foreach (char ch in new NTE<SM.B>()) yield return (i++ == 0 ? char.ToUpper(ch) : ch);
yield return (',');
yield return (' ');
foreach (char ch in new NTE<SM.A>()) yield return (ch);
yield return ('!');
}
}
class NTE<PT> : IE1
{
public IE2 GetEnumerator()
{
foreach (Type t in typeof(PT).GetNestedTypes()) { yield return t.Name[t.Name.Length - 1]; }
}
}
class SM
{
internal struct A
{
public struct cow { }
public class too { }
public interface car { }
public enum will { }
public struct pad { }
}
internal class B
{
public class wish { }
public struct see { }
public interface hill { }
public enum ball { }
public class boo { }
}
}
}
 |
Wednesday, August 16, 2006
On certain machines, I'm repeatedly using remote desktop to access a Windows 2003 server so that I can debug something running in the context of ASP.NET (web app, web service, etc). Since various apps and services on these machines are segregated in different app pools, the first thing I have to do when I run something like VS.NET and bring up the attach-to-process dialog is figure out which running instance of w3wp.exe is the one I need to attach to. Sometimes I get lucky and pick the right instance on the first try. Other times I miss and have to try the others one at a time. While this isn't something that takes a tremendous amount of time, the repetitive nature of this drives me nuts.
So I finally sat down and wrote a little script to automate this annoying procedure for me. The result is a javascript file that I can just run with the name of the app pool I'm interested in debugging on the command line, like so:
c:\>apdebug AcmeAppPool
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.
Active app pools...
AcmeAppPool (2860)
MSSharePointAppPool (3268)
DefaultAppPool (3652)
Launching debugger for 'AcmeAppPool' (2860)...
The script enumerates the running instances of w3wp.exe, displaying the app pool they host and their corresponding process ID, then launches the JIT debugging dialog if the app pool specified on the command line was active. Since I'm very often interested in the same app pool on a given machine, a desktop shortcut to that script with the right app pool name on the command line gets me to the right spot with just a double click.
A more detailed description, with sample output and a screenshot, along with links to the script source you can view and/or download for your own use can be found here.
 |
Tuesday, May 09, 2006
Friday, March 24, 2006
Here's one for the "Huh…I didn't know that…although now that I do, it's probably not that important"-department.
When you let the C# 2.0 compiler capture variables for use within the body of an anonymous method you're passing to someone, the class that the compiler synthesizes for holding & passing the captured arguments to the target method is not marked [Serializable]. For example, consider the following program:
using System;
class Program { static void Main() { AppDomain ad2 = AppDomain.CreateDomain("Math Domain");
ad2.DoCallBack(CallDoMath); // Okay. ad2.DoCallBack(delegate { DoMath(2, 2); }); // Okay.
int x = 3; ad2.DoCallBack(delegate { DoMath(x, x); }); // SerializationException. }
static void CallDoMath() { DoMath(1, 1); }
static void DoMath(int a, int b) { Console.WriteLine( "[{0}] {1} + {2} = {3}", AppDomain.CurrentDomain.FriendlyName, a, b, a + b ); } }
At (1), a request is made to invoke the CallDoMath method in a different AppDomain than the one Main is executing in. AppDomain.DoCallback expects a reference to a CrossAppDomainDelegate. Since the signature of CallDoMath matches the signature of CrossAppDomainDelegate, the compiler is happy to expand ad2.DoCallBack(CallDoMath) into ad2.DoCallback(new CrossAppDomainCallback(CallDoMath)) for me. The result is that CallDoMath executes in the context of the AppDomain named "Math Domain".
At (2), an anonymous delegate is used to call DoMath(2, 2) "directly". In typical anonymous delegate fashion, this approach just saved me from having to write a stub like CallDoMath that conforms to the signature of CrossAppDomainDelegate. Instead, I've let the compiler write that little method for me. The results are the same as for (1).
But at (3), I've reference a local variable named 'x' within the body of the anonymous method that will call DoMath(x, x). That variable is a local variable located on the callstack for the Main method. In this situation, the compiler conspires to 'capture' that variable into an object on the heap, the reference to which is passed to the compiler-synthesized anonymous method. So even though it looks like I'm referencing the variable 'x' directly from within the anonymous method, I'm really dereferencing a field of an object. This line of code results in a SerializationException because (a) it's a cross-AppDomain call, which requires that all parameters be either [Serializable] or extend MarshalByRefObject and (b) the compiler-synthesized class that holds the captured variables is neither.
In retrospect, this is easy enough to see using ILDASM or Reflector. But even though I've looked at the compiler-synthesized class for captured arguments before, it's not something I ever paid attention to until I bumped into it a few minutes ago tinkering with some multi-AppDomain code.
At any rate, there's a bit of trivia for the archives…
|
|