Announcement

Collapse
No announcement yet.

Calling managed .NET DLL from NSIS - this works :-)

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Calling managed .NET DLL from NSIS - this works :-)

    Hi there,

    This is a howto on calling managed .NET DLLs from NSIS. It works and is very easy.

    Background:

    One thing you should know is, that when you call a .NET DLL from NSIS, your installer will be dependent upon that the .NET framework is installed. The NSIS documentation contains information on how to detect the presence of the framework and how to download and install it if nessecary.

    If you have a C# DLL, it requires that you write a managed .NET C++/CLR wrapper. You do not need to write a C++ Win32 wrapper, which is not so funny when you want to call your managed C# DLL. In your wrapper you will be able to call your C# code directly.

    Setting up your project:

    I assume you already have your C# DLL ready with some classes and methods in it you want to call. If you have this in a solution, you can now add a new project to the solution, select C++ CLR, then select class library. This will generate a managed C++/CLR dll and this is the DLL you will be calling from NSIS. Remember to add your C# project as a reference in your C++/CLR project.

    The C++/CLR code:

    Alright. What you have to do now is to write a C++/CLR funtion for each C# method you want to call. The C++/CLR function should look like this in your cpp or h file:

    PHP Code:
    #pragma once // placed at the top of the file

    extern "C" __declspec(dllexportcharSomeFunction()
    {
      
    // get a string from your C# method (in this case it is a static method)
      
    System::String ^str MyNamespace::MyClass::MyMethod();

      
    // convert string to char* (this is one line)
      
    charchp = (char*)(void*)System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(str);

      
    // return the char* to NSIS
      
    return chp;

    That's it To call the function from NSIS:
    PHP Code:
    SetOutPath $PLUGINSDIR
    System
    ::Call 'DLLName::SomeFunction(v) t .r0' 
    This will return the string in register $0. You can have parameters too. See the NSIS documentation for more information on System::Call.

    Make sure to include both the C++/CLR dll and the C# dll in your installer and place them in for instance the $PLUGINSDIR. When calling the C++/CLR dll with System::Call, be sure to make a call to SetOutPath $PLUGINSDIR first, so NSIS can find the DLLs.

    Hope you can use this.

    Regards Claes Brandt
    Last edited by claesabrandt; 13 August 2008, 10:40.

  • #2
    Nice! Please could you make a Wiki page for this?

    Stu

    Comment


    • #3
      Done. It is now located here:
      http://nsis.sourceforge.net/Calling_Managed_.Net_DLL_From_NSIS

      Comment


      • #4
        I justed tested my installer on a system with no .NET framework at all, and it seems I have to make sure that both the .NET framework and Visual C++ redistributable are installed. I'm gonna write this into the wiki.

        Comment


        • #5
          Forget that. I found it to be something else. Basically it is because the cannot find the C# dll when calling it via the C++ dll. I will work on it and get back when I have the solution.

          Comment


          • #6
            Try setting the current directory to $PLUGINSDIR with SetOutPath. If that doesn't work you could try updating the PATH variable to point to $PLUGINSDIR. Maybe it would be easier just to extract the C# dll to $SYSDIR.

            Stu

            Comment


            • #7
              Unfortunately none of those three method works, I tried them. It would have been nice. I will explain at the bottom of this post why and some details, but here is a workaround, that will work:

              1) Create your script that calls your C++/CLR DLL which then in turn calls your C# DLL. This is what I described in the first post. But do not copy your C# DLL file in this script. Resulting exe could be for instance setup1.exe

              2) Create another script that includes your C# DLL and setup1.exe. In .onGUIInit it copies them to $TEMP, then executes setup1.exe. When setup1.exe runs and calls the C++/CLR DLLs, which in turn calls the C# DLLs, the .NET framework can find the C# DLLs. When setup1.exe finishes, you delete both the C# DLL and setup1.exe then call abort. You will never see the first installer.

              I guess I should include this in the Wiki page?

              ---

              Here is why it will not work trying to set the current dir, setting the path or extract the C# dll to $SYSDIR: When the NSIS installer invokes those C++/CLR DLL functions, .NET assumes it should look for the C# DLLs in the same directory as the installer exe.

              Now, we could extract the C# DLL in the same directory as where the installer exe runs, but if it runs from a readonly directory, it extract will fail.

              There are ways to make .NET probe for DLLs in other directories than where the executing exe is located. This MSDN article explains it: http://msdn.microsoft.com/en-us/library/15hyw9x3(VS.71).aspx. The problem in those methods is that we still cannot copy a config file to a readonly directory.

              We could also use reflection to load the C# assembly from any location we want, but then we loose compile-time error catching, and loading the assembly like this is ugly and I had some trouble getting NSIS to release the assembly file once it was loaded - it simply couldn't delete the DLL when exiting the installer.

              Claes

              Comment


              • #8
                I have work in progress This time it is a real NSIS plug-in, that can load a .NET DLL. I guess it will be called CLR.dll or CLRLoader.dll or something. What I have working so far:

                1) It is placed in the plugins dir in NSIS.
                2) It is called with (in one line):
                PHP Code:
                CLRLoader::Invoke "SomeAssembly.dll" "SomeNamespace.SomeClass" "SomeMethod" "param1,param2,param3"
                pop $
                3) It can handle string parameters and string return value at the moment.

                Todo: Dynamic number of parameters.
                Todo: Object type return value.
                Todo: Fix a bug where that causes the DLL to remain in the tmp directory. NSIS can't delete it.

                I also have it in a version, where you call it with System::Call, and this version does not remain in the tmp directory. This version has to be included manually in the installer and placed in for instance $PLUGINSDIR.

                It is called with (one line):
                PHP Code:
                System::Call 'CLRLoader::Invoke(t "SomeAssembly.dll", t "SomeNamespace.SomeClass", t "SomeMethod", t "param1,param2,param3") t .r0' 
                Both versions are attached.

                If you want to try it, remember to rename the desired DLL to CLRLoader.dll. If it is the "native" NSIS version, put it in the pluginsdir.

                Comment


                • #9
                  My new NSIS plug-in is now working. It can call methods in managed .NET DLLs. It supports dynamic number of parameters of types void, string (unicode), int, float, bool. Return value can be these types too. Strings and floats must be enclosed in "". The .NET classes called must have a default constructor.

                  You can find the plug-in attached to this post. This is version 0.1 of the plugin. I will write a wiki plug-in page for it shortly. It is provided here as-is for testing.

                  Let me know what you think of it, if you have any suggestions, questions or have trouble using it. Also, if you think more types should be supported for input and return values, let me know.

                  The following is a sample script that copies a .NET dll to pluginsdir along with this plugin. The .NET method called takes the following parameters: string, int, float and boolean. It then returns a string.
                  PHP Code:
                  SetOutPath $PLUGINSDIR
                  File 
                  "SomeAssembly.dll" some test .NET assembly
                  File 
                  "CLR.dll" this plugin
                  System
                  ::Call `CLR::Call(w 'SomeAssembly.dll::SomeNamespace::SomeClass::SomeMethod("some string value",12,"15,8",false)') w .r0
                  This replaces the above method I described and my former wiki, as this plug-in fixes some issues with the method described previously.

                  Regards Claes

                  P.S.: Minor issue: You have to call it via System::Call. While this works perfectly, I would like it to be a true NSIS plugin. The reason for why it is called via System::Call is that if I make it a real NSIS plugin, the DLL remains in the $PLUGINS dir when the installer closes. If I get to fix that issue, I can make it a real NSIS plugin, that does not need System::Call.
                  Last edited by claesabrandt; 18 August 2008, 12:22.

                  Comment


                  • #10
                    You can use `` for outer quotes saving you having to use $\" for ".

                    Stu

                    Comment


                    • #11
                      Excellent, thanks . I corrected the post with your suggestion.

                      Comment


                      • #12
                        Stu, I have a question. I really would like this to be a native NSIS dll, but as stated, the dll will not be deleted by NSIS, when the installer closes. I found out that it is automatically marked by NSIS to be deleted along with the directory, on next reboot, and so the file is gone. My question is, would that be ok? Or should I for now stick to the version where I call it with System::Call? The call would be much cleaner, if I made it a native NSIS plugin, and I probably would devide the parameters up into different parts. And the user would not have to include it manually, just put it in the plugins dir.

                        Comment


                        • #13
                          Sure keep as both and let the user decide which to use.

                          Stu

                          Comment


                          • #14
                            Stu, would it be possible to remove the zip file from post #8, as it seems some have downloadet that instead of the zip in post #9. The zip in post #8 should not be used.

                            Comment


                            • #15
                              Sorry I just deleted the wrong one. Either way it would be best to upload to the Wiki instead.

                              Upload to for example:


                              Then to embed in your page:
                              <attach>CLRLoader.zip</attach>

                              Stu

                              Comment

                              Working...
                              X
                              😀
                              🥰
                              🤢
                              😎
                              😡
                              👍
                              👎