Thursday, October 15, 2009

In the last post, I have covered an aspect of sandboxing – unexpected AppDomain switches caused by thread promotion. In this post, I will focus on sandboxing, too. However, this time the primary focus is not security but performance. Nevertheless, at the end of this post, I will present an idea that might cause you less pain if you think about the thread-promotion issue.

So what are the performance issues of executing plug-ins in sandboxed AppDomains? Creating and managing AppDomains internally is by far not the most expensive part here. Typically, calls across application domains have much more impact on performance. There are different options for calling across application domains:

  • AppDomain.DoCallback
  • Calling objects in other application domains via transparent proxies
  • ICLRRuntimeHost::ExecuteInAppDomain (a COM-based API internally used by msclr/appdomain.h and its various call_in_appdomain templates)

Compared to calls inside an appdomain, all of these options are dark slow. Even if you use an optimization described here, there is a significant cost involved in cross AppDomain calls. For scenarios that require a lot of calls between the host application and the plug-in and vice versa, this is often not acceptable.

A customer of mine recently came up with a simple but very interesting idea that I have not considered so far: Why should the plug-in be executed in a different AppDomain? Can’t we create a sandboxed AppDomain in which only our assemblies and system assemblies have full-trust permissions whereas the plug-ins can only use a restricted permission-set?

Like most interesting questions, this question is not easy to answer. I do not claim that I have the ultimate answer to this question, but I want to explain my opinion here. At the end of the day it comes down to the question what are the real benefits an AppDomain can give you? In a later post, I will likely address AppDomains in more detail. For now it is sufficient to know that AppDomains can provide isolation; they are boundaries especially for

  • assembly and type loading (and unloading)
  • configuration
  • CAS security assignments

The first use-case for AppDoamins was ASP.NET. I think it is in fact fair to say, that many features of AppDomains only exist because the ASP.NET team needed them. However, this does not mean that these features are needed in all plug-in scenarios. Let’s take a look at each item mentioned above:

Assembly and type loading (and unloading): If you need hot-pluggable plug-ins (those that can be plugged in and out at runtime) you have to use AppDomains. AppDomains offer shadow copying of loaded assemblies which allows you to overwrite the assembly while it is loaded and, as explained here, shutting down AppDomains is the only way to unload assemblies. However, for many pluggable applications, it is acceptable that a restart of the application is required to replace a plug-in.

Configuration: While it can be convenient if each plug-in can have its own configuration file, it is seldom a strict requirement. For many applications, it is acceptable if the app and the plug-ins have to share a configuration file. In my experience, a plug-in typically receives its configuration as creation-parameters from the host application and not via configuration files.

CAS permission assignments: For this aspect the answer is not that easy. Is it more secure if a plug-in is executed in a separate sandboxed application domain? Isn’t it sufficient if the host and the plug-in are executed in a sandboxed application domain in which the host’s code has full-trust permissions and the plug-in is executed with restricted permissions? Are there aspects that make it easier for a sandboxed plug-in assembly to exploit assemblies in its own application domain than to exploit assemblies in other application domains? I am not aware of such a case, but I do not dare to say “no” here. Maybe there is a CAS permission that I have not thought of, and maybe you can use it in a way that I have not considered yet. If such a permission exists and if this permission is granted to the sandboxed plug-in, it could bypass CAS. However, if your sandbox has only the permission to execute type-safe code (SecurityPermissionFlag.Execute) and if everything else the plug-in needs is provided by an types that the host application provides to the plug-in developer, the plug-in would not have such a permission. Therefore my personal answer to this question is: For a plug-in that has only the execution permission, I consider it safe to have the application and the plug-in executing in the same sandboxed application domain. If you disagree with me, I would be more than happy if you could tell me your opinion (my email address alias is my firstname the server name is heege.net).

If you agree with me, it is time to discuss how to create a sandboxed application domain. One option is to use the simple sandboxing API, however, there are two disadvantages in this case:

  • You would have to name each and every non-GACed assembly that should have full-trust permissions. For realistic applications this list could be quite long.
  • As discussed here, it is easy to write code that causes unintended switches to the (full-trusted) default application domain

