NSClient++ Help (#1) - Developing own DLL (#126) - Message List

Developing own DLL

I am developing a plugin for Windows systems. It is called via nrpe. It is an executable Windows binary. I want to make it run in NSClient++ as dynamic Link Library. What is the easiest way to do that? How do I have to write my classes, or is there a way to do it without changing my Code, just to implement something like an Interface. In the end I want to call my Plugin with an "inject"-command. Can you tell my what I have to do?

Thanks, reichi

  • Message #329

    What language is it you are using?

    But in short there is a set of function you expose from your DLL and thats about it, if you look at the source modules/* you can quite easily see exactly how it is done, there is also a bunch of .h files used (for instance nscapi.h) that define the interface. If you develop in C++ there is a bunch of helper code that you can use so you wont have to manage the interface at all.

    Notice though that I have some minor changes for 0.4.0 (minor optional additions) and for 0.5.0 there will be some API changes (notably string handling).

    // Michael Medin

    • Message #330

      I am using C# for development.

      • Message #331

        Then you are "on your own" sort of... but should not be so hard... You need to export (one or more of these) from your DLL (for syntax check the .h file) And I am not sure exactly here this is from the top of my head, can write up better info later on if you want, but this might give you some idea on what to do to get started.

        • NSModuleHelperInit

        Called to say "hello" to you, the function you get here can be used to "extract" call back functions that you can use to do stuff with the "core".

        • NSLoadModule

        Used to "boot" your module.

        • NSUnloadModule

        "un-boot" you mosule (ie. terminate and free-up memory/resources)

        • NSGetModuleName

        Return a nice name for your module.

        • NSGetModuleDescription

        Return a nice description for your module.

        • NSGetModuleVersion

        Return version (never used as of now, but might some day be used :)

        • NSHasCommandHandler

        Return true if you handle commands

        • NSHandleCommand

        Handle a command (ie. run a "check")

        • NSHasMessageHandler

        Return true if you want to handle messages (log data)

        • NSHandleMessage

        Handle a log message (not needed unless you responded true above)

        • NSCommandLineExec

        Handle command line execution (ie nsclient++ -noboot <module> ...)

        Thats about all you need to do...

        // MickeM

        • Message #339

          Can you tell me how I have to explain the nsclient++ to use my module with command for instance:

          command[my_own_mod]=inject myCommand -H ...
          

          Where do I have to declare myCommand, thats something, I didn't yet understand?!

          Thanks, reichi

          • Message #340

            in short: you dont...

            If you want to use an external program (ie not an internal plugin) then you just point the way to your executabel/script. If you want (as I understood it) want to develop an internal NSCLient++ module (DLL). Then you build the DLL and add it to the modules/ directory and also under the [modules] section.

            After that the "module" will be called (in the same order as the modules section) whenever a command is executed, and the first module that responds with "ok" will end "chain" and return the result to the caller.

            Before that your DLL need to respond "true" to NSHasCommandHandler to make your module into a command handler. (there is also a way t register commands with the client via one of the callbacks, but that is not really used as of now, but will be in 0.5.0)

            It works like this:

            on_boot:
            ...
            foreach module in <[modules]>
              module.NSModuleHelperInit 
              module.NSLoadModule 
              if module.NSHasCommandHandler 
                command_handlers.add(module)
              ...
            
            on_incoming_request:
              ...
              foreach module in command_handlers:
                ret := module.NSHandleCommand
                if is_valid_return_code(ret):
                  return ret;
              ...
              return "unknown command";
            
            ...
            on_exit:
              foreach module in <[modules]>
                module.NSUnloadModule
              ...
            

            in sort of python:ish meta programming :)

            // Michael Medin

            • Message #691

              Hi

              I am working on a CSharp version of a dll. I have some question about type conversion. I NSClient will call the dll, it will expect exactly the same parameter as you defined in the NSC_WRAPPERS_MAIN(). could you tell me how I can declare this in c# ? : extern "C" int NSModuleHelperInit(NSCModuleHelper::lpNSAPILoader f); Also do you have any hint on how to work with TCHAR** in C# ? I wanted to stick to C++ but turned out that someone already develop a part of what I want to do in C#.

              thanks Franck

              • Message #692

                Hi I found how to pass a function as parameter in C# here is what I did. I am not sure it is the right way to do in term of write a c# dll for NSClient. But I will keep you guys posted. If anyone have a hint, tips or snippet. I'd be glad to read it.

                franck

                • Message #693

                  sorry here is what I did. sorry for the spam. it is quite late in France right now. getting tired.

                  ..... namespace NSCSharpHelper {

                  public delegate void* NSAPICSharpLoader([MarshalAs?(UnmanagedType?.LPTStr)] String buf);

                  } ...... int NSModuleHelperInit(NSCSharpHelper.NSAPICSharpLoader f);

                  franck

                  • Message #694

                    I don't really know how to do it in C# but using/writing DLLs from C# ought to be fairly simple and well documented from Microsoft (and the like) Id start by looking at CodeProject?. And especially read up on writing DLL:s and look at "using" the "W32 API" (as it is the same strings and what not).

                    NSClient++ uses normal DLL:s and the strings are normal Unicode "windows" string. Everything was designed to be "compatible" so it should be possible to write extensions in virtually every language (for windows). An important aspect that may not be obvious is that you need to marshal threading as well as the memory.

                    I will at some point look into this but I do not have the time at the moment so if you get something up and running feel free to submit it and I shall be happy to create a sample .net plugin.

                    // Michael Medin

                    • Message #696

                      Hi I am checking out your code and seems like there is no need to handle thread. If I take the example of CheckDisk, it looks like the NSclient++ is handling the thread. Am I right?

                      franck

                      • Message #697

                        that depends...

                        if you don't use any variables between calls, most checks dont do that and then it is not a problem. For examples of where I do it you can check the CheckCPU and CheckMemory? and such.

                        But there is no handling by the core to protect plugins from threadding issues. Requests are executed as they arrive...

                        // Michael Medin

                        • Message #698

                          ok thanks for following up I may have more questions later on. I am trying to build a small NSCSharpUtil that will help me create easily a dll module in C#. So I guess there are more questions comming soon. I will be glad to share my results with you. thanks for your help. franck

                          • Message #710

                            Hi I have a question about the function NSModuleHelperInit. It is basically a function which takes another one as parameter. My understanding is that NSClient will call NSModuleHelperInit from my dll and pass it a pointer to a function namely NSAPILoader. This is normally just a pointer soI tried to define my function in the c# as follows but it is not working whebn it comes to load the dll in NSClient++. Looks like my definition of NSModuleHelperInit is wrong. here is my c# declaration

                            public static int NSModuleHelperInit(IntPtr? pointer);

                            Could you help me on this?

                            • Message #713

                              the forums is not really suitable for deep threading I see :)

                              Anyways.

                              NSCLient loads your DLL and calls NSModuleHelperInit. Then it calls NSModuleLoad (or some such).

                              The parameter for NSModuleHelperInit is not an int pointer it is a function pointer, the function in question can be used to extract an assortment of other function pointers from NSClient to access the core from your plugin.

                              I am at a conference at the moment so I can dig a little deeper next week if you want...

                              // Michael Medin

                              • Message #715

                                sure Any help will be more than welcome. about the threading you can use the flat view option :)

                                regards

                                • Message #716

                                  The defenitions (C++) for your functions BTW:

                                  97 	        typedef INT (*lpModuleHelperInit)(NSCModuleHelper::lpNSAPILoader f);
                                  98 	        typedef INT (*lpLoadModule)();
                                  99 	        typedef INT (*lpGetName)(TCHAR*,unsigned int);
                                  100 	        typedef INT (*lpGetDescription)(TCHAR*,unsigned int);
                                  101 	        typedef INT (*lpGetVersion)(int*,int*,int*);
                                  102 	        typedef INT (*lpHasCommandHandler)();
                                  103 	        typedef INT (*lpHasMessageHandler)();
                                  104 	        typedef NSCAPI::nagiosReturn (*lpHandleCommand)(const TCHAR*,const unsigned int, TCHAR**,TCHAR*,unsigned int,TCHAR *,unsigned int);
                                  105 	        typedef INT (*lpCommandLineExec)(const TCHAR*,const unsigned int,TCHAR**);
                                  106 	        typedef INT (*lpHandleMessage)(int,const TCHAR*,const int,const TCHAR*);
                                  107 	        typedef INT (*lpUnLoadModule)();
                                  

                                  and all the call backs:

                                  102 	        // Types for the Callbacks into the main program
                                  103 	        typedef NSCAPI::errorReturn (*lpNSAPIGetBasePath)(TCHAR*,unsigned int);
                                  104 	        typedef NSCAPI::errorReturn (*lpNSAPIGetApplicationName)(TCHAR*,unsigned int);
                                  105 	        typedef NSCAPI::errorReturn (*lpNSAPIGetApplicationVersionStr)(TCHAR*,unsigned int);
                                  106 	        typedef NSCAPI::errorReturn (*lpNSAPIGetSettingsString)(const TCHAR*,const TCHAR*,const TCHAR*,TCHAR*,unsigned int);
                                  107 	        typedef NSCAPI::errorReturn (*lpNSAPIGetSettingsInt)(const TCHAR*, const TCHAR*, int);
                                  108 	        typedef NSCAPI::errorReturn (*lpNSAPIGetSettingsSection)(const TCHAR*, arrayBuffer::arrayBuffer*, unsigned int *);
                                  109 	        typedef NSCAPI::errorReturn (*lpNSAPIReleaseSettingsSectionBuffer)(arrayBuffer::arrayBuffer*, unsigned int *);
                                  110 	        typedef void (*lpNSAPIMessage)(int, const TCHAR*, const int, const TCHAR*);
                                  111 	        typedef NSCAPI::errorReturn (*lpNSAPIStopServer)(void);
                                  112 	        typedef NSCAPI::errorReturn (*lpNSAPIExit)(void);
                                  113 	        typedef NSCAPI::nagiosReturn (*lpNSAPIInject)(const TCHAR*, const unsigned int, TCHAR **, TCHAR *, unsigned int, TCHAR *, unsigned int);
                                  114 	        typedef void* (*lpNSAPILoader)(TCHAR*);
                                  115 	        typedef NSCAPI::boolReturn (*lpNSAPICheckLogMessages)(int);
                                  116 	        typedef NSCAPI::errorReturn (*lpNSAPIEncrypt)(unsigned int, const TCHAR*, unsigned int, TCHAR*, unsigned int *);
                                  117 	        typedef NSCAPI::errorReturn (*lpNSAPIDecrypt)(unsigned int, const TCHAR*, unsigned int, TCHAR*, unsigned int *);
                                  118 	        typedef NSCAPI::errorReturn (*lpNSAPISetSettingsString)(const TCHAR*, const TCHAR*, const TCHAR*);
                                  119 	        typedef NSCAPI::errorReturn (*lpNSAPISetSettingsInt)(const TCHAR*, const TCHAR*, int);
                                  120 	        typedef NSCAPI::errorReturn (*lpNSAPIWriteSettings)(int);
                                  121 	        typedef NSCAPI::errorReturn (*lpNSAPIReadSettings)(int);
                                  122 	        typedef NSCAPI::errorReturn (*lpNSAPIRehash)(int);
                                  123 	        typedef NSCAPI::errorReturn (*lpNSAPIDescribeCommand)(const TCHAR*,TCHAR*,unsigned int);
                                  124 	        typedef NSCAPI::errorReturn (*lpNSAPIGetAllCommandNames)(arrayBuffer::arrayBuffer*, unsigned int *);
                                  125 	        typedef NSCAPI::errorReturn (*lpNSAPIReleaseAllCommandNamessBuffer)(arrayBuffer::arrayBuffer*, unsigned int *);
                                  126 	        typedef NSCAPI::errorReturn (*lpNSAPIRegisterCommand)(const TCHAR*,const TCHAR*);
                                  127 	
                                  

                                  // MickeM

                                  • Message #717

                                    Hi Micheal. I was aware about this. my C# class is implementing or handling these functions. the only problem is handling a function as parameter inside my c#. I am still digging.

                                    Franck

                                    • Message #732

                                      Hi Micheal. I solved this issue by twicking a bit your NSplugin. basically passing the function as variable end up playing with the ret and the sfp of the init function. The solution I found is to create a proxy dll in c++ that is able to pass out some static variable to my C# dll.

                                      I am still dealing with other bug. once everything is fixed. I will share my solution with you.

                                      regards.

                                      • Message #733

                                        I did some quick testing and you (apparently) have to write a wrapper in managed C++, from which you can use the C# code...

                                        The wrapper will be about 20-30 lines so it should not be too bad I think... but I have yet to figure out how to put them both in the same "DLL"...

                                        // MickeM

                                      • Message #734

                                        there is a very basic but functional "C# plugin" with a managed C++ wrapper in the SVN now.

                                        The "main" problem is that I don't exactly know how to get rid of having 2 DLL:s (if at all possible) Also note that the "C# DLL" has to be in the same directory as the *exe* (not the modules directory).

                                        // MickeM

                                        • Message #735

                                          Hi thanks for commiting this example. looks like if I want to create two modules, I have to create two wrappers. let me know if my solution is better. Basically the idea was to create a dll(proxy) that we loaded first. in the NSPlugin.cpp I added this. Obviously it is nasty code for now. but the Idea is to ask each dll wich type they are and if it is a proxy. I just cached the function for every dll written in c#.

                                          of course i am marshaling my c# in order to deal with unmanaged types.

                                          /*at this point we have to handle C# library we got to detect which type is it to handle CSharp in a different way */

                                          /*a bit nasty for now just to make sure it is working */

                                          typedef int (*lpwhichTypeAreYou) (); lpwhichTypeAreYou fwhichTypeAreYou = (lpwhichTypeAreYou)GetProcAddress?(hModule_, "NSwhichTypeAreYou"); int myType = fwhichTypeAreYou(); //proxy if(myType ==0){

                                          try { fModuleHelperInit = (lpModuleHelperInit)GetProcAddress?(hModule_, "NSModuleHelperInitProxy"); fModuleHelperInit(NSAPILoader); } catch (...) {

                                          throw NSPluginException(file_, _T("Unhandled exception in getDescription."));

                                          }

                                          } //CSharp else if(myType ==1){

                                          try {

                                          typedef void (*lpCSharpModuleHelperInit) ();

                                          lpCSharpModuleHelperInit fCSharpModuleHelperInit = (lpCSharpModuleHelperInit)GetProcAddress?(hModule_, "NSCSharpModuleHelperInit"); fCSharpModuleHelperInit(); } catch (...) {

                                          throw NSPluginException(file_, _T("Unhandled exception in getDescription."));

                                          }

                                          } //c++ else {

                                          fModuleHelperInit = (lpModuleHelperInit)GetProcAddress?(hModule_, "NSModuleHelperInit"); if (!fModuleHelperInit)

                                          throw NSPluginException(file_, _T("Could not load NSModuleHelperInit"));

                                          try {

                                          //NSCModuleHelper::lpNSAPILoader NSAPILoaderAddr=&NSAPILoader; fModuleHelperInit(NSAPILoader);

                                          } catch (...) {

                                          throw NSPluginException(file_, _T("Unhandled exception in getDescription."));

                                          }

                                          }

                                          • Message #736

                                            Not sure I follow what you mean.

                                            As far as I can tell it is impossible to create "proper" DLL:s in C# (apparently you need Managed C++ for that). So if you have a solution to that it would be nice. But I dont really see how the above would solve that? But with the above code you don't get any "callbacks" which would be sort of a bad thing (Since they are quite handy). But I think it is fairly simple to get marshal the function pointer to C#.

                                            // MickeM

                                            • Message #737

                                              yeah I didn't want to copy and paste everything. basically in the proxy dll I save the callback function as static. So in my c# i load this proxy dll and I can get all the call back pointer since they are static.

                                              This suppose I have implemented functions in the proxy which allow me to get pointer to those function. I have to admit that This is may be too complicated. but with this I have one dll which save pointers for every c#. hope this is clear.

                                              • Message #738

                                                it would (in that case) make more sense to write a managed "C# plugin" that loads C# plugins, but since it (AFAICT) requires compile time knowledge of the implementation I don't really see the benefit, but I suppose it would be a way to go.

                                                Anyways, I am not a .net developer so I honestly don't really know. The "reason" I did things the way I did them is that all "functions" are handled by the same wrapper layer I have for the C++ plugins so it was less code to write :)

                                                // Michael Medin

                                                • Message #739

                                                  that's right. thanks for your help. I am going to use your example for sure. I have some idea of how to merge our views. but how can I share code with you?

                                                  • Message #740

                                                    well, either you can attach it here or you can email or some such, I might possibly figure out how to setup commit access for the SVN if you want to write a "proper plugin" or some such...

                                                    // MickeM

                                                    • Message #741

                                                      ok I will start by attaching files first. also I got a problem on your last commit. in the file trayIcon.cpp the function ChangeWindowMessageFilter?. got this. error C2373: 'ChangeWindowMessageFilter?' : redefinition; different type modifiers looks like you are redefining a function from winuser.h that you may not??

                                                      any hint?

                                                      • Message #742

                                                        uhmm, you are most likely using a newer version of windows API then I... you can probably add a #define around it to get away from the problem or perhaps better rename the function...

                                                        I have (sometimes) added an underscore to function names to get away from the problem, but might have forgotten in this instance...

                                                        BTW; what branch are you on? (current version is stable, but I am in the process of backporting all those changes to the trunc so in a week or so the trunc will again be the "development"...

                                                        // MickeM

                                                        • Message #743

                                                          I used the branch stable https://druss.medin.name/trac/nscp/browser/branches/stable. Since it is where you commited the code. I renamed the function by by adding and underscore like this : ChangeWindowMessageFilter?_ and it is compiling find. thanks

                                                          • Message #744

                                                            thats what I usually do... feel free to submit a patch and I shall apply it tonight...

                                                            // MickeM

                                                            • Message #746

                                                              ok I mailed it. one more issue is the call of createEnvironmentBlock. looks like you may declare the definition of the function? it is declare in the userenv.h

                                                              1>NSClient++.obj : error LNK2019: unresolved external symbol impCreateEnvironmentBlock@12 referenced in function "public: static bool cdecl tray_starter::startTrayHelper(unsigned long,class std::basic_string<wchar_t,struct std::char_traits<wchar_t>,class std::allocator<wchar_t> >,class std::basic_string<wchar_t,struct std::char_traits<wchar_t>,class std::allocator<wchar_t> >,bool)" (?startTrayHelper@tray_starter@@SA_NKV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@0_N@Z)

                                                              • Message #747

                                                                sorry for spamming now i feel stupid :) did not add the userenv.lib in the project. May be something to add in the project properties in the trunk!!

                                                                franck

                                                                • Message #753

                                                                  Hi. I got something working. I added a basic "c# federator" which is basically collecting all Managed dll into one. I reused your example for that. basically people could just develop the c# code using the interface I will give them. And the c# federator will append all functions as if it was its own. Did you got the mail i sent you? if yes I can send you my project.

                                                                  Note I tried to merge both c++ and c# dll without success. Finally I guess it is impossible in our case if we still want to use the unmanaged c++ wrapper.

                                                                  If I have time I will try to continue debugging what I started with i.e. the c# marshalled dll to plug directly.

                                                                  franck

                                                                  • Message #754

                                                                    Dont know what a C# fedarator is, but the "overall plan" I have for this is to:

                                                                    1. define a C# interface
                                                                    2. Write a Managed C++ module that "loads other modules" (based on the interface)
                                                                    3. Have under [modules] a ".net loader"
                                                                    4. add a new modules section with pure C# modules that are loaded "via" the .net loader module.

                                                                    And I got the email a while back so feel free to send it over.

                                                                    // MickeM

                                                                    • Message #755

                                                                      THat is pretty much what I did. The "federator" is a name i choose for the ".net loader". may be the word federate make more sence in french :).

                                                                      I am about to send you the mail. Let me know what you think.

                                                                      • Message #762

                                                                        looks like the mail did not work. I added modules projects in an svn. here is the link: https://opensvn.csie.org/viewcvs.cgi/NSClient%2B%2B/modules/modules/?root=middleware

                                                                        franck

                                                                        • Message #763

                                                                          Hi I just correct a bug inside your SampleManagedPlugin?.h:

                                                                          line 53 was: List<String> args; I changed it to: List<String> args = gcnew List<String>();

                                                                          now command arguments are working regards

                                                                          • Message #879

                                                                            Hey guys. This thread seams to cope with pretty much the topic I am up to.

                                                                            As I understand the whole topic: 1. you need the managed C++ DLL 2. write the actual C# Plugin.

                                                                            I read about the C# Sharp Interface/francks "federator". Any chance you might put the interface or the federator on the svn? That'd really help...

                                                                            Cheers,

                                                                            Max

                                                                            • Message #880

                                                                              Hi. I have enable the anonymous check out on my SVN. http://opensvn.csie.org/middleware/

                                                                              under NSClient++. I will load my code tonight since open svn is having some problem. Did not have any problem so far with it. All my C# are getting load and "federate" properly.

                                                                              Regards Franck

                                                                              • Message #881

                                                                                Great! Thanks for the quick reply.

                                                                                I might be (most likely i will) ask some further questions :)

                                                                                • Message #884

                                                                                  Hey franck,

                                                                                  NSModuleInterface is the actual Interface which has to be used to create the C# Plugin + I need the ManagedPluginFederator? but do I also need the "SampleManagedPlugin?" (the c++ one)?

                                                                                  Could you explain the process of creating a plugin with your federator?

                                                                                  • Message #885

                                                                                    My Process:

                                                                                    1. build Federator, output (dll) -> SampleManagedPlugin?\lib 2. Build ManagedPlugin?, modify SampleManagedPlugin?.c++ to use lib/ManagedPluginFederator.dll. But I'm actually getting:

                                                                                    1>SampleManagedPlugin?.cpp 1>.\SampleManagedPlugin?.cpp(11) : error C2660: 'SampleManagedPlugin?<target>::loadModule': Funktion akzeptiert keine 1 Argumente (function does not accept 1 argument) 1> with 1> [ 1> target=ManagedPluginFederator::Federator 1> ]

                                                                                    3. build my plugin, put it in modules\managedModules and GO.

                                                                                    Am I right?

                                                                                    Cheers, Max.

                                                                                    • Message #886

                                                                                      Ohh sorry you changed that already I was in the middle of something so I read your question quite quickly. For me this error is like you have changed the federator and added and argument to the loadmodule function which should not have any.

                                                                                      Note that you may want to copy the federator where your exe is. So I will add a forth step to what you have already.

                                                                                      4. copy ManagedPluginFederator?.dll in the same directory as NSClient.exe

                                                                                      regards Franck

                                                                                      • Message #887

                                                                                        no, i didnt change the federator. This error occurs when compiling the SampleManagedPlugin?. The affected lines are:

                                                                                        SampleManagedPlugin?<ManagedPluginFederator::Federator> gPluginImpl; NSC_WRAPPERS_MAIN_DEF(gPluginImpl);

                                                                                        • Message #888

                                                                                          ok looks like you may go into your solution explorer in the project SampleManagedPlugin? in visual studio and make sure you have included the header file NSCHelper.h. At least that the path is right.

                                                                                          • Message #902

                                                                                            Had anyone tried to install the solution? I want to package the whole thing now and run it as a service. Just realised that managed modules are getting skipped onload. So when I do NSclient++ /test everything works find but if I do NSClient++ /install then NSclient++ /start , no managed commands is available. I check the log file. the printf I did in the c++ module when it gets load is not showing up when I start the service. But it is showing up when I run it in test mode.

                                                                                            Any Ideas?

                                                                                            thanks