Friday, September 16, 2011

P/Invoke: Bundling native DLL:s in Silverlight 5 RC applications

One of the many interesting new features in Silverlight 5 RC is the ability for elevated trust applications (in-browser as well as out-of-browser) to access native DLL:s via P/Invoke.

Some interesting application examples are given here and here. These examples show how easy it is to access system DLL:s such as user32.dll, kernel32.dll and psapi.dll. However, the issue appears to be more complex when it comes to native DLL:s that are not automatically available on the system.

Ideally (I think), you would like to bundle the native third party DLL:s as Content in the XAP file, and the XAP file would be artificially included in the P/Invoke search path. Unfortunately, this is not the way it works today. When I bundle my third-party native DLL as Content in my Silverlight 5 RC application, and try to access one of its methods via the following declaration,

[DllImport("NativeDll")]
public static extern int add(int a, int b);


I get a discouraging DllNotFoundException. It does not help if I add the .dll extension to the file name, and I have also not been able to find the DLL by applying any of the pack URI approaches to the DLL file name.

Several people, including myself, have brought up this issue in various forums, but so far I have not seen any published solution to the problem.

Well, as we say in Sweden, "själv är bäste dräng" (which is similar to "if you want something done, do it yourself"), so here is my attempt to solve the problem. Granted, it might not be the most Elegant Solution, but at least it is A Solution.

It is important to note that this solution is applicable primarily to Silverlight 5 RC. In the best of worlds, Microsoft will fix the native DLL path issue in the RTM release of Silverlight 5, and then this solution would be superfluous. We'll see what happens in the end.

Note also that since P/Invoke is a feature requiring elevated trust, this solution is only relevant to applications running in elevated trust (in- or out-of-browser).


In short, the solution is to bundle the native third party DLL:s as resource files in the Silverlight 5 RC application, and when the application is started copy these DLL:s to a local directory and add the directory to the system path during the execution of the Silverlight application.

In more details, here is how it can be done:

Add each native third party DLL as an existing item to the root folder or, even more preferably, to an assets sub-folder in the Silverlight 5 RC application project.


In the File Properties window, set Build Action to Resource and Copy to Output Directory to Copy if newer.


The DLL copying and PATH environment variable setting of course requires some additional code. Fortunately :-) I have created a static utility class for this purpose. The class is named NativeDllHelper and can be found it is entirety here. Download and add this file to your Silverlight 5 RC application project.

Next, go to the App.xaml.cs or equivalent file. Add the following using statement.

using Cureos.Utility;

In the Application_Startup (or equivalent) event handler, add the following method call to the first line of the event handler.

NativeDllHelper.SetupNativeDllFolder("relpath/dllname.dll", ...);

where relpath is the path relative to the project root folder where the native DLL:s are located, and dllname is the file name of the DLL. Specify all DLL:s to be copied in one argument list; the SetupNativeDllFolder method should only be called once at start-up to avoid adding duplicate entries of the local DLL directory to the system path.

The native DLL files are copied to a My Documents sub-folder Silverlight\Native. If you prefer a different folder for the DLL:s, edit the static constructor of the NativeDllHelper class.

Here is an example call of SetupNativeDllFolder, copying one file NativeDll.dll located in the Assets sub-folder of the Silverlight 5 RC application project.

NativeDllHelper.SetupNativeDllFolder("Assets/NativeDll.dll");

Having performed these steps, no user intervention is required when running the application, the native methods in the third party DLL:s can simply be invoked using only the file name of the DLL:

[DllImport("NativeDll")]
public static extern int add(int a, int b);


A complete example SL 5 RC application utilizing this functionality can be downloaded from here. Note that it contains a C(++) project for creating the example native DLL.
If you try out the application and get build errors due to copy prevention the first time, building a second time should do the trick. You might also need to manually set the start page of the Web project to the .aspx file.


The application itself is very simple; there is this button

with the following click event handler associated to it:

private void Button_Click(object sender, RoutedEventArgs e)
{
  button.Content = add(5, 7);
}

The add method is a native method, as declared above. Clicking the button leaves us with the following satisfying button update:

And that is basically all that there is to it. The utility method for copying DLL:s from the application resources can most certainly be further improved, for example by more efficiently handling DLL:s already existing locally. With the current implementation, any existing DLL:s are automatically overwritten. All improvement suggestions as well as reports of successes or failures are highly appreciated.

12 comments:

  1. Thanks for the sharing.

    Note to test the NativeDll solution, the xap file need to be signed.

    ReplyDelete
  2. Hi Chris,

    and many thanks for pointing this out.

    You are absolutely right (and I could have pointed this out in the post), when running trusted applications (such as this) in-browser the XAP file has to be signed. A trusted certificate also needs to be installed locally, and a specific registry key needs to be set. This is described in more detail here: http://www.pitorque.de/MisterGoodcat/post/Silverlight-5-Tidbits-Trusted-applications.aspx

    When running out-of-browser, these steps are not necessary. And it should also work to run the application in-browser as a web application started from Visual Studio.

    Anyway, it is good to be aware of these in-browser limitations.

    Regards,
    Anders

    ReplyDelete
  3. Strange,but it's not fixed in RTM(5.0.61118.0).
    Still no way to bundle-not as "ExtensionPart" nor "AssemblyPart".

    Thanks.

    ReplyDelete
  4. Andrey, thanks for your comment, I noticed this too when testing Silverlight 5 on my csipopt project. It's a pity that this issue has not been dealt with in the RTM release, but maybe Microsoft did not consider this kind of P/Invoke usage suitable with Silverlight? Or this is simply not considered important enough?

    ReplyDelete
  5. Thanks for the sharing. It was vary helpful!

    ReplyDelete
    Replies
    1. Dear Marko, happy to hear that my code has been useful to you. Thanks for letting me know. Kind regards, Anders

      Delete
  6. Hi

    Thanks for the info !

    Could you tell me why the xap needs to be signed ?

    Also, could you please provide some code to check if the destination file already exists before copying ?

    Thanks & Regards
    Etienne

    ReplyDelete
  7. Well, checking if the file exists is easy, I just got confisued with the streams...

    if (File.Exists(_nativeDllDirectory+Path.DirectorySeparatorChar+dllPath))

    ReplyDelete
  8. Dear Etienne,

    thank you for your comments. Yes, it should suffice to use regular File operations to verify whether a DLL is already available. I have not bothered to implement support for version control etc., but I suppose that could be a useful extension too.

    The signing is necessary when deploying the elevated-trust in-browser Silverlight application. All in all, there are many deployment restrictions with this scenario, so as far as I understand it is only practically possible to deploy such an application over for example a company intranet. A more detailed explanation is provided by "Mr. Goodcat": http://www.pitorque.de/MisterGoodcat/post/Silverlight-5-Tidbits-Trusted-applications.aspx

    Regards,
    Anders

    ReplyDelete
  9. This solution was exactly what I needed Anders. This blogpost plus your contribution on Github [NativeDllHelper] has made my day. The tilting point for me was the functinality of the helper class that copies dlls from the asset folder to the client system! That was ingenious. Thank you so much for sharing. You deserve a vacation. My country will be a good place to spend one :D

    Charles [Nigeria]

    ReplyDelete
  10. You are welcome, Charles! Glad this work was of help to you.
    Best regards, Anders

    ReplyDelete
  11. What a piece of useful code! Cheers mate.

    ReplyDelete