Key to solving the latter issue is sandboxing the default application domain: If the default application domain is sandboxed, so that a plug-in executes only with the permission to execute type-safe code, and if the plug-in as well as the host application execute in the same application domain, there is no need to create another application domain and thread-promotion automatically ends up in the right application domain.

Sandboxing the default application domain requires a custom AppDomainManager implementation. In this implementation you can to specify a HostSecurityManager. This HostSecurityManager could then grant full-trust permissions to all assemblies with your public key and execution-only permissions to all other assemblies. (Notice that assemblies from the GAC are always full-trust assemblies). The following C# code shows how to implement and AppDomainsManager:

// AppDomainSandboxer.cs
// compile with: csc /t:library /keyfile:keyfile.snk AppDomainSandboxer.cs

using System;
using System.Reflection;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;

namespace AppDomainSandboxer
{
  public class SandboxingAppDomainManager : AppDomainManager
  {
    SandboxingHostSecurityManager hostSecurityManager =
             new SandboxingHostSecurityManager();

    public override HostSecurityManager HostSecurityManager
    {
      get
      {
        return hostSecurityManager;
      }
    }
  }

  public class SandboxingHostSecurityManager : HostSecurityManager
  {
    private static PermissionSet psExecute;

    private static PermissionSet psFullTrust;

    private static StrongNamePublicKeyBlob publicKeyBlob;

    static SandboxingHostSecurityManager()
    {
      psExecute = new PermissionSet( PermissionState.None);
      psExecute.AddPermission( new SecurityPermission(
                                     SecurityPermissionFlag.Execution));
      psFullTrust = new PermissionSet( PermissionState.Unrestricted);
      foreach ( object evObj in Assembly.GetExecutingAssembly().Evidence)
      {
        StrongName sn = evObj as StrongName;
        if (sn != null)
        {
          publicKeyBlob = sn.PublicKey;
          return;
        }
      }
      throw new Exception( "No public key found in AppDomainSandboxer assembly");
    }

    public override HostSecurityManagerOptions Flags
    {
      get
      {
        return HostSecurityManagerOptions.HostResolvePolicy;
      }
    }

    public override PermissionSet ResolvePolicy( Evidence evidence)
    {
      foreach ( object evObj in evidence)
      {
        StrongName sn = evObj as StrongName;
        if (sn != null)
        {
          if (sn.PublicKey.Equals(publicKeyBlob))
          {
            return psFullTrust;
          }
          return psExecute;
        }
      }
      return psExecute;
    }
  }
}

There are two options to use this SandboxingAppDomainManager inside an application: You can either start the application with two special environment variables named APPDOMAIN_MANAGER_ASM and APPDOMAIN_MANAGER_TYPE as described here, or you can create a native application that hosts the CLR.

10/15/2009 9:40:45 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |   |  Trackback
 Friday, October 09, 2009

One of the benefits of managed code is sandboxed execution: To avoid that malicious code is executed by plug-ins, an application does not need to use a separate language with restricted features. Plug-ins can be written in any .NET language, because the host can execute them in a sandbox with restricted permissions. Implementing a library that can be called by sandboxed code requires the infamous AllowPartiallyTrustedCallers attribute. With this attribute, you explicitly state that you are confident that your library does not have security vulnerabilities. If your library has to interoperate with native code, you should surely know about a pitfall that can easily allow a user of your library to bypass the sandbox. This potential vulnerability can be caused by a CLR interop feature called thread promotion. In this post, I will explain this pitfall and how to avoid it in your libraries. However, before you can understand the impacts of thread promotion for CAS, I have to explain a little bit more about sandboxing first.

To execute code in a sandbox, the CLR offers an API called the simple sandboxing API. This API creates a new AppDomain in which only assemblies form the GAC and a set of explicitly specified assemblies are executed with full-trust permissions. All other assemblies are executed with permissions specified in a permission-set that is passed to this API.

Obviously, the permission-set that is granted to plug-ins should not contain several permissions. These include the skip-verification permission which allows you to execute code that is not proven to be type-safe and the permission to execute unmanaged code. Both permissions could be used to bypass the sandbox. The more permissions an app grants to a sandbox, the more features can be used by the plug-in. In my opinion, most sandboxes should only have the permission to execute code. All other features it needs should be provided by custom libraries that the host application implements.

