Problems Loading .dlls
-
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?
-
On 02/12/2014 at 16:31, xxxxxxxx wrote:
Mohamed,
yes, you are right. Except for some terminology. "the compiler will give" part. The compiler has nothing to do with it. LoadLibrary() in combination with GetProcAddress() will give you the function pointer. But the later part is right again, the compiler needs to trust you about the usage. -
On 02/12/2014 at 16:39, xxxxxxxx wrote:
Oh yeah. Sorry about that.
I forgot to post the header section in my plugin's main.cpp file.
Yes. I am including the header file for the DLL's project. But that's all. And nothing else.#include <Windows.h> #include "C:\Program Files\MAXON\Your C4D version\plugins\SimpleDLL\SimpleH.h" #The DLL's .h file must be here!! #include "c4d.h" #include <string.h>
I am not setting any of the compiler's options to point at any of the DLL program's files either.
That was another mistake I was making.-ScottA
-
On 02/12/2014 at 16:49, xxxxxxxx wrote:
@Andreas you are right, my terminology was wrong !
Tongue
[URL-REMOVED] , but you got my point about the trust part.
@Scotta you can compile the Cinema4D plugin without the header nor the library linkage , you just need the operating system API to hook the DLL
[URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.
-
On 02/12/2014 at 19:01, xxxxxxxx wrote:
I'm still trying to figure out how to use classes in my DLL's.
But you can mark this thread as Solved if want Andreas. I'll leave that up to you.If anyone has a class based example. They can send it to me at: [email protected]
-ScottA
-
On 07/12/2014 at 13:08, xxxxxxxx wrote:
Hey guys.
I finally managed to figure out how to use dll's with classes in them.
It requires using a class as an "interface". Which is something I've never done before. And it took me a while to figure it all out.
I'll attempt to write a quick tutorial how to do this here.
Hopefully I won't gloss over anything. And it should be a simple cut and paste operation for you to follow along with.Start Tutorial
Lets begin by using a simple 64bit C4D CommandData plugin to host the .dll to make it simple.
After you have your C4D CD plugin created and working. Lets write the DLL in a brand new separate project, and separate folder.
*Note: I will put this new DLL project in my C4D plugins folder. But it can go anywhere you want.
You'll just have to change the file paths accordingly if you do that.-Launch Visual Studio and choose "New project"
-I named my project: "ExportingDllClasses" which will create a .dll file with this same name
-Select the C++ ->Win32 Project wizard
-In the wizard's Applications Settings window. Check "dll" & "Empty project"
-If you're using a 64 bit C4D plugin. Go to the Configuration Manager and change the platform to win64. Using copy setting from win32.
-You should now have a .dll making project consisting of only empty folders at this point
-Add a new .h file called "generic_interface.h" to your Header Files folder
-Add a new .cpp file called "mydll.cpp" to your Source Files folderPut the following code into these two files. And compile it:
/////generic_interface.h //We create an "Interface" class of virtual functions that will be the home class of our .dll //Then we will create another class derrived from this interface...which we will then export so other programs can use it class Iclass { public: virtual void destroy() = 0; virtual double myAdd(double a, double b) = 0; virtual double GetNumber(double num) = 0; }; /////////////////////////////////////////////////////////// /////mydll.cpp #include "generic_interface.h" #include <iostream> #include <windows.h> using namespace std; class MyClass : public Iclass { private: int my_data; public: MyClass() : my_data(0) { cerr << "MyClass constructor\n"; //Outputs message if an error occurs } ~MyClass() { cerr << "MyClass destructor\n"; //Outputs message if an error occurs } void destroy() { delete this; } double myAdd(double a, double b) { return a+b; } double GetNumber(double num) { return num; } }; //This will export the above MyClass class so other programs can use it from the .dll file extern "C" __declspec(dllexport) Iclass* __cdecl create_Iclass() { return new MyClass; }
//Now we add the code to load the .dll file into our C4D plugin in the plugin's main.cpp file
#include <windows.h> #include "C:\\Program Files\\MAXON\\Your C4D Version\\plugins\\ExportingDllClasses\\generic_interface.h" #include "c4d.h" #include <string.h> ...Your other plugin stuff here... Bool PluginStart(void) { //example of installing a crashhandler old_handler = C4DOS.CrashHandler; // backup the original handler (must be called!) C4DOS.CrashHandler = SDKCrashHandler; // insert the own handler /////////////// Dynamically Loading the DLL section ////////////// //Get the folder where the .dll file is located Filename fn = "C:\\Program Files\\MAXON\\CINEMA 4D R13\\plugins\\ExportingDllClasses\\x64\\Debug"; char *path = fn.GetString().GetCStringCopy(); //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(); //Use SetDllDirectory to set the directory path where the .dll file exists //Stops loading the plugin if the .dll file is not found if(!SetDllDirectory((LPCSTR)path)) { GePrint("No .dll file Found!!"); return FALSE; } //Try to Load the .dll...Don't load the plugin if it fails HMODULE WINAPI dll_handle = LoadLibrary("ExportingDllClasses.dll"); if(!dll_handle) { GePrint("Error loading the .dll with LoadLibrary()!!"); DWORD err = GetLastError(); GePrint(LongToString((LONG)err)); return FALSE; } //If we have successfully loaded the .dll file //First we create a custom type called: "iclass_factory" (it's called a factory because we can create as many instances as we want) //Then we point to it using GetProcAddress() typedef Iclass* (__cdecl *iclass_factory)(); iclass_factory icf = reinterpret_cast<iclass_factory>( ::GetProcAddress(dll_handle, "create_Iclass")); if(!icf) { GePrint("Unable to load create_Iclass from DLL!"); ::FreeLibrary(dll_handle); //Stop loading the plugin if this failed return FALSE; } //Then we ask the factory to create a a new instance of the Iclass interface class in the dll program //Now this instance is local and can be treated like any other class. So we get it's members the same way Iclass *ik = icf(); //Then we get the functions from the DLL and use them in C4D double sum = ik->myAdd(2.4, 2); GePrint(RealToString(sum)); double num = ik->GetNumber(55.8); GePrint(RealToString(num)); //Finally. We destroy the instance, and free the memory used ik->destroy(); ::FreeLibrary(dll_handle); //Lastly...Register your plugin if the dll was loaded properly if(!RegisterSimplePlugin()) return FALSE; return TRUE; } ..The rest of you plugin's main.cpp code here...
Hopefully this is all clear enough to follow.
Let me know if I left something out.-ScottA
-
On 08/12/2014 at 15:24, xxxxxxxx wrote:
I'm not 100% sure "may someone more experienced than me can confirm this" , I think you got a problem here with your library, it may work only for the same compiler & language.
I think a better option would be "don't return the class" , you need to just create the class, hold its pointer somewhere by the library, and make all your exported functions access that pointer to operate on class internal data/functions. , I think this would let the library be portable to any language that accepts C DLLs.
but as I said this needs confirmation as I didn't test it myself.