Problems Loading .dlls
-
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-dllthis 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 returnMy 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-dllexportif 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
[URL-REMOVED]-ScottA
[URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.
-
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
[URL-REMOVED]-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
[URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.
-
On 02/12/2014 at 14:27, xxxxxxxx wrote:
@Mohamed.
Does my code help you out with your question about sub folders?
The sPath variable can be used to target the .dll in any folder. Not just the master plugin folder.For example if it's in the "res" folder it would be something like this:
//If you want to target the res folder use this instead //**Just be sure you have actually put the .dll file in the res folder** Filename fn = GeGetPluginPath() + "res"; char *sPath = fn.GetString().GetCStringCopy();
Did that answer your question?
-ScottA
-
On 02/12/2014 at 16:26, xxxxxxxx wrote:
I think I understand how it works with LoadLibrary() now, but using this method you won't need the headers & libraries anymore I guess "so the compiler will give you a function pointer, and will trust you about the usage"
is this true?