Many libraries that are called by plug-ins have to call native code. If the native code called by such a library uses threads that have not executed managed code so far (native-only threads) or if you use technologies that can automatically switch to other threads (like COM), you have to consider thread promotion. Thread promotion is done when a native-only thread invokes a native->mananaged thunk. Normally, native->managed thunks simply stay in the AppDomain that the managed thread is executing. (Due to the .retainappdomain flag of the .vtfixup metadata in the assembly manifest.) However, when a native->managed thunk promotes a native-only thread to a managed thread, it has to pick an AppDomain. Because in this case the CLR does not have any information about which AppDomain is supposed to execute the managed function, the thread starts its managed life in the default AppDomain (the first AppDomain, which is created automatically when the CLR starts). Typically the default AppDomain executes all assemblies with full-trust. Therefore, the managed code executes with full-trust permissions now. If the libraries code calls back into the plug-in e. g. via a delegate, an event or via a virtual function call, the plug-in is now executing with full-trust permissions and not in its sandbox any more.

The following code demonstrates this unintended switch to the default AppDomain. Notice that in main, a new AppDomain called child is created and that the method Program::ExecuteInChildDomain is called in this new AppDomain. ExecuteInChildDomain calls a native function fNative and passes a pointer to a native callback method named fNativeCallback. Now fNative creates a new native thread which invokes a managed function. When created, the new thread is a native-only thread. When the thunk is invoked to call the managed function, the native-only thread is promoted to a managed thread that executes in the default domain, not in the child domain that created the thread.

// DangersOfNativThreadsInSandboxedAppDomains.cpp : main project file.
// cl /clr DangersOfNativThreadsInSandboxedAppDomains.cpp
 
#include <windows.h>
 
typedef void (__stdcall*PFN_NATIVECALLBACK)();
 
void _stdcall fNative(PFN_NATIVECALLBACK pfnCallback);
void _stdcall fNativeCallback();
DWORD WINAPI ThreadMain(LPVOID pfnCallback);
 
using namespace System;
 
ref class Program
{
internal:
  static void DumpAppDomainInfo(String^ method)
  {
    AppDomain^ current = AppDomain::CurrentDomain;
    Console::WriteLine( "Method: {0}, AppDomain: {1}",
      method,
      current->IsDefaultAppDomain() ? "(default AppDomain)"
                                    : current->FriendlyName);
  }
 
  static void Main()
  {
    Program::DumpAppDomainInfo("Program::Main");
 
    AppDomain^ child = AppDomain::CreateDomain("childDomain");
    child->DoCallBack(gcnew CrossAppDomainDelegate(&ExecuteInChildDomain));
  }
 
  static void ExecuteInChildDomain()
  {
    Program::DumpAppDomainInfo("Program::ExecuteInChildDomain");
 
    fNative(&fNativeCallback);
  }
};
 
void main()
{
  Program::Main();
}
 
void __stdcall ManagedFunctionCalledByNativeCallback()
{
  Program::DumpAppDomainInfo("::ManagedFunctionCalledByNativeCallback");
}
 
#pragma unmanaged
 
DWORD WINAPI ThreadMain(LPVOID pfnCallback);
 
void _stdcall fNative(PFN_NATIVECALLBACK pfnCallback)
{
  // this new thread starts as a native-only thread and gets promoted
  // when the new thread calls a managed function for the first time
  HANDLE hThread = ::CreateThread(NULL, 0, &ThreadMain, pfnCallback, 0, 0);
  ::WaitForSingleObject(hThread, 1000);
  ::CloseHandle(hThread);
}
 
DWORD WINAPI ThreadMain(LPVOID pfnCallback)
{
  ((PFN_NATIVECALLBACK)pfnCallback)();
 
  return 0;
}
 
void _stdcall fNativeCallback()
{
  ManagedFunctionCalledByNativeCallback();
}

Now that we know about the problem, it is time to think about a solution: How can a native library that calls managed functions from native code ensure that its managed functions end up in the right application domain? There are two fundamentally different approaches that I would like to mention here: one that is based on an explicit switch of the application domain, and another one that is based on a switch that the CLR can do implicitly.

