none
Cygwin fork and RtlCloneUserProcess

    Question

  • Hi,

    yes, I know, I'm on undocumented and unchartered territory again.  Nevertheless, let me explain...

    As you probably know I'm one of the core maintainers of Cygwin.  For those who don't know Cygwin, Cygwin is an Open Source POSIX emulation layer running in the Win32 subsystem, in contrast to SUA which is a POSIX subsystem on its own.  As we already learned a couple of times in the past, the SUA POSIX subsystem has a couple of advantages over the Win32 subsytem, NT kernel-wise, when it comes to implementing POSIX-like behaviour.  For instance, the NtSetInformationProcess(ProcessAccessToken) API is blocked for Win32 processes since Windows Vista.  But I digress.

    One of our biggest all-time problem in Cygwin is the implementation of the POSIX fork call.  What we do so far is basically to start the same executable again using CreateProcess, then, in the child, longjmp to fork, copy all R/W data from parent to child to create a copy of the parent memory in the child, do some necessary synchronisation tasks with the parent, reset or recreate some of the internal data structures, and go ahead.

    This is slow and, worse, it has a big problem.  The memory layout of Win32 components is not necessarily exactly the same.  Thread stack positions change, heap allocations change, etc.  Usually the memory layout of the Win32 stuff is not important for Cygwin, even after fork, but the problem is, sometimes the Win32 stuff gets allocated in a memory location which would be needed by Cygwin.

    For instance, a DLL loaded in the parent process has to be loaded into the exact same spot in the child so that duplicating the data and bss segments result in an exact copy, otherwise the fork failed.  But there's no way at all to specify the address at which a DLL has to be loaded, other than the default load address in the DLLs file header.  In case of address collisions, DLLs are rebased on the fly by the Windows loader.  But unfortunately they are not necessarily rebased to the same address as in the parent.  So, while we can duplicate the parent most of the time, it's not foolproof, and especially DLL memory locations are a constant source of trouble. Rebasing the DLLs so that their default load addresses don't collide is a necessity, practically every time packages containing DLLs are updated, but even that is not safe, and ASLR since Vista made things worse.

    Lately I found that Windows Vista introduced an undocumented function call RtlCloneUserProcess, which is also available in Windows 7 and 8 CP.  After some reading on the net and some playing around with this call, I found out that it works and is actually some kind of fork implementation.  My test application forked successfully, and basic things worked out of the box.

    I was very excited, especially because RtlCloneUserProcess seemed to be a way out of the rebase misery.  But I found pretty quickly that it was too early for enthusiasm.  While some basic stuff worked, other basic stuff didn't work in the child.  For instance, if running in a console, output to the console didn't show up. LoadLibrary always returned ERROR_INVALID_HANDLE, unless the DLL was already loaded in the process.  A call to MessageBox simply crashed.  I tried to get this working, including calling more undocumented functions like CsrClientConnectToServer, but nothing worked.  Eventually I found out that the RtlCloneUserProcess was used by one system component only, the SUA POSIX subsystem...

    So, at long last, here's my question.

    Is there any chance at all, to get this call working in the Win32 subsystem?  If so, is there any chance at all that we could get information from Microsoft how to do it, so that we can get a quick and reliable fork in Cygwin, even given that Cygwin is Open Source?  Alternatively, is there any chance we could get some information on how to avoid the aforementioned memory layout and rebase problems using only documented calls?


    Thanks in advance,
    Corinna

    Tuesday, March 20, 2012 11:25 AM

All replies

  • Hi, Corinna

    I am also looking for the help to emulate fork functionality using RtlCloneUserProcess. But i could not call this function successfully. I am getting some negative staus. Could you plz tell how to use this function. Here  i am  pasting the exapmle i tried.

    #include

    <iostream>

    #include

    <Windows.h>

    #include

    <WinNT.h>

    #include

    <winternl.h>

    typedef

    struct _CLIENT_ID

    {

    HANDLE UniqueProcess;

    HANDLE UniqueThread;

    } CLIENT_ID, *PCLIENT_ID;

     

    typedef

    struct _SECTION_IMAGE_INFORMATION {

    PVOID EntryPoint;

    ULONG StackZeroBits;

    ULONG StackReserved;

    ULONG StackCommit;

    ULONG ImageSubsystem;

    WORD SubSystemVersionLow;

    WORD SubSystemVersionHigh;

    ULONG Unknown1;

    ULONG ImageCharacteristics;

    ULONG ImageMachineType;

    ULONG Unknown2[3];

    } SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION;

     

    typedef

    struct _RTL_USER_PROCESS_INFORMATION {

     

    ULONG Size;

    HANDLE ProcessHandle;

    HANDLE ThreadHandle;

    CLIENT_ID ClientId;

    SECTION_IMAGE_INFORMATION ImageInformation;

    } RTL_USER_PROCESS_INFORMATION, *PRTL_USER_PROCESS_INFORMATION;

     

    using

    namespace std;

    #define

    RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED 0x00000001

    #define

    RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES 0x00000002

    #define

    RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE 0x00000004 // don't update synchronization objects

    // end_rev

    //private

    typedef

    int (*EntryPointfuncPtr)(__in ULONG ProcessFlags, __in_opt PSECURITY_DESCRIPTOR ProcessSecurityDescriptor, __in_opt PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, __in_opt HANDLE DebugPort, __out PRTL_USER_PROCESS_INFORMATION ProcessInformation );

    NTSTATUS

    RtlCloneUserProcess( __in ULONG ProcessFlags, __in_opt PSECURITY_DESCRIPTOR ProcessSecurityDescriptor, __in_opt PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, __in_opt HANDLE DebugPort, __out PRTL_USER_PROCESS_INFORMATION ProcessInformation );

    VOID RtlExitUserProcess(NTSTATUS in1);

    typedef void (*exitfunct)(NTSTATUS);

    int

    main()

    {

    PRTL_USER_PROCESS_INFORMATION puif =

    new RTL_USER_PROCESS_INFORMATION();

    HINSTANCE LoadMe = 0;

    char* my_file = "C:/Windows/System32/ntdll.dll";

    LoadMe = LoadLibraryExA(my_file,NULL,0);

    // Check to see if the library was loaded successfully

    // if (LoadMe != 0)

    // printf("LoadMe library loaded!\n");

    // else

    // printf("LoadMe library failed to load!\n");

    EntryPointfuncPtr LibMainEntryPoint;

    LibMainEntryPoint = (EntryPointfuncPtr)GetProcAddress(LoadMe,

    "RtlCloneUserProcess");

    RtlZeroMemory(puif,

    sizeof(RTL_USER_PROCESS_INFORMATION));

    puif->Size =

    sizeof(RTL_USER_PROCESS_INFORMATION);

    int returncode = LibMainEntryPoint(0x4,NULL,NULL,NULL,puif);

    cout<<

    "Return Code from cloneprocess"<<returncode<<endl;

    return 0;

    }

     

    Thursday, March 22, 2012 12:41 PM
  • Hi Srinivasulu,

    What status code does RtlCloneUserProcess return?  Please note that a status code of STATUS_SUCCESS (0) means, you're back in the parent, while a status code of STATUS_PROCESS_CLONED (0x129) means your in the child process.  This is no error.  Also, setting the first parameter to 4 (== RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE)  seems wrong to me.  After all, if you want to clone a process, you need the handles of the parent process, so the first parameter should be at least

    RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES | RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE

    In my case, I built my test application with either the Cygwin GCC or the Mingw64 GCC.  For simplicity, I created a special libntdll.a which defines the RtlUserProcess@20 as exported symbol, so I didn't have to load the function dynamically.  With this tweak, I called the function like this:

    #define WINVER 0x0600
    #include <windows.h>
    #include <ntdef.h>
    
    #include <stdio.h>
    #include <unistd.h>
    
    #define RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED 1
    #define RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES  2
    #define RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE   4
    
    #ifndef STATUS_SUCCESS
    #define STATUS_SUCCESS                  ((NTSTATUS) 0)
    #endif
    #define STATUS_PROCESS_CLONED           ((NTSTATUS) 0x00000129)
    
    typedef struct _CLIENT_ID
    {
      ULONG UniqueProcess;
      ULONG UniqueThread;
    } CLIENT_ID, *PCLIENT_ID;
    
    typedef struct _SECTION_IMAGE_INFORMATION {
      PVOID EntryPoint;
      ULONG StackZeroBits;
      ULONG StackReserved;
      ULONG StackCommit;
      ULONG ImageSubsystem;
      WORD SubsystemVersionLow;
      WORD SubsystemVersionHigh;
      ULONG Unknown1;
      ULONG ImageCharacteristics;
      ULONG ImageMachineType;
      ULONG Unknown2[3];
    } SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION;
    
    typedef struct _RTL_USER_PROCESS_INFORMATION
    {
      ULONG Length;
      HANDLE Process;
      HANDLE Thread;
      CLIENT_ID ClientId;
      SECTION_IMAGE_INFORMATION ImageInformation;
    } RTL_USER_PROCESS_INFORMATION, *PRTL_USER_PROCESS_INFORMATION;
    
    typedef struct _PROCESS_BASIC_INFORMATION
    {
      DWORD_PTR ExitStatus;
      PVOID PebBaseAddress;
      DWORD_PTR AffinityMask;
      DWORD_PTR BasePriority;
      ULONG_PTR UniqueProcessId;
      ULONG_PTR InheritedFromUniqueProcessId;
    } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;
    
    typedef enum _PROCESSINFOCLASS {
      ProcessBasicInformation = 0
    } PROCESSINFOCLASS;
    
    NTSTATUS NTAPI
    RtlCloneUserProcess (ULONG, PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR,
                         HANDLE, PRTL_USER_PROCESS_INFORMATION);
    NTSTATUS NTAPI NtQueryInformationProcess (HANDLE, PROCESSINFOCLASS, PVOID,
                                              ULONG, PULONG);
    
    int main (int argc, char **argv)
    {
      SECURITY_ATTRIBUTES sa;
      HANDLE maph = NULL;
      char *map = NULL;
      NTSTATUS status;
      RTL_USER_PROCESS_INFORMATION pi;
      PROCESS_BASIC_INFORMATION pbi;
    
      setvbuf (stdout, NULL, _IONBF, 0);
      setvbuf (stderr, NULL, _IONBF, 0);
    
      sa.nLength = sizeof sa;
      sa.lpSecurityDescriptor = NULL;
      sa.bInheritHandle = TRUE;
      maph = CreateFileMapping (INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE,
                                0, 4096, NULL);
      if (!maph)
        fprintf (stderr, "CreateFileMapping failed, err %lu\n", GetLastError ());
      else
        {
          map = (char *) MapViewOfFile (maph, FILE_MAP_WRITE, 0, 0, 4096);
          if (!map)
            fprintf (stderr, "MapViewOfFile failed, err %lu\n", GetLastError ());
        }
    
      if (map)
        {
          printf ("map at %p\n", map);
          strcpy (map, "Alle meine Entchen");
        }
    
      status = RtlCloneUserProcess (RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES
                                    | RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE,
                                    NULL, NULL, NULL, &pi);
      switch (status)
        {
        case STATUS_SUCCESS:
          printf ("RtlCloneUserProcess in parent %lu, child pid  = %lu\n",
                  GetCurrentProcessId (), pi.ClientId.UniqueProcess);
          if (map)
            {
              printf ("parent map: <%s>\n", map);
              Sleep (1000L);
              strcpy (map, "schwimmen auf dem See");
              Sleep (2000L);
              printf ("parent map: <%s>\n", map);
            }
          break;
        case STATUS_PROCESS_CLONED:
          status = NtQueryInformationProcess (GetCurrentProcess (),
                                              ProcessBasicInformation,
                                              &pbi, sizeof pbi, NULL);
          if (!NT_SUCCESS (status))
            {
              fprintf (stderr, "NtQueryInformationProcess failed, status 0x%08lx\n",
                       status);
              printf ("RtlCloneUserProcess in child  %lu\n",
                      GetCurrentProcessId ());
            }
          else
            printf ("RtlCloneUserProcess in child  %lu, parent pid = %lu\n",
                    GetCurrentProcessId (), pbi.InheritedFromUniqueProcessId);
          if (map)
            {
              printf ("child map: <%s>\n", map);
              Sleep (2000L);
              printf ("child map: <%s>\n", map);
              strcpy (map, "Koepfchen in das Wasser");
            }
          printf ("child exits\n");
          break;
        default:
          fprintf (stderr, "RtlCloneUserProcess failed, status 0x%08lx\n", status);
          break;
        }
      return 0;
    }

    But keep in mind that you won't see any output from the child when running this in a console window.  Either you run this in a Cygwin terminal like Mintty, or you have to redirect the output into a file to see what happened.

    Again, it would be really nice if Microsoft could help us to get a real fork working in Cygwin, if that's possible at all.  It's a pity that this function is only available to SUA so far.

    Corinna


    Thursday, March 22, 2012 2:32 PM
  • Hi, Corinna

    Thank you  for the help. Actually i was running the program  on Windows Server 2008 and got negative number for the return status. Today ran the same  in Windows7 system. i am able to duplicate the process. but  i got corrputed  stack problem for RTL_USER_PROCESS_INFORMATION  variable pi at parent process return statement.

    As per microsoft site, RtlCloneUserProcess works if WINVER is 0x0600 or above. I have to emulate the fork call on Windows server 2008. is not it possible?

    Regards

    Srinivasulu 

    Friday, March 23, 2012 6:47 AM
  • Hi Srinivasulu,

    my example works on 2008 exactly as on W7 or 2008 R2.  The failures when calling certain Win32 functions are the same, too.

    Corinna

    Friday, March 23, 2012 11:21 AM
  • Again, it would be really nice if Microsoft could help us to get a real fork working in Cygwin, if that's possible at all. It's a pity that this function is only available to SUA so far.

    Corinna,

    These forums probably are not the best place to get attention of MS.

    Your chances may be better if you could approach some manager involved in Win8 development, and mention that, after planned removal of SUA on win8, Cygwin will remain the only viable compatibility toolkit.

    Regards,
    -- pa

    Saturday, March 24, 2012 3:17 AM
  • Hi Pavel,

    I would do that immediately, if I knew how to contact them.  The only points of contact I know of are these forums, the connect site, or by opening a support contract via my MSDN subscription.  Do you have a suggestion how to go forward?

    Thanks,
    Corinna

    Saturday, March 24, 2012 9:56 AM
  • Well, a quick goo... er, binging in MSDN blogs immediately reveals some names of Win8 Kernel PMs.

    http://blogs.msdn.com/search/searchresults.aspx?q=program%20manager%20Kernel%20team§ions=12943

    Sorry, this is not a too helpful idea, but only these people know the answer. Anything found on "indocumented" web sites may just break after some Patch Tuesday.

    Regards,

    -- pa

    • Edited by Pavel A Sunday, March 25, 2012 1:18 AM
    Saturday, March 24, 2012 7:40 PM
  • Oh well, I had hoped there's some kind of "official" channel for this kind of contact.  I'm not feeling overly comfortable writing unsolicited private email.  But thanks all the same.

    As far as undocumented goes, I had hoped that there's a chance to revisit the state of that call and something like a receipt how to utilize it in the Win32 realm.  The most important problem here is the reproducability of the parent memory layout and avoiding the DLL rebase problem.

    Corinna

    Sunday, March 25, 2012 9:06 AM
  • Hi, Corinna

    I could run my exapmple on windows 2008 SP1( with SP2 not successful) . My Actual application is server application, which has many threads and one thread is waiting on tcp port for incoming requests. When i called RtlCloneUserProcess with the args (RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES, NULL, NULL, NULL, pi), the child process exists with access violation error. i think this is because of the arguments 2nd and 3rd. Could you plz help me to fix this or share any document about this function.

    Regards

    Srini 

    Tuesday, March 27, 2012 1:19 PM
  • Srinivasulu,

    I can't help you, sorry.  I know exactly as much as has been outlined in my postings in this thread.  I have no documentation about this function nor does any exist as far as I know.  Otherwise I wouldn't have opened this thread to get this function working in Cygwin in the first place, right?

    Corinna

    Tuesday, March 27, 2012 2:49 PM
  • >>Corinna wrote: Is there any chance at all, to get this call working in the Win32 subsystem?

    For Win32 you'd have to locate all the user and gdi handles, duplicate the resources (since those objects don't have a refcount on the kernel side) and then overwrite all the old handle values in the forked process with the duplicated ones. Even then AFAIR, things like HDCs don't have a user land duplicate/copy function. I don't know which bits of Windows Cygwin uses so these things might not be important.

    For the console, you do need to re-establish a link to csrss, but the setup is only done in ntdll if the CsrPort handle is NULL. This isn't the case in the forked process which of course, has the parent processes handle value. Nowhere in ntdll sets it to back to NULL so there's no chance of getting that to work unless you d/l the ntdll symbols, find where the handle is, and NULL it out yourself. Inheriting the handle is no good, since connecting to csrss also maps in a shared memory section which goes missing in the forked process.

    I haven't checked but the non-inheriting of shared memory sections is probably why MessageBox crashes, as it uses one to get the approprate strings for IDOK buttons etc. This is mapped in by User32's DllMain or by the exported ClientDllInitialize, though again this has internal checks to stop double connection.

    Of course, all those are undoc'd implementation details subject to change yadda yadda, but everybody knows that already. Ignoring all the lengths you have to go to, I'd guess its possible to emulate a more complete fork using this, but it's not very practical, what with requiring the downloading of symbols to be absolutely sure.

    There's probably something I forgot, but that's about the initial scope of labour required for a third party.

    • Edited by adeyblue Wednesday, March 28, 2012 3:18 PM
    Wednesday, March 28, 2012 3:17 PM
  • adeyblue wrote:
    > For Win32 you'd have to locate all the user and gdi handles, duplicate the resources (since those objects don't have a refcount on
    > the kernel side) and then overwrite all the old handle values in the forked process with the duplicated ones. Even then AFAIR, things
    > like HDCs don't have a user land duplicate/copy function. I don't know which bits of Windows Cygwin uses so these things might not
    > be important.

    Well, the point is that, in theory, every Cygwin process is free to use whatever Windows function it wants.  There are a few restrictions in terms of current working directory and environment usage which should be handled plainly on the POSIX side to avoid trouble.  Cygwin itself doesn't use a lot of user32 functionality and usually doesn't keep any handles open.  But we have no control what the application does.

    > For the console, you do need to re-establish a link to csrss, but the setup is only done in ntdll if the CsrPort handle is NULL. This
    > isn't the case in the forked process which of course, has the parent processes handle value. Nowhere in ntdll sets it to back to NULL
    > so there's no chance of getting that to work unless you d/l the ntdll symbols, find where the handle is, and NULL it out yourself.
    > Inheriting the handle is no good, since connecting to csrss also maps in a shared memory section which goes missing in the forked
    > process.

    I'm with you in terms of the CsrPort handle, but as far as shared memory goes, the forked process actually inherits the shared memory.  Have a look into my test application above, it tests this issue explicitely.  My first test used Cygwin's mmap in fact, and the reason to test that was that so far Cygwin has to reinstantiate the shared memory regions after every fork, which also takes a lot of time and potentially suffers badly if DLLs are rebased by the WIndows loader.

    > I haven't checked but the non-inheriting of shared memory sections is probably why MessageBox crashes

    As far as I can see it's more like a problem with using a user handle which has no meaning in the forked child.

    > There's probably something I forgot, but that's about the initial scope of labour required for a third party.

    This sounds worrysome.  Even if that works, I'm certain I'd need help to get all the bits right.

    Alternatively, while this doesn't make fork faster, it would be already helpful if there would be some other way to make sure that a newly started process has the same basic memory layout as the parent, so that we don't have so much problems with DLLs rebased by the Windows loader.

    Corinna

    Thursday, March 29, 2012 9:09 AM
  • > I'm with you in terms of the CsrPort handle, but as far as shared memory goes, the forked process actually inherits the shared memory.  Have a look into my test application above, it tests this issue explicitely

    Your code tests user-mapped shared memory. Not that MSDN documents it, but these are explicitly mapped into forked child processes because MapViewOfFile calls NtMapViewOfSection with the SECTION_INHERIT parameter set to ViewShare. The system mappings aren't inherited because they set the parameter to ViewUnmap. If you modify your code to use that internal function (its documented on MSDN here) you'll see it isn't inherited.

    > Alternatively, while this doesn't make fork faster, it would be already helpful if there would be some other way to make sure that a newly started process has the same basic memory layout as the parent, so that we don't have so much problems with DLLs rebased by the Windows loader.

    NtCreateProcess can give you a cloned address space only, minus the aforementioned shared sections and any threads. But again, you have to tell csrss, reconnect to User etc. so what it would save you on one hand, it undoubtedly costs you with the other.

    Thursday, March 29, 2012 8:02 PM
  • > Your code tests user-mapped shared memory. Not that MSDN documents it, but these are explicitly mapped into
    > forked child processes because MapViewOfFile calls NtMapViewOfSection with the SECTION_INHERIT parameter
    > set to ViewShare. The system mappings aren't inherited because they set the parameter to ViewUnmap.

    Interesting.  I wasn't aware of that difference. I simply assumed that all user space DLLs like user32.dll would use the ViewShare flags or even just simply the Win32 MapView... call.

    Actually, since my first tests using Cygwin's mmap, which uses the NtMapViewOfSection code with the ViewShare mapping explicitely., and the same code converted to Win32 calls just worked, I simply forgot about the flag at all.

    Corinna

    Friday, March 30, 2012 1:08 PM
  • Hi, Corinna

    I am getting following NTSTATUS from the routine RtlCloneUserProcess STATUS_INVALID_PAGE_PROTECTION(0xC0000045L). This is happening on Win2008 R2.

    And i am calling the function with the process flag RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES only. do you have any idea why  i am getting this error.?

    I am also running the program as Admin .

    Regards

    Srini

    Tuesday, April 03, 2012 7:29 AM
  • Hi Srinivasulu,

    my last reply is still valid.  I don't know any more than has been already discussed in this thread.

    Corinna

    Tuesday, April 03, 2012 11:16 AM
  • Hmm.. how ever Thank you for your help, Corinna. 

    Regards

    Srini

    Wednesday, April 04, 2012 3:29 PM
  • Hi,

    There are a couple of undocumented functions or structures around RtlCloneUserProcess. Knowing one of them is not enough to use it correctly. And there is no guarantee for a product using undocumened APIs. Thank you for understanding.

    I searched and there is a similar thread at http://forum.sysinternals.com/duplicate-a-process_topic24863.html.

    Best regards,

    Jungang Bai 

    Friday, April 13, 2012 7:11 AM
  • Hi Jungang,

    I'm fully aware that there's no guarantee when using undocumented calls.

    However, the idea my request was specificially to ask Microsoft if it's possible to open up the "undocumented" policy concerning RtlCloneUserProcess and it's companion calls, to allow Cygwin to implement a quick and reliable fork implementation in the Win32 user space, at least starting with Windows Vista.

    If that's not possible for whatever reason, is there a chance to get information how to reliably avoid the DLL rebase problems when creating a duplicated process the hard way (CreateProcess/copy stuff/longjmp), as Cygwin uses it today?

    I knew the thread you mentioned, but apart from an interesting start, it deteriorates pretty quickly into a principle discussion about how to learn coding, unfortunately.

    Thanks,
    Corinna

    Tuesday, April 17, 2012 11:36 AM
  • FYI:  I've cross-posted this thread to the Interoperability Scenarios: Technical Questions forum in the Open Specifications Forums, with a brief (and I hope accurate) summary of the issue:  

    http://social.msdn.microsoft.com/Forums/en-US/os_interopscenarios/thread/6fcef39c-1f24-40d9-bcbb-8d5e70a089c5

    If we're all very lucky it may get more of the right kind of attention in that context.  In any case, it seemed worth a try.



    Friday, May 25, 2012 7:32 PM