This scenario is not supposed to work in this Rotor release. There is support in the PAL SEH emulation to make it work better though. The key is to wrap the delegate by PAL_LocalFrame in the marshaller which we currently don't do. There is just a small comment in the implementation of PAL_LocalFrame in pal\win32\win32pal.c to suggest that this is possible. You could try to wrap the delegate in PAL_LocalFrame by yourself as a workaround.
Even if the PAL_LocalFrame is used, there will be a small differences in the order filters and handlers get executed under some circumstances. -Jan This posting is provided "AS IS" with no warranties, and confers no rights. -----Original Message----- From: Cristian Diaconu [mailto:[EMAIL PROTECTED]] Sent: Wednesday, May 01, 2002 3:04 AM To: [EMAIL PROTECTED] Subject: [DOTNET-ROTOR] SEH Interop via PAL - .NET vs Rotor Here's a scenario that behaves differently (and incorrectly IMHO) in Rotor vs .Net. (Sorry if it's a bit long - code included at the end of the message): The facts: 1. I have an assembly that starts with managed code in main() and calls an unmanaged function "UnmanagedException(...)" in a DLL called UnmanEx.dll. 2. The UnmanagedException(...) function takes a delegate as a parameter and uses it to perform a call back into managed code (same module where main() is implemented, not that it make any difference). 3. The code for the callback (implemented in EvHandlerClass::EvHandler ( ... )) does nothing more than throw a new Exception() while inside a try/finally block. (well, it also prints some messages, but those are for tracking purposes only - no relevance to the current problem). Also note that the code for the callback does NOT contain any exception handling code whatsoever. 4. The code in UnmanEx.dll::UnmanagedException( Delegate ) simply calls the delegate parameter inside a VC++ style __try/__except block. The filter for the block just "eats away" any exception it receives (empty block with EXCEPTION_EXECUTE_HANDLER). Essentially, the test creates a base layer in managed code (call it A), that calls a layer in unmanaged code (call it B). Further B sets up a "raw SEH" exception frame and calls back in managed code layer C which sets up a finally frame and throws an exception. Thinking about it, the code should behave as follows: A calls B. B sets up exception frame ExB and calls C. C throws an exception but has a finally block FbC. B now must catch the exception thrown by C and must silently "eat it away" by executing its empty block. This is the first pass. In the second pass, the finally block for C (Fbc) should get executed after which the execution should continue without any exception back into A and out of main(). (I hope the included code would make this clearer.) Lo and behold, this is exactly what happens in .NET! In Rotor however, the VC++ "raw SEH" frame seems to mess things up royally. Depending on whether you run it in the debugger or at the command line you get either: >>>>>>>>>>>>>>> PAL Assertion failed - pRegistration == NULL || (PVOID)pRegistration > (PVOID)&pRegistration - line 57 in file .\exception.c Unhandled exception at 0x77f7f570 in clix.exe: User breakpoint. PAL Assertion failed - pRegistration == NULL || (PVOID)pRegistration > (PVOID)&pRegistration - line 63 in file .\exception.c Unhandled exception at 0x77f7f570 in clix.exe: User breakpoint. <<<<<<<<<<<<<<< or the more radical >>>>>>>>>>>>>>> Assert failure(PID 2616 [0x00000a38], Thread: 2868 [0xb34]): ! m_fPreemptiveGCDisabled File: d:\work\dotnet\rotor\sscli\clr\src\vm\threads.h, Line: 899 Image: D:\Work\dotnet\rotor\sscli\build\v1.x86fstchk.rotor\clix.exe <<<<<<<<<<<<<<< I believe the issue is the manner in which PAL's SEH emulation works. PAL simulates SEH by installing a global exception filter (PAL_ExceptionFilter) in "win32pal.c" PAL_Startup(...). Furthermore there are a bunch of macros and helper functions (most notably PAL_TryHelper/PAL_EndTryHelper in "exception.c") that keep a linked list of exception frames (essentially glorified setjmp buffers) in a TLS slot. This creates a parallel structure of exception frames to the one normally available in fs:[0]. This works fine IF either: 1. PAL_ExceptionFilter gets to see every exception in the system first and has a chance to do it's dispatching. 2. Everybody using ROTOR uses the PAL implementation of SEH instead of the VC++ provided one. Point 2. above is the sticky one. If I was to think of a solution, I would say that the stubs between managed and unmanaged code must get an opportunity to filter *all* the exceptions without having to wait for PAL_ExceptionFilter to kick in. This implies that managed/unmanaged stubs have their own "raw SEH" frames linked to fs:[0]. I guess that's how the .NET implementation works, but I'm afraid that this approach is not portable (which is why it changed in Rotor?). In fact, the PAL documentation includes a note about the SEH implementation that's quite helpful. Originally I assumed that Rotor does exactly what I suggest above by reading the following in the PAL docs: "Unfortunately, the try/catch block around the jitted code cannot be the regular C/C++ SEH try block. It is the native EH record plugged directly into the fs:[0] chain in the Windows i386 version." That's not the case though, which opens the question of whether Win32 "raw SEH" can be used with Rotor at all? This, I guess, would be a big deal for any "real life" projects. Thanks for the patience reading all of this :-), Cristian // CallUnmanEx.il .assembly CallUnmanEx {} .assembly extern mscorlib {} .module extern UnmanEx.dll .class public sealed auto autochar EventHandlerDelegate extends [mscorlib] System.MulticastDelegate { .method public hidebysig specialname rtspecialname void .ctor( class System.Object obj, void* meth ) runtime managed {} .method public virtual instance void Invoke( int32 x ) runtime managed {} } .class public EvHandlerClass { .method public hidebysig static pinvokeimpl("UnmanEx.dll" cdecl) void UnmanagedException(class EventHandlerDelegate x) cil managed preservesig {} .method public hidebysig static pinvokeimpl("UnmanEx.dll" cdecl) void ThrowsException() cil managed preservesig {} .method public specialname rtspecialname void .ctor() il managed { .maxstack 1 // call super constructor ldarg.0 call instance void [mscorlib]System.Object::.ctor() ret } .method public instance void EvHandler( int32 notUsed ) cil managed { .maxstack 1 .try { ldstr "In EvHandler..." call void [mscorlib]System.Console::WriteLine( class System.String ) newobj instance void [mscorlib]System.Exception::.ctor() throw leave exitSEH } finally { ldstr "In EvHandler finally..." call void [mscorlib]System.Console::WriteLine( class System.String ) endfinally } exitSEH: ret } .method instance void function() il managed { .maxstack 2 ldstr "In function()...before call to UnmanagedException..." call void [mscorlib]System.Console::WriteLine( class System.String ) // load this instance as environment for method ldarg.0 // load method pointer ldftn instance void EvHandlerClass::EvHandler( int32 ) // the next line creates an instance of the delegate newobj instance void EventHandlerDelegate::.ctor(class System.Object, void*) call void EvHandlerClass::UnmanagedException( class EventHandlerDelegate ) ldstr "In function()...after call to UnmanagedException..." call void [mscorlib]System.Console::WriteLine( class System.String ) ret } } .method static public void main() il managed { .entrypoint .maxstack 2 newobj instance void EvHandlerClass::.ctor() call instance void EvHandlerClass::function() ret } // UnmanEx.dll typedef void (__stdcall *PFN)( int notUsed ); extern "C" __declspec(dllexport) void UnmanagedException( PFN delegate ) { __try { delegate( 0 ); } __except( EXCEPTION_EXECUTE_HANDLER ) {} }