Explicitly switching application domains can be done either via AppDomain::DoCallback or with helper functions defined in a Visual C++ header file called <msclr/appdomain.h>. This header defines several overloads of a function called call_in_appdomain, each of these functions take the identifier of the application id you want to call and a function pointer. Various overloads exist for the different calling conventions and for different numbers of arguments. To use this, you could modify your implementation of fNative as follows:

DWORD WINAPI ThreadMain(LPVOID pvCallbackInfo);
 
struct CallbackInfo
{
  PFN_NATIVECALLBACK pfnCallback;
  int appDomainId;
};
 
void _stdcall fNative(PFN_NATIVECALLBACK pfnNativeCallback)
{
  DWORD appDomainId;
  HRESULT hr = msclr::_detail::get_clr_runtime_host()
    ->GetCurrentAppDomainId(&appDomainId);
  hr; // error handling ignored for simplicity here
 
  CallbackInfo callbackInfo = { pfnNativeCallback, appDomainId };
  // this newly create thread starts as a native-only thread and gets
  // promoted when the new thread calls a managed function for the first time
  HANDLE hThread = ::CreateThread(NULL, 0, &ThreadMain, &callbackInfo , 0, 0);
  ::WaitForSingleObject(hThread, 1000);
  ::CloseHandle(hThread);
}
 
DWORD WINAPI ThreadMain(LPVOID pvCallbackInfo)
{
  CallbackInfo* pCallbackInfo = (CallbackInfo*)pvCallbackInfo;
  msclr::call_in_appdomain(pCallbackInfo->appDomainId,
    pCallbackInfo->pfnCallback);
 
  return 0;
}

This implementation passes not only the function pointer of the callback function to the thread, but a pointer to a structure that contains the callback function pointer as well as an integer value identifying the callback application domain. The thread then uses this information to invoke the callback function in the right application domain.

As I mentioned, there is a second approach which makes sure that the CLR automatically marshals the call to the right application domain. This approach is based on a method that you have probably used before. It is called Marshal::GetDelegateForFunctionPointer. This method generates a thunk that can be called by native code via a function pointer. Notice that in contrast to other thunks, this thunk is aware of the application domain where it was created. Therefore, it can automatically switch to the right application domain even when this thunk is called by a thread that has just been promoted to a managed thread. The following code shows how to use this method:

[System::Runtime::InteropServices::UnmanagedFunctionPointer(
           System::Runtime::InteropServices::CallingConvention::StdCall)]
delegate void CallbackDelegate();
 
static void ManagedCallback()
{
  Program::DumpAppDomainInfo("Program::ManagedCallback");
}
 
static void ExecuteInChildDomain()
{
  Program::DumpAppDomainInfo("Program::ExecuteInChildDomain");
 
  CallbackDelegate^ cb = gcnew CallbackDelegate(&ManagedCallback);
 
  using System::Runtime::InteropServices::Marshal;
  // this call generates a thunk that can be called by native code
  IntPtr fnPtr = Marshal::GetFunctionPointerForDelegate(cb);
  fNative((PFN_NATIVECALLBACK)fnPtr.ToPointer());
 
  // the delegate’s lifetime determines the lifetime of the generated thunk
  // => keep the delegate alife as long as native code needs the thunk
  GC::KeepAlive(cb);
}

In this post I have explained an issue that you have to understand if you want to write assemblies that use C++/CLI interop and allow partially trusted callers at the same time. In one of my next posts I will describe a safty net that you can implement in your pluggable application that ensures that no undesired bypassing of the sandbox can occur even if your plugin uses assemblies that are not aware of this issue.

10/9/2009 5:53:23 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |   |  Trackback
 Wednesday, September 30, 2009

Recently, a customer came up with a straightforward deployment problem:

I want to deploy an assembly that wraps a native DLL in the GAC. Deploying the wrapped native assembly in System32 is to error prone, deploying the wrapped assembly in WinSxS is too much of an overhead. How can I ensure that the managed wrapper can load the native assembly?

I have thought about a solution to this problem some times, but I have to admit, even though my first idea was right, so far I never found the time to prove that my guess really works. Today I took the time to take a closer look at the solution.

Here is was my initial idea:

From inspecting mscorlib with ILDASM, I knew that an assembly can have links to other files.

.assembly mscorlib
{
… attributes ignored here …
.file nometadata sortkey.nlp
.hash = (6E 30 2E 50 36 FB 60 2C 8E 50 C0 83 54 8B CC EA)
.file nometadata sorttbls.nlp
.hash = (F9 04 A8 31 CD FB 23 72 95 63 5D 14 9A A0 66 8E)
… other .file entries ignored here …
.mresource public sortkey.nlp
{
.file sortkey.nlp at 0x00000000
}
.mresource public sorttbls.nlp
{
.file sorttbls.nlp at 0x00000000
}
}

Furthermore you can see that the files are installed together with mscorlib.dll in the GAC:
dir c:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089
4.550.656 mscorlib.dll
262.148 sortkey.nlp
20.320 sorttbls.nlp
… other files ignored here …

My assumption was that if you can install native DLLs in the same way in the GAC, they can be magically found when needed. Creating assemblies with .file references is easy. Assume the following simple native DLLs:
nativedll2.cpp, compile with CL /LD /EHs nativedll2.cpp
#include
using namespace std;

void __declspec(dllexport) fNative2()
{
cout << "fNative2 called." << endl;
}


nativedll.cpp, compile with CL /LD /EHs nativedll.cpp
#include
#include

using namespace std;

#pragma comment(lib, "nativeDll2.lib")
void __declspec(dllimport) fNative2();

void __declspec(dllexport) fNative()
{
cout << "fNative called." << endl;
fNative2();
}

A wrapper assembly for nativedll.cpp can be created with this source code
testasm.dll:
using namespace System;

#pragma comment(lib, "nativedll.lib")
void __declspec(dllimport) fNative();

public ref class Test
{
public:
void DoSth()
{
Console::WriteLine("Test.DoSth called. Calling native DLL now.");
fNative();
}
};

To compile it with the two native DLLs as linked files, use the following command line:
cl /LD /clr testasm.cpp /link /keyfile:..\keyfile.snk /assemblylinkresource:nativedll.dll /assemblylinkresource:nativedll2.dll

Notice that the linker flag /assemblylinkresource adds the .file entries to the assembly manifest:
.file nometadata nativedll.dll
.hash = (EA 16 B4 08 D1 C4 D2 C0 77 CB 00 C7 93 A0 85 4A 41 BC F2 52 )
.file nometadata nativedll2.dll
.hash = (9D D1 39 D0 4C F9 08 7E 33 2F CD D2 8B C3 3E 55 BD E1 81 27 )
.mresource public nativedll.dll
{
.file nativedll.dll at 0x00000000
}
.mresource public nativedll2.dll
{
.file nativedll2.dll at 0x00000000
}

Given that the DLL is signed, it can be placed into the GAC:
Gacutil –i testasm.dll

Notice the .hash metadata for each .file entry. According to my tests, this metadata is not used for assembly validation (e. g. via SN.EXE –v testasm.dll), but during the installation of the assembly into the GAC. If you modify a linked file before adding the assembly to the GAC, GACUtil will report the following error:
Failure adding assembly to the cache: One or more modules were streamed in which did not match those specified by the manifest. The hash of one or more modules found does not match the hash recorded in the manifest.

Inspecting the assembly’s GAC_32 subdirectory after successfully installing it to the GAC shows the following files:
105.472 nativedll.dll
105.472 nativedll2.dll
24.064 testasm.dll

Obviously, the native DLLs are now installed into the GAC. The interesting question is: Are they really loaded from the GAC? The answer is yes. To prove this, use the following simple client application:

Testapp.cpp (should be in a different directory)
using namespace System;

int main()
{
Console::WriteLine("calling Test.DoSth now");
Test t;
t.DoSth();
Console::WriteLine("Press enter to stop app");
Console::ReadLine();
}

You can compile this file with the following command line:
cl /clr testapp.cpp /FU …relative path to testasm.dll …

Executing testapp.exe should work even when nativedll.dll and nativedll2.dll are not in a location that LoadLibrary normally uses; they are loaded from the GAC. If you want to verify this, compile all sources as shown, run testapp.exe and attach a debugger while the app is waiting at Console::ReadLine. In the debugger, you can see where dependent DLLs are loaded from (via the WinDBG command “lm –v” or the VS modules window).
9/30/2009 5:47:19 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |   |  Trackback
 Friday, April 06, 2007

Now that the book is written and all urgent tasks I had to defer due to the book are done, I find some time to blog about technical topics.

Recently, a customer asked me how to marshal function pointers across managed / unmanaged interop boundaries. If you know a simple API and two attributes and if you are aware of a pitfall specific to marshaling function pointers, the job can be quite easy.

To discuss this topic, consider the following simple API:

namespace NativeAPI
{
  struct CallbackData
  {
    int i;
    double d;
  };

  typedef void (*PFNCallback)(CallbackData* p);

  class SampleClass
  {
    PFNCallback _pfn;
  public:
    SampleClass(PFNCallback pfn);
    void F();
  };
}

In my book I focus on wrapping class libraries that use virtual functions for callbacks instead of function pointers, because virtual functions are the typical C++-like approach for callbacks. But obviously, a lot of C++ class libraries have their roots in C which can force you to care about arguments of function pointer types.

The managed equivalent to a native function pointer is a delegate. There are different ways to map between delegates and function pointers. The following code shows a delegate that can be mapped to the native function pointer of type PFNCallback:

namespace ManagedWrapper {   using namespace System::Runtime::InteropServices;

  [StructLayout(LayoutKind::Sequential)]
  public value struct CallbackData
  {
    int i;
    double d;
  };

  [UnmanagedFunctionPointer(CallingConvention::Cdecl)]
  public delegate void CallbackDelegate(CallbackData% p);
}

Notice that I first define a managed wrapper type for CallbackData that has the same binary layout as its native counterpart. This is done with the StructLayout attribute. The attribute is only used for documentation purposes, because sequential layout is the default setting for custom value types defined in C++/CLI (and C#). The delegate type has the UnmanagedFunctionPointerAttribute, which is not optional in this case. Using this attribute, you can specify the calling convention of the native function pointer type. In the native API's header file, the PFNCallback is defined without an explicit calling convention specification – this is not recommended, but it occurs quite often. In this case, the function pointer type has the default calling convention, which depends on compiler switches. If no compiler switch is used, the default calling convention for C-style function pointers is __cdecl. Using the compiler switches /Gz or /Gr, the default calling convention can be changed to __stdcall or __fastcall. In the concrete scenario that my customer faced, neither /Gz nor /Gr are used. To express that the CallbackDelegate should be marshaled to a __cdecl function pointer, the UnmanagedFunctionPointerAttribute is necessary. If the native function pointer is a __fastcall function, you can not provide a simple mapping, because calling __fastcall functions from managed code is not supported by the current version (2.0) of the CLR. Once you have properly defined the delegate, you can use the function Marshal::GetFunctionPointerForDelegate to receive a pointer to a native->managed thunk for the delegate. This pointer can then be passed to native code. If native code uses this pointer for function calls, the thunk performs the native->managed transition and invokes the delegate. To wrap NativeAPI::SampleClass, you can implement the following wrapper class:

namespace ManagedWrapper
{
  public ref class SampleClass
  {
    NativeAPI::SampleClass* _pWrappedObject;
    CallbackDelegate^ _callbackDelegate;
  public:
    SampleClass(CallbackDelegate^ cbd);
    void F();
    ~SampleClass();
  };
}

In the constructor of this wrapper class, you can use Marshal::GetFunctionPointerForDelegate to determine the function pointer that is passed to the constructor of NativeLib::SampleClass. The following code shows the implementation of ManagedWrapper::SampleClass.

namespace ManagedWrapper
{
  SampleClass::SampleClass(CallbackDelegate^ callbackDelegate)
  : _callbackDelegate(callbackDelegate),
  _pWrappedObject(nullptr)
  {
    IntPtr p = Marshal::GetFunctionPointerForDelegate(_callbackDelegate);
    _pWrappedObject = new NativeAPI::SampleClass((NativeAPI::PFNCallback)p.ToPointer());
    if (!_pWrappedObject)
      throw gcnew OutOfMemoryException("Could not allocate memory on C++ free store");
  }

  void SampleClass::F()
  {
    _pWrappedObject->F();
  }

  SampleClass::~SampleClass()
  {
    NativeAPI::SampleClass* pWrappedObject = _pWrappedObject;
    _pWrappedObject = nullptr;
    delete _pWrappedObject;
  }
}

Notice that in the member initialization list of the constructor, I first store a handle to the target delegate in a member variable. This is important, to control the lifetime of the thunk. At the first view, one might think that the thunk manages a tracking handle to the target delegate, which would keep the delegate alive as long as the thunk exists. However, the opposite is the case. It is not the thunk that keeps the delegate alive; the delegate keeps the thunk alive: The thunk is guaranteed to exist only as long as the delegate exists. The thunk only contains a weak reference to the target delegate which it can use for invocation if the delegate is not garbage collected. Due to this implementation, the following code would definitely be a bug:

SampleClass::SampleClass(CallbackDelegate^ callbackDelegate)
: _pWrappedObject(nullptr)
{
  IntPtr p = Marshal::GetFunctionPointerForDelegate(callbackDelegate);
  _pWrappedObject = new NativeAPI::SampleClass((NativeAPI::PFNCallback)p.ToPointer());
  if (!_pWrappedObject)
    throw gcnew OutOfMemoryException("Could not allocate memory on C++ free store");
}

Since the delegate that was used to create the thunk (and the function pointer referring to the thunk) is not stored in a member variable, it can be GCed unless other references to the delegate exist. When this delegate is GCed, the thunk can be removed as well. Notice that the thunk is not automatically removed when its target delegate is deleted, because the thunk and the delegate are created on different heaps: the delegate is instantiated on the GC heap, whereas the thunk is created on a heap that I like to call the CLR's code heap. An important difference of these heaps is that the code heap consists of memory pages that have the attribute PAGE_EXECUTE_READWRITE. The following code shows you some internals about Marshal::GetDelegateForFunctionPointer

// GFPFDTests.cpp
// build with "CL /clr GFPFDTests.cpp"

#include "windows.h"

using namespace System;
using namespace System::Diagnostics;
using namespace System::Runtime::InteropServices;

typedef void (__cdecl* PFN)();

[UnmanagedFunctionPointer(CallingConvention::Cdecl)]
public delegate void PFNDelegate();

// managed function has __cdecl calling convention
// therefore, a PFN can point to it.
void __cdecl F()
{
Console::WriteLine("::F called");
}
int main(array ^args)
{
  PFNDelegate^ d1 = gcnew PFNDelegate(F);
  IntPtr p1 = Marshal::GetFunctionPointerForDelegate(d1);
  IntPtr p2 = Marshal::GetFunctionPointerForDelegate(d1);
  // calling M::GFPFD twice for same delegate returns same thunk
  Debug::Assert(p1 == p2);

  PFNDelegate^ d2 = gcnew PFNDelegate(F);
  p2 = Marshal::GetFunctionPointerForDelegate(d2);
  // calling M::GFPFS twice for different delegates returns
  // different thunks even if delegate target is the same
  Debug::Assert(p1 != p2);
  MEMORY_BASIC_INFORMATION mbi;
  VirtualQuery(p1.ToPointer(), &mbi, sizeof(mbi));
  // thunks are allocated on a heap that consits of pages   // with the PAGE_EXECUTE_READWRITE flag
  Debug::Assert((mbi.Protect & PAGE_EXECUTE_READWRITE) ==
                 PAGE_EXECUTE_READWRITE);

  void* pD1 = *(void**)&d1; // hack to get native pointer to delegate
  VirtualQuery(pD1, &mbi, sizeof(mbi));
  // .NET objects are allocated on the GC heap which consists of
  // pages with the PAGE_READWRITE flag
  Debug::Assert((mbi.Protect & PAGE_READWRITE) ==
                PAGE_READWRITE);

  PFN pfn = (PFN)p1.ToPointer();
  pfn();
  WeakReference wr(d1);
  d1 = nullptr;
  GC::Collect(2);
  Debug::Assert(!wr.IsAlive);
  try
  {
    pfn();
    // since the target delegate does not exist any more,
    // an exception is thrown here and the line below is
    // not executed
    // if you execute this in a debugger, you will likely see
    // a "Managed Debugging Assistant" instead.
    // MDAs are CLR internal assertion-like constructs     Debug::Assert(FALSE);
  }
  catch (System::AccessViolationException^ ex)
  {
    Debug::WriteLine("Expected exception");
  }
}

In this post I have discussed how to treat marshal delegates to native function pointers. However, I have not discussed what you should do if the signature of the native function requires parameter marshalling. This will be addressed in my next post.

4/6/2007 10:13:08 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |   |  Trackback
 Saturday, January 13, 2007
A long project is comming to an end soon. After one year of writing, my book will be finished soon. 3 chapters need some minor changes. All other chapters of my book are now going to be copy edited. The book will summarize essentail parts of my research on C++/CLI in the last two years. Notice that the announcement in amazon.com is not 100% correct. The title of the book will be "Expert C++/CLI: .NET for Visual C++ programmers" and the release date will be Mid March.
1/13/2007 9:00:07 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |   |  Trackback
 Tuesday, April 11, 2006

1) Since April, 1st I am an MVP for Visual C++.

2) My first article in the MSDN Magazine has been published: http://msdn.microsoft.com/msdnmag/issues/06/05/MixAndMatch/default.aspx

4/11/2006 1:15:43 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |   |  Trackback
 Tuesday, March 21, 2006

In news://msnews.microsoft.com/microsoft.public.dotnet.languages.vc Edward Diener asked an very interesting question: Why can't I call a function having arguments of a native type like std::string from another assembly?

Assume you have some code like this one:
 
// Conversions.cpp
// compile with "CL /clr /LD conversions.cpp"
// output: mixed code assembly Conversions.dll
#include <string>
 
public ref class Conversions
{
public:
  static void S2S(System::String^ s1, std::string& s2) { /* ... */ }
};
 
This code should compile as expected, however, it would not give you the expected result!
 
The code below looks like a suitable client:
 
// ConversionsClient.cpp
// compile with "CL /clr ConversionsClient.cpp"
#using "Conversions.dll" 
#include <string>
int main()
{
  std::string s;
  Conversions::S2S("asdf", s);
}
 
If you try to compile this code, you will get a disappointing error message:
 
error C3767: 'Conversions::S2S': candidate function(s) not accessible
 
Why is a public static function S2S of a public type Convesions not accessible?
 
To use the native type std::string in managed code, the compiler generates a managed value type std::string in the assembly where std::string is used. This managed wrapper value type is private, therefore, the Conversions::S2S cannot be called from outside the assembly even though it is a public function of a public type.
 
At the first view it seems, key to the solution is to make sure the compiler generates a public type for std::string, in theory this is possible, however it would not help to solve the problem. In fact the native wrapper type has been defined as a private type for some good reasons.
 
Assume the native wrapper type for std::string was public. To call S2S, one would have to pass a tracking handle to a System::String, defined in mscolib.dll, and a value of the type std::string defined in the assembly Conversions.dll. The std::string type that we pass in ConversionsClient.cpp is a different one! It is the native wrapper type defined in ConversionsClient.cpp - not the native wapper type defined in Conversions.dll. Therefore, the parameters would not match.
 
So how can we solve this problem?
 
The origin of the problem is the fact, that type identity rules of .NET do not allways mix well with the type identity rules of native C++. To solve this problem, you can switch back to the world of native code sharing without loosing you managed code features. Simply create a mixed code static library:
 
Create a static mixed code library from the code :
 
// ConversionsLib.cpp
// compile with "CL /c /clr ConversionsLib.cpp"
// make lib with "LIB ConversionsLib.obj"
// output: mixed code static library ConversionsLib.lib
 
#include <string>
void S2S(System::String^ s1, std::string& s2) { /* ... */ }
Create a client from the code below.
 
// ConversionsLibClient.cpp
// compile with "CL /clr ConversionsLibClient.cpp"
#include <string>
 
#pragma comment (lib, "ConversionsLib.lib")
void S2S(System::String^ s1, std::string& s2);
 
int main()
{
  std::string s;
  S2S("asdff", s);
}
 
Conclusion: Beware the different type identity rules. Native types are identifies by their namespace-qualified typename, managed types are identified by their assembly- and namespace-qualifies typename. If you need native type identity rules, use native code sharing features, if you need managed type identity, use managed code sharing features.
3/21/2006 5:36:54 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |   |  Trackback
 Friday, January 20, 2006
1/20/2006 11:07:12 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |   |  Trackback