![]() |
#1 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
![]()
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:
![]() PHP Code:
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; 13th August 2008 at 11:40. |
![]() |
![]() |
![]() |
#2 |
Moderator
Join Date: Nov 2002
Location: Surrey, England
Posts: 8,434
|
Nice! Please could you make a Wiki page for this?
Stu |
![]() |
![]() |
![]() |
#3 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Done. It is now located here:
http://nsis.sourceforge.net/Calling_Managed_.Net_DLL_From_NSIS |
![]() |
![]() |
![]() |
#4 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
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.
|
![]() |
![]() |
![]() |
#5 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
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.
|
![]() |
![]() |
![]() |
#6 |
Moderator
Join Date: Nov 2002
Location: Surrey, England
Posts: 8,434
|
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 |
![]() |
![]() |
![]() |
#7 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
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 |
![]() |
![]() |
![]() |
#8 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
I have work in progress
![]() 1) It is placed in the plugins dir in NSIS. 2) It is called with (in one line): PHP Code:
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:
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. |
![]() |
![]() |
![]() |
#9 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
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:
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; 18th August 2008 at 13:22. |
![]() |
![]() |
![]() |
#10 |
Moderator
Join Date: Nov 2002
Location: Surrey, England
Posts: 8,434
|
You can use `` for outer quotes saving you having to use $\" for ".
Stu |
![]() |
![]() |
![]() |
#11 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Excellent, thanks
![]() |
![]() |
![]() |
![]() |
#12 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
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.
|
![]() |
![]() |
![]() |
#13 |
Moderator
Join Date: Nov 2002
Location: Surrey, England
Posts: 8,434
|
Sure keep as both and let the user decide which to use.
Stu |
![]() |
![]() |
![]() |
#14 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
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.
|
![]() |
![]() |
![]() |
#15 |
Moderator
Join Date: Nov 2002
Location: Surrey, England
Posts: 8,434
|
Sorry I just deleted the wrong one. Either way it would be best to upload to the Wiki instead.
Upload to for example: http://nsis.sf.net/File:CLRLoader.zip Then to embed in your page: <attach>CLRLoader.zip</attach> Stu |
![]() |
![]() |
![]() |
#16 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
New wiki page including download
No problem
![]() |
![]() |
![]() |
![]() |
#17 |
Moderator
Join Date: Nov 2002
Location: Surrey, England
Posts: 8,434
|
I noticed your float has a comma in it rather than a full stop in the sample code.
Stu |
![]() |
![]() |
![]() |
#18 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Yes, in Denmark we use a comma there. I know that you use dot in UK and US, so I will correct the sample. However, I am working on another way to pass in parameters (using variable arguments in c++), which will eliminate the problem.
|
![]() |
![]() |
![]() |
#19 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
Love the CLRLoader...did just what I needed. However, is there a trick to get the CLRLoader.dll to delete from the $PLUGINSDIR when the NSIS Setup ends. All the other DLLs and files delete okay, including the one that CLRLoader calls, just the CLRLoader.dll is left behind. Here's the code snipet:
PHP Code:
|
![]() |
![]() |
![]() |
#20 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Yes, it is a known issue. I am working on how to fix it, but for now you just have to leave it as is. The file is deleted the next time the user reboots the system. Did you use the new plug-in located at http://nsis.sourceforge.net/Call_.NET_DLL_methods_plug-in, or did you you one of the previous versions?
|
![]() |
![]() |
![]() |
#21 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
Thanks for the quick reply. I did get the one from that link. I found that I could delete CLRLoader.dll right after the NSIS installer closed and not have to wait for the reboot.
I actually loaded up the code that was in the Zip and played around a bit in VS2005 (I'm an old c programmer). Your code does a good job of cleaning up and it does successfully free the called .NET assembly as it is deleted on installer exit. It seems its the way that the System::Call is releasing the resources associated with the CLRLoader.dll...or at least CLRLoader.dll is not getting/responding to a command to "unload". Hope that helps and thanks for coming up with this one. Looking forward to the eventual fix. |
![]() |
![]() |
![]() |
#22 |
Major Dude
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
|
Just one question: Is there a specific reason why your "CLR.dll" must be called through the System-Plugin instead of making it a "normal" NSIS plugin? This way the parameters could be passed to the CLR plugin directly, instead of wrapping them into the System plugin parameters. Like this:
code: My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc My source of inspiration: http://youtu.be/lCwY4_0W1YI |
![]() |
![]() |
![]() |
#23 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
There is no reason anymore, and I will soon replace it with a native NSIS plugin.
One reason why it is not a native NSIS plugin yet is that when I startet this, the dll wouldn't be deleted from the temporary folder. But when I called it through the System::Call, the dll was deleted. However, at some point during development of the dll, even when calling through System::Call the dll is not deleted. So at this point it is not anymore a valid argument for not making it a native NSIS plugin. The more important reason for the delay of the native dll release is that I am not sure if NSIS supports unicode out of the box. I have not yet had any luck passing unicode strings to the native dll. I tried with CHAR and wchar_t in exdll.h, but no luck. Can anyone help me with this? |
![]() |
![]() |
![]() |
#24 |
Major Dude
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
|
I don't think NSIS does support Unicode yet. But there is development going into that direction. However I think the strings passed from NSIS to the System-Plugin are *not* Unicode, so passing the None-Unicode strings directly from NSIS to your CLR-Plugin should be no worse at least...
BTW: Do you remember to unload the plugin after all work is done in order to allow the plugin DLL to be delete? Like this: code: My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc My source of inspiration: http://youtu.be/lCwY4_0W1YI |
![]() |
![]() |
![]() |
#25 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Alright. I have uploaded a new version of the plugin, version 0.2. It is now a native NSIS plugin
![]() Thanks for the feedback rbchasetfb. I have tried in those directions too regarding unloading. LoRd_MuldeR, regarding making a Destroy function. I am not quite sure what to put in that function. Another thing. Is there a way to get to know how many parameters I have passed in, so I can skip passing in the int with number of parameters? Wiki page: http://nsis.sourceforge.net/Call_.NET_DLL_methods_plug-in. |
![]() |
![]() |
![]() |
#26 | ||
Major Dude
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
|
Quote:
Quote:
code: code: My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc My source of inspiration: http://youtu.be/lCwY4_0W1YI |
||
![]() |
![]() |
![]() |
#27 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
Thanks for the speedy update, claesabrandt. I've downloaded and tried your example, but when I call pop $0 after CLR:Call, $0 contains the name of my DLL. If I call pop $0 again, it contains the "Namespace.Classname" parameter. I'm probably missing something, but I did use the testscript.nsi you included, just don't seem to be able to get the return string in $0.
|
![]() |
![]() |
![]() |
#28 |
Major Dude
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
|
Returning a value could also be "optimized" with a macro:
code: code: My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc My source of inspiration: http://youtu.be/lCwY4_0W1YI |
![]() |
![]() |
![]() |
#29 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
rbchasetfb, I had the same problem, but then I found out that I still was compiling the old CLR.dll with the installer. Make sure no CLR.dll is located in the script's directory and that you do not call File CLR.dll. Just put the CLR.dll in NSIS's plugins dir. Let me know if it helps.
|
![]() |
![]() |
![]() |
#30 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Just corrected a bug where if more that one class were found in the .net assembly, only the first class could be loaded. Version 0.3 can be downloaded now and the wiki has been updated.
Wiki: http://nsis.sourceforge.net/Call_.NET_DLL_methods_plug-in |
![]() |
![]() |
![]() |
#31 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
Ok. Here's the latest.
TEST SCENARIO 1: Downloaded your new v0.3. Copied the CLR.dll to my NSIS\Plugins folder and insured that CLR.dll exists no where else. Compiled and ran the test script and it returned the dll file name again. TEST SCENARIO 2: So, took the code from the zip file, converted the vcproj file to work in VS2005 (changed version to 8.00 and removed the MinFrameworkVersion attribute from the AssemblyReference tags), opened the vcproj in VS2005 and compiled it. Insured the new CLR.dll was in the plugins folder and built and ran the test script...got the right result back from the .NET function called!!! However, the DLL was left behind in $PLUGINSDIR again. TEST SCENARIO 3: So, opened up a virtual machine of mine and installed VS C++ 2008 Express, loaded the unaltered code from the zip file, compiled it. Moved it to NSIS\Plugins, built and ran the test script and got the same result as in Test Scenario 1 above, the dll filename was returned. BTW Thanks for working on this one as much as you are. It has some great value. I will use the one I built in Scenario 2 for now, but look forward to any advancements you make. |
![]() |
![]() |
![]() |
#32 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
That is really strange. I'm already compiling it for .NET framework 2.0, just using VS2008 to do it. I cannot reproduce the error at all
![]() |
![]() |
![]() |
![]() |
#33 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
claesabrandt, I think I fixed the need for passing the number of parameters. In the Call function in your code, replace the num params and params sections with the following code and add the itreplace function above the Call function in the code.
Here's the code to replace in the Call function: PHP Code:
Here's the itreplace function: PHP Code:
PHP Code:
PHP Code:
On the issue of the strange results from my test scenario's I wrote about earlier, I've even tried the tests on other machines and get the same results. My last test runthrough was on a Windows 2003 Server with only .net 2.0 installed. For now, I'll live with the stay-behind dll file and use the VS2005 compiled one with the changes above. Thanks again. Let me know if you find any bugs in the above code. |
![]() |
![]() |
![]() |
#34 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Thanks for the reply, it is nice to have other eyes on this, I appreciate your help
![]() I had that model ealier too (splitting a parameters string), but what happens if your strings has commas in them? Actually, I still prefer having the parameters as they are now in the plugin, as it is cleaner to read instead of having them all in one string. I do have some code to split that string taking into acount that strings can have commas in them that are not two seperate parameters. But, isn't it okay as it is now? What I would like to spend some time on is 1) Getting the DLL to be deleted after install and 2) Investigating the problem you encountered with the return value. Could it be the stack that is corrupted in some way? Last edited by claesabrandt; 4th September 2008 at 18:01. |
![]() |
![]() |
![]() |
#35 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
Okay, maybe on to something here. That return value being the dll name problem seems to stem from the version of .NET installed.
My development box, and the two machines I was testing on, both had .NET 2.0 installed as well as the .NET 2.0 Service Pack 1 and .NET 3.0 Service Pack 1. When I went to a machine with no .NET 2.0 installed, then installed the base .NET 2.0 redistributable with no Services packs, the dll name showed up as the return value. Once I installed just the .NET 2.0 Service pack 1, the problem resolved and I got expected responses from the dll calls. BTW - this is all with my VS2005 compiled DLL. I still get the dll name returned with your VS2008 compiled DLL on the new .NET 2.0 w/SP1 machine. This may be coming down to some variations of the .NET 2.0 environment based on other .NET Framework installs. If I'm not mistaken, VS2008 requires .NET 3.5 installed. Maybe that has affected the .NET 2.0 assemblies in some way. |
![]() |
![]() |
![]() |
#36 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
claesabrandt, can you check the file version of you System.dll in the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 folder. Mine reads 2.0.50727.1433. It seems that the .1433 build number is the important part, when its .42, my VS2005 build doesn't work (dll calls return the dll filename).
I've tried installing .NET 3.5 SP1 on my test machine which installs .NET 2.0 System.dll with a file version of 2.0.50727.3053 and your compiled CLR.dll returns the dll filename as before. |
![]() |
![]() |
![]() |
#37 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Mine reads 2.0.50727.1433 as yours. Now that you mention this, I have a vmware machine where my plugin sometimes returns a bogus value. I will investigate too, but it sounds like the stack is getting messed up in some way.
|
![]() |
![]() |
![]() |
#38 |
Junior Member
Join Date: Sep 2008
Posts: 15
|
I was thinking it might read something differently entirely on your machine than .1433, .42 or .3053. So it would seem that that is not it. I'm at a loss on this issue as neither of us are able to both compile the same CLR.dll and subsequently recreate the problem.
At this point, I've changed my prerequisite NSIS code to install NetFx20SP1_x86.exe instead of the usual dotnetfx.exe. This insures .NET 2.0 SP1 is installed. Additionally, I'm using the VS2005 compiled version of the CLR.dll. |
![]() |
![]() |
![]() |
#39 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Hmm, I just tried it on my vwmare machine, and on that System.dll has the same version 2.0.50727.1433. Here the call actually fails and has the same result as you experienced - it returns the name of the dll file. Don't worry, we will eventually find the problem
![]() |
![]() |
![]() |
![]() |
#40 |
Member
Join Date: Aug 2008
Location: Denmark
Posts: 54
|
Wait, it seems the problem is completely different. NSIS actually outputs the error "Could not load "..blabla..CLR.dll". However, the DLL has been copied by NSIS to the correct location, so it is some load error.
|
![]() |
![]() |
![]() |
|
Thread Tools | Search this Thread |
Display Modes | |
|
|