Problems Loading .dlls
-
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.
-
On 09/12/2014 at 03:33, xxxxxxxx wrote:
Hi Mohamed,
I'm not quiet sure, what you are referring to? -
On 09/12/2014 at 07:16, xxxxxxxx wrote:
Hi Andreas, I'm referring to this:
extern "C" \__declspec(dllexport) Iclass\* \__cdecl create_Iclass() { return new MyClass; } here he returns the class, and have the header to show the compiler the class layout, but what if the DLL is compiled with MSVC100 and used in a program which requires MSVC110 or Intel compiler, or even used in a non C++ program, if it is for C++, the Class layout in the DLL will be different from the Class layout in the executable, which will create a potential crash for an undefined behavior "hard to debug". this is all an assumption from what I learnt about C++ and why Classes are hard to export. I think a possible solution would be: static MyClass\* theClass = nullptr; extern "C" \__declspec(dllexport) bool \__cdecl create_Iclass() { if(!theClass) theClass = new MyClass; return theClass != nullptr; }
rest of the functions should operate on that pointer, delete that pointer at the end, etc... this is considered an encapsulated class which is inside the DLL and won't need to be exported outside the DLL borders.
off topic: how to put code in a code brackets on forums. -
On 09/12/2014 at 11:05, xxxxxxxx wrote:
after some reading, I noticed what Scotta did is a Class interface, which should be fine across different C++ compilers.
but I think it will be a " C++ DLL " so it is unusable for other languages like C#.