Problems Loading .dlls



  • On 28/11/2014 at 10:09, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   13 
    Platform:   Windows  ;   
    Language(s) :     C++  ;

    ---------
    Hi,
    I can't get .dll files to load using LoadLibrary().
    When C4D starts up a window pops up saying it can't find the .dll file.
    It works fine if I put the .dll file in the main Maxon folder. But I want to store & load the .dlls in my plugin's folder structure.

    I also tried putting the .dll file here: C:\Program Files\MAXON\my C4D version\resource\libs\win64\SimpleDLL.dll
    But C4D still doesn't find it when it launches.

    This is how I'm trying to load the .dll from my C: drive. But it's doesn't work.
    What am I doing wrong?
    And do I really need to do all of this type conversion gymnastics just to load the .dll file with LoadLibrary()?

    #include "c4d.h"  
    #include <string.h>  
    #include "c4d_basedocument.h"  
      
    #include <string>       //for std::wstring  
    #define UNICODE         //for LPCTSTR  
    #include <Windows.h>  
    using namespace std;  
      
    //This custom method converts a Cineam4D string type to a STD C++ string type  
    std::string C4DStringToStdString(const String& s)  
    {  
      std::string r;  
      const LONG len = s.GetCStringLen(STRINGENCODING_XBIT);  
      CHAR* tmp = new CHAR[len + 1];  
      if (tmp)  
      {  
          if (len == s.GetCString(tmp, len + 1, STRINGENCODING_XBIT))  
          {  
              r.assign(tmp);  
          }  
      }  
      delete[] tmp;  
      return r;  
    }  
      
    //This custom method converts a string to a wstring type so we can use it in FindWindow() later on  
    std::wstring string2wstring(const std::string &s)  
    {  
      int len;  
      int slength = (int)s.length() + 1;  
      len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);  
      wchar_t* buf = new wchar_t[len];  
      MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);  
      std::wstring r(buf);  
      delete[] buf;  
      return r;  
    }  
      
      
    Bool RegisterSimplePluginCMD(void);  
      
    Bool PluginStart(void)  
    {  
     if (!RegisterSimplePluginCMD()) return false;  
     return true;  
    }  
      
    void PluginEnd(void)  
    {  
    }  
      
    Bool PluginMessage(Int32 id, void* data)  
    {  
      switch (id)  
      {  
      case C4DPL_PROGRAM_STARTED:  
      {  
          //Get the path to the.dll file on your HD  
          Filename fn = "C:\\SimpleDLL.dll";  
          GePrint(fn.GetFileString());  //<---just checking to see if the dll was found  
      
      
          //Convert the C4D String type into an LPCWSTR type  
          String c4dPath = fn.GetString();                      //The C4D String type path to the .dll file  
          string stlPath = C4DStringToStdString(c4dPath);       //Convert to a STL type string  
          wstring buffer = string2wstring(stlPath);             //A temporary buffer is required  
          LPCWSTR path = buffer.c_str();                        //Converted to an LPCWSTR type  
      
      
          //Load the .dll file so C4D can use it  
          HMODULE WINAPI dll_handle = LoadLibrary(path);  
          //or  
          //HINSTANCE dll_handle = LoadLibrary(path);  
      
      }break;  
      
      
      
      case C4DPL_INIT_SYS:  
      {  
          if (!resource.Init())  
              return false;     don't start plugin without resource  
          return true;  
      }  
      
         case C4DMSG_PRIORITY:  
             //react to this message to set a plugin priority (to determine in which order plugins are initialized or loaded  
             //SetPluginPriority(data, mypriority);  
             return true;  
      
         case C4DPL_BUILDMENU:  
             //react to this message to dynamically enhance the menu  
             //EnhanceMainMenu();  
             break;  
      
     }  
      
     return false;  
    }  
    

    -ScottA



  • On 28/11/2014 at 14:46, xxxxxxxx wrote:

    Hello Scott,
    I'm not sure you'll want to do what you are trying to do. How is this code supposed to work on Mac?
    But anyway, you'll have your reasons.
    I'm pretty sure, the problem is within your sting conversion. At first I tried with you complete code and had the same problems. The I did the following and it worked:
    I copied an arbitrary dll (log4net.dll in this case) in my plugins subdirectory and called:

    HMODULE WINAPI dll_handle = LoadLibrary(TEXT(".\\plugins\\log4net.dll"));
    

    So I recommend to double check your string code and if it is at all necessary to do such an complicated multi step string conversion. I guess not, but that leads to far in a forum for Cinema 4D API programming.



  • On 28/11/2014 at 15:35, xxxxxxxx wrote:

    Hmm. That doesn't work for me either.
    C4D still can't find the .dll when it launches. :frowning2:

    I'm not a Mac user. And I need to use #include<Windows.h> for LoadLibrary() to even compile.
    On top of that. The LoadLibrary() examples I have all require an LPCWSTR (unicode) type path string value. And that's why I'm doing all of that nasty converting stuff.
    So I had thought LoadLibrary() was a PC only thing.
    Is there a cross platform way to load .dlls into C4D?

    I've always thought that this subject needs to be covered with a tutorial. I don't even know if I'm writing my .dlls correctly or not. But they do seem to work fine if I put them in the main Maxon folder.
    I just wish I could get them to work in other folders (for example: my plugin's "res" folder).

    -ScottA



  • On 28/11/2014 at 16:37, xxxxxxxx wrote:

    Here is some code in example form - but it does not use wide/unicode characters.  Are you printing your path to see that it is properly formatted (':'and '' are properly handled such as '\' for the latter)?

    #ifdef	_WINDOWS
    #include <Windows.h>
      
    class MyPlugin ...
    {
    ...
    #ifdef	_WINDOWS
    		HMODULE  m_hDll;
    #else
    		void*    m_pDllLibrary;
    #endif
    ...
    };
      
    //*---------------------------------------------------------------------------*
    void MyPlugin::UnloadDll()
    //*---------------------------------------------------------------------------*
    {
    #ifdef _WINDOWS
    	if (m_hDll)
    		::FreeLibrary(m_hDll);
    	m_hDll = NULL;
    #else // _MAC or LINUX
    	if (m_pDllLibrary)
    		dlclose(m_pDllLibrary);
    	m_pDllLibrary =	NULL;
    #endif
    }
    //*---------------------------------------------------------------------------*
    Bool MyPlugin::LoadDll()
    //*---------------------------------------------------------------------------*
    {
    #ifdef _WINDOWS
    	if (m_hDll)
    		return TRUE;
    	String	strLocation =	"";
    	String	strResult =		"";
    	GetSystemEnvironmentVariable("ABSOLUTE_STORAGE", strResult);
    	if (strResult.Content())
    	{
    		strLocation = strResult+"\\MyDllFolder";
        }
    	if (!strLocation.Content())
    	{
    		return FALSE;
    	}
      
    	// Load Dynamic Library
    	// - Convert C4D String into char* string
    	char	cLocation[2048];
    	strLocation.GetCString(&cLocation[0], strLocation.GetLength()+1L);
    	m_hDll = ::LoadLibrary(cLocation);
    	if (!m_hDll)
    	{
    		AddToLog("Failed to load DLL ["+strLocation+"]");
            return FALSE;
      }
    	m_pfOriginalInterface = reinterpret_cast<MessageFunc>(::GetProcAddress(m_hDll, "DLLFunc"));
    #elif defined(_MAC)
    	if (m_pDllLibrary)
    		return TRUE;
    	// Explicitly defined as application folder.
    	m_pDllLibrary =		dlopen("/Applications/My.app/Contents/MacOS/libMyLib.dylib", RTLD_LAZY);
    	if (!m_pDllLibrary)
    	{
    		AddToLog("Failed to load Dylib");
    		return FALSE;
    	}
    	char*	error_message =		dlerror();
    	if (error_message)
    	{
    		AddToLog("Failed to load Dylib: "+String(error_message));
    		return FALSE;
    	}
    	m_pfOriginalInterface = reinterpret_cast<MessageFunc>(dlsym(m_pDllLibrary, "DLLFunc"));
    #else
    	String	strResult =		"";
    	String	strLocation =	"";
    	GetSystemEnvironmentVariable("ABSOLUTE_STORAGE", strResult);
    	if (strResult.Content())
        {
    		strLocation = strResult+"/MyDllFolder";
        }
    	else
        {
            strLocation = "/usr/company/myapp/MyDllFolder";
        }
    	if (!strLocation.Content())
    	{
    		return FALSE;
    	}
    #endif		//_WINDOWS
      
    	return TRUE;
    }
    


  • On 28/11/2014 at 16:58, xxxxxxxx wrote:

    hey Scotta,

    loading dll from custom directory is doable, I think you are looking for this:
    http://stackoverflow.com/questions/3832290/altering-dll-search-path-for-static-linked-dll

    this is working for static linked dlls, easy to do!!



  • On 28/11/2014 at 18:40, xxxxxxxx wrote:

    It occurs to me that I'm probably using the dll wrong. And this is why it's not loading?

    My DLL
    I have a simple raw C++ dll project that just has two simple functions:
    -A function that outputs a string value via cout
    -A function that outputs an int value via return

    My C4D Plugin
    In order to bring these dll functions into my C4D CommandData plugin's code and use them. I have to put a copy of the dll project's .lib file in my plugin's project. Then I have to link to it in Linker->Input->Additional Dependencies.
    I also need to put a copy of the dll project's .h file in my plugin's project files. And #include it at the top of my plugin.

    When I have the generated .dll file in my main MAXON\my C4D version\ folder.
    I create an instance of the class that's in my raw C++ dll project in my plugin code. And then access the values for the two functions that way.
    But this doesn't work when I put the .dll file anyplace except the main MAXON\my C4D version folder. And nothing you guys have suggested is fixing it.

    Maybe I'm using and/or applying the .dll wrong?
    Are dll functions supposed to be accessed inside of C4D plugin code?
    How else would I get the values from it?

    I'm flying kind of blind here because there are no tutorials on using dlls with C4D.
    I'm using tutorials for creating and using dlls in raw C++ projects as my learning source. And they might not be the same process.

    -ScottA



  • On 28/11/2014 at 19:26, xxxxxxxx wrote:

    just to be sure before debugging the dll path problem, did you export these functions? here is a how to:
    http://stackoverflow.com/questions/538134/exporting-functions-from-a-dll-with-dllexport

    if you didn't export/import functions, they will be statically compiled inside the cinema 4d plugin "and the external dll will be useless"

    BTW I also upvote Scotta question, I'm really interested in knowing a simple way to put the dll in a sub folder, I see many commercial plugins doing this and not sure how it is done "after all, the links that I posted are considered theories that work from main.exe file toward .dll file, here we are in a .dll file, not controlling how the main.exe file is behaving"



  • On 28/11/2014 at 19:53, xxxxxxxx wrote:

    Yeah I'm exporting them.
    This is the code in my DLL project

    //The .h file  
    class myclass  
    {  
      public:  
      static __declspec(dllexport) void hello();  
      static __declspec(dllexport) int number();  
    };  
      
      
    //The .cpp file  
    #include <iostream>  
    using namespace std;  
    #include "SimpleH.h"  
      
      void myclass::hello()  
      {  
          cout<< "I'm called from the DLL" << endl;  
      }  
      
      int myclass::number()  
      {  
          return 55;  
      }
    

    The program generates a .dll file in the Release folder. Which I then copy & paste into the MAXON\my C4D version\ folder. And it works.
    I can get the values from it by creating an instance of "myclass" in my C4D plugin. And then call the functions to get their values.
    *Not sure if this is how to correctly use a .dll though?

    But if I move the .dll to another folder and try to load it with LoadLibrary(path). C4D can't find it when it launches.

    -ScottA



  • On 29/11/2014 at 20:45, xxxxxxxx wrote:

    After watching some more videos about dlls. It seems that it's a very common practice to include a copy of the .dll along side the program's .exe (in this case the Maxon\Your C4D Version\ folder).
    So I think LoadLibrary() isn't my real problem here.

    However,
    If I have a copy of the .dll sitting next to the CINEMA 4D.exe files. Then there's doesn't seem to be any need to use LoadLibrary() at all. Because it will load the .dll without it.
    So this makes it very hard to test if LoadLibrary() is actually working or not.
    All I can do is check it with:

      
      if(dll_handle != 0) GePrint("found the DLL");  
      else GePrint("DLL Not found!");  
    

    So the real problem is.
    How the heck do we get C4D to launch without a copy of the .dll sitting next to the CINEMA 4D.exe files in the master folder?
    There's got to be a way. Because apparently some people are doing it.

    -ScottA



  • On 01/12/2014 at 06:28, xxxxxxxx wrote:

    Hi guys,

    I actually don't know what to begin with...

    First of all, this entire library loading has nothing to do with Cinema 4D. There's nothing special to do in Cinema 4D, that you wouldn't need to do in every other context as well.

    Then there seems to be an inherent misunderstanding about linking libraries.
    You need to differentiate static and dynamic linking (and I recommend everybody to check it out, use a tool like dumpbin or some objdump variant, there's also a nice plugin for TotalCommander to see the exported symbols of a library) :

    If you statically link a library, then you won't need to provide a .dll with your plugin, nor will you need to do a LoadLibrary() call within it. The library functions will be resolved and integrated (linked) into your binary (in this case your C4D plugin dynamic link library .cdl or .cdl64) at compile- (or rather) link-time. Normally you won't link a dll statically, but rather take a static library, that's meant for this purpose.

    In contrast dynamic linking, means that function calls (or basically any symbol) won't get resolved at compile time, but will be rather left "open". At run-time these will normally get resolved by the dynamic linker or loader. For this to work you will need to provide the DLL  in a path where the loader can find it. By default this is (amongst others) the directory your executable resides in. When building your project you will need a .lib file in addition to the .dll. From the .lib the compiler will take the function offsets within the dll, in order to be able to resolve function addresses and parameter offsets...

    And finally you can do the dynamic linking yourself, by loading the DLL at run-time manually, also known as delayed loading of DLLs. This is where LoadLibrary() comes into play. In order for this to work, you need to pay close attention to the Microsoft documentation, as even the version Windows might play a role in this.
    Regarding Scott's question, if there is a cross-platform way to do it: No, I don't think so. This is highly system dependent stuff and you will need different code branches to support such a concept across the different platforms (that's why I expressed my doubts...).

    Now, I'm finally returning to Scott's initial questions:
    Looking into the MSDN (MSDN LoadLibrary function) they recommend to use LoadLibrary() in conjunction with SetDllDirectory(). I have tested it here, and it works quiet nicely. BUT you have to pay close attention to another Windows concept: The working directory. Using relative (no need to say, that absolute paths are an absolute no go) paths with SetDllDirectory(), these start in your working directory. When you are running Cinema 4D from Visual Studio and did not change the setting for the Working Directory in your project, then this will be the project directory (where your .vxproj resides in). Of course this is different, when you are starting from Start-menu or with an icon on your desktop or via commandline. And you know, the user can also change the working directory (for example via Properties of the "icon"). So in the end you will end up with code, that needs to find out the Cinema 4D directory and afterwards will combine this with the path to your library.
    In order to debug this stuff, also note Window's GetLastError() function. And there's procmon (Process Monitor) from Sysinternals, which is a great tool, to find out what is actually happening, when you call LoadLibrary() (where the system searches for the DLL). In order to use it, you should setup a filter, otherwise you will be overwhelmed by the generated logfile...

    To come to an end: I heard your requests on doing some tutorial or writing some more elaborate documentation on this topic. All I can say is, I have added it to our list. But it won't be given high priority, as this (I repeat myself) is actually not a Cinema 4D related topic.



  • On 01/12/2014 at 08:02, xxxxxxxx wrote:

    Thanks Andreas.

    Is it at all possible to see a very simple example of this?
    Or possibly the LoadLibrary() & SetDllDirectory() code you used?
    I could use a little push point me in the right direction.

    If you can't I'll understand.

    -ScottA



  • On 01/12/2014 at 12:34, xxxxxxxx wrote:

    I'd like to add two points.

    I forgot to mention a very nice tool, which can (amongst other things) show you the symbols exported by a library. It's called Dependency Walker and is free.

    When talking with Sebastian about this, he thought it would be a good idea to explain, that the .cdl64 files you generate in your plugin projects are nothing else but DLLs, only renamed to .cdl64. And Cinema 4D does basically the same as you are planning to do, it uses LoadLibrary() to do a delayed loading of these library in order to integrate and start your plugin.



  • On 01/12/2014 at 18:32, xxxxxxxx wrote:

    I agree with Andreas here.  Your best bet is to go to MSDN and Apple's Development site and peruse their code examples and documentation on how to load dynamic libraries because, as mentioned, these are OS-based operations and not part of C4D.  Also, as noted, there are several ways to specify the location of the dll.  Mine used an environment variable with some appendages (all names changed due to NDA!).  You can use absolute paths (if you can construct them on all systems) or the working directory with appended paths.



  • On 01/12/2014 at 20:24, xxxxxxxx wrote:

    I've been doing that Robert. But it's been rough trying to figure it all out.
    At this point I can now use LoadLibrary() to load the .dll from my plugin folder. And now I'm in the process of learning how to use GetProcAddress() to target the functions in my dll.
    But I still don't know how to get the value from it yet.

    To make it easier.
    I've changed my DLL code and gotten rid of the class. And now I just have a function in it called GetNumber() that returns int 55;
    And I'm loading it like this based on an example I found on the web:

                    typedef double (*LPGETNUMBER)(double Nbr);  
                  LPGETNUMBER lpGetNumber;  
                  lpGetNumber = (LPGETNUMBER)GetProcAddress(dll_Handle, "GetNumber");
    

    It all works without errors.
    But I'm still trying to figure out how to get the value from my dll.
    If I cast it to an int I get a strange number like -112355447.

    I think I'm almost there. But it's slow going.

    -ScottA



  • On 01/12/2014 at 23:57, xxxxxxxx wrote:

    As you said, you are almost there.

    GetProcAddress() does not call your function. What you get from GetProcAddress() is a function pointer. You can then use the function pointer to call your function.
    Like so:

    GePrint(String::FloatToString(lpGetNumber()));
    


  • On 02/12/2014 at 07:05, xxxxxxxx wrote:

    I tried that. But I get an error that it can't convert LPGETNUMBER to a Real.
    So I'm trying to figure out how to convert it to a C4D compatible Real type this morning.
    I'm in the middle of a casting war. Wink

    -ScottA



  • On 02/12/2014 at 07:09, xxxxxxxx wrote:

    Scott,
    you are not supposed to cast LPGETNUMBER into a Float/Real, whatsoever. It is a function pointer! Use it to call the function as I showed in my previous post.



  • On 02/12/2014 at 07:31, xxxxxxxx wrote:

    OK.
    But If GetProcAddress() calls (executes the function) that's in the DLL. How do I get the value it sends out into C4D?
    That function is written in Raw C++ and not able to output any C4D values.
    So how would I connect the two things together so that C4D can read the output?

    -ScottA



  • On 02/12/2014 at 08:56, xxxxxxxx wrote:

    Scott,
    GetProcAddress() does NOT execute the library function (i repeat myself). To say it very (perhaps too) simple, it does a table look-up to get the function pointer for a given function name. And then you take the resulting function pointer to call the function.
    You have done the typedef for the function pointer already. You already defined it to return double, which could be easily cast to a Float64... perhaps you should read something about function pointers. As this concept somehow seems to block you.



  • On 02/12/2014 at 09:29, xxxxxxxx wrote:

    I'm getting closer.
    I'm now getting values back from the dll.
    For some reason I can't get the return value from GetNumber(). It's forcing me to put my own value in the params in the program. I'm trying to figure out why.
    But the MyAdd() function is working properly. And I can use it in my plugin code. Yay!

    // The DLL code

    //DLL's .h file  
    #ifdef SIMPLEDLL_EXPORTS  
    #define SIMPLEDLL_API __declspec(dllexport)   
    #else  
    #define SIMPLEDLL_API __declspec(dllimport)   
    #endif  
      
      
    //DLL's .cpp file  
    #include <iostream>  
    #include "SimpleH.h"  
    using namespace std;  
      
    extern "C"  
    {  
      __declspec(dllexport) int GetNumber()  
      {  
          return 55;  
      }  
      
      __declspec(dllexport) int MyAdd( int a, int b)  
      {  
          return a+b;  
      }  
    };
    

    // The plugin's main.cpp code without the error checking stuff

                case C4DPL_PROGRAM_STARTED:  
              {  
                  char *sPath = GeGetPluginPath().GetString().GetCStringCopy();  
      
                  if(!SetDllDirectory((LPCSTR)sPath)) GePrint("SetDllDirectory FAILED");  
      
                  HINSTANCE dll_Handle = NULL;      
                  dll_Handle = LoadLibrary("SimpleDLL.dll");  
      
                  //Get the value from the GetNumber() method in the DLL                  
                  typedef double (*LPGETNUMBER)(double Nbr);  
                  LPGETNUMBER lpGetNumber = NULL;   
                  lpGetNumber = (LPGETNUMBER)GetProcAddress(dll_Handle, "GetNumber");   
                  double gn = lpGetNumber(6); //<--- For some reason I have to supply a param. value here???  
                  GePrint(RealToString(gn));  
      
                  //Get the value using the MyAdd() method in the DLL  
                  typedef int (*LPADDNUMBERS)(int a, int b);    
                  LPADDNUMBERS lpAddNumbers = NULL;  
                  lpAddNumbers = (LPADDNUMBERS)GetProcAddress(dll_Handle, "MyAdd");   
                  int addn = lpAddNumbers(6, 6);  
                  GePrint(LongToString(addn));  
      
                  //Free the memory used  
                  FreeLibrary(dll_Handle);  
      
              }break;
    

    Almost there I think.
    I'm not ignoring your advice Andreas. I'm just trying to figure out what you're saying.
    So don't get mad at me. Smile

    -ScottA

    *Update:- Found the problem

    typedef double (*LPGETNUMBER)(double Nbr);
    should be
    typedef double (*LPGETNUMBER)();

    And also
    __declspec(dllexport) int GetNumber()
    should be
    __declspec(dllexport) double GetNumber()

    Using "int" in the DLL code and "double" in the the plugin's code returned a value of zero.
    That was tripping me up. So it's very important to keep the types matched properly.

    I think that does it Andreas. I think I'm done.

    Thanks again for your help.
    -ScottA


Log in to reply