Author Topic: Creating Object in a DLL by new Operator (C++)  (Read 10207 times)

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Creating Object in a DLL by new Operator (C++)
« on: 2004-10-14 16:53:33 »
Okay, I've got a problem with my plugin-interface for BrutePix.
BrutePix can handle DLL Plugins now, however I've got a problem with the deallocation of the space for a class.

This is the only function the DLL exports:
Code: [Select]
CBrutePlugin* CreatePlugin()
{
    return new CMyPlugin;
}

CMyPlugin is derived from CBrutePlugin.


Then, BrutePix loads the DLL with functions like LoadLibrary and GetProcAddr... no problems with that.

However, how do I delete that created Object?
When I try to delete it with the "delete" Operator in BrutePix, I get an error that the pointer is not within the local heap...

Any suggestions?

 - Alhexx

Qhimm

  • Founder
  • *
  • Posts: 1996
    • View Profile
    • Qhimm.com
Creating Object in a DLL by new Operator (C++)
« Reply #1 on: 2004-10-14 17:02:59 »
Usually when doing plug-in architectures some care must be taken to encapsulate different memory allocation mechanisms... The local heap is one way the system checks this, I believe. Usually when dealing with plugin-created allocations, the architecture also uses an exported FreePlugin function which, you guessed it, deallocates the object (which is 100% OK since the plugin allocated it in the first place and therefore know the right way to deallocate it). The destructor should be called correctly in any case (as long as you declared it virtual), but the actual memory (de-)allocation usually needs to be handled in one place, not spread out over different modules.

There might exist some more clever way to get around this issue (actually, it probably exists), but I don't know of any off-hand...

Micky

  • *
  • Posts: 300
    • View Profile
Creating Object in a DLL by new Operator (C++)
« Reply #2 on: 2004-10-14 17:14:11 »
You want to look for the design pattern "object factory". The problems with freeing/allocating memory is Win32-specific.

sfx1999

  • *
  • Posts: 1142
    • View Profile
Creating Object in a DLL by new Operator (C++)
« Reply #3 on: 2004-10-14 19:43:40 »
I'm not sure what you are saying. Is your DLL creating an internal object, and your main program can't delete it?

Kislinskiy

  • Guest
Creating Object in a DLL by new Operator (C++)
« Reply #4 on: 2004-10-15 13:13:44 »
@Alhexx

Wie Qhimm schon andeutete, MUSST Du neben einer Create()-Methode noch eine Free()-Methode aus der DLL in Deine Anwendung importieren. Windows hat für jede DLL ein anderen Heap, auf den andere Anwendungen nicht einfach so zugreifen können.

sfx1999

  • *
  • Posts: 1142
    • View Profile
Creating Object in a DLL by new Operator (C++)
« Reply #5 on: 2004-10-15 19:39:04 »
So using Babelfish, Kislinskiy, did you just say he needs to implement a separate object free function in his DLL?

Kislinskiy

  • Guest

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Creating Object in a DLL by new Operator (C++)
« Reply #7 on: 2004-10-17 15:14:43 »
Alright, I got it.
I just thought that there might be a way to do it without that Destroy function, since Milkshape handles it that way.
However, it looks like BrutePix will need Plugins that DO have those Destroy Functions :D



 - Alhexx

Cyberman

  • *
  • Posts: 1572
    • View Profile
Creating Object in a DLL by new Operator (C++)
« Reply #8 on: 2004-10-21 20:36:41 »
Quote from: Alhexx
Alright, I got it.
I just thought that there might be a way to do it without that Destroy function, since Milkshape handles it that way.
However, it looks like BrutePix will need Plugins that DO have those Destroy Functions :D
 - Alhexx


The reason why that is, well actually Microsoft does not like you using Object allocations between a main program and a DLL.  This is because there are some rather anoying sticky issues with call convention and object packaging, even within MS compilor systems. This is where COM comes in since it's a 'Standard' that MS doesn't seem to follow anymore (shrug).

You object has to destroy itself JUST for that reason alone.  The calling program won't know how to deallocate it as it would if the object code were linked in with the primary prgram.  Still there are some issues. I was advised by many programers to avoid OOP through DLL's and stick with conventional C Calls and conventions.

Having made several different types of plugins, this seems to be the simplest and sanest method.  You are just scratching the edge of the rats nest DLL's are in general.

It is just as difficult to deal with under linux sadly as well. (Though you can do some things with ELF that you can't under Windows in reguard to this).  The biggest problem is function call conventions with DLL's (this includes constructors and destructors), object organization is also a big issue.  So if you wish to make your plugin headaches less, I personally would make a DLL call interface to access the data within the objects created by the DLL's and never pass the object to the program that loaded the DLL.  Otherwise you could get into some rather anoying situations.

Cyb

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Creating Object in a DLL by new Operator (C++)
« Reply #9 on: 2004-11-17 21:06:16 »
Okay, I've been playing around with those Class-Exporting DLLs one more time. This time I've implemented that Destroy-Function in the DLL.
Everyting works fine, but when delete the object cia DLL, I get an error Message:

Quote
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.



I've created a Test-Project for that, and here are my files:

The Test Project consist of 2 single Projects, a DLL exporting a dervied class, and an executable calling the DLL's functions.

So let's start with the DLL.

This is the DLL's Main Module (DestroyDLL.cpp):
Code: [Select]
#include "windows.h"
#include "Kathy.h"

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}

CPerson* APIENTRY CreatePerson()
{
return new CKathy();
}

void APIENTRY KillPerson(CPerson* pPerson)
{
delete pPerson;
}



Here's Kathy.h:
Code: [Select]
#include "Person.h"

class CKathy : public CPerson
{
public:
CKathy() {};
~CKathy() {};
    char* SayIt();
};


And it's implementation, Kathy.cpp:
Code: [Select]
#include "Kathy.h"

char* CKathy::SayIt()
{
    return "I Love You.";
}


CKathy is derived from an object called CPerson (CPerson.h):
Code: [Select]
class CPerson
{
public:
CPerson() {};
~CPerson() {};
    virtual char* SayIt() = 0;
};





So much for the DLL.
The Executable consists of only 2 Files:
Person.h (see above) and DestroyEXE.cpp:
Code: [Select]
#include <stdio.h>
#include <windows.h>
#include "Person.h"

// Function Prototypes
typedef CPerson*(*PERSONPROC)();
typedef void(*KILLPROC)(CPerson*);

// Application Entry Point
int main(int argc, char* argv[])
{
    // Try to load DLL
    HMODULE hMod = LoadLibrary("DestroyDLL.dll");
    if(hMod == NULL)
    {
        printf("ERROR: Could not load DestroyDLL.dll!\n");
        return -1;
    }

    // Search for CreatePerson Function
    PERSONPROC kp = (PERSONPROC)GetProcAddress(hMod, "CreatePerson");
    if(kp == NULL)
    {
        printf("ERROR: Could not find CreatePerson!\n");
        FreeLibrary(hMod);
        return -1;
    }

// Search for KillPerson Function
KILLPROC kill = (KILLPROC)GetProcAddress(hMod, "KillPerson");
if(kill == NULL)
{
printf("ERROR: Could not find KillPerson!\n");
FreeLibrary(hMod);
return -1;
}

    // Create a Kathy for us ^_^
    CPerson* pKathy = kp();
    if(pKathy == NULL)
    {
        printf("ERROR: Kathy creation failed!\n");
        FreeLibrary(hMod);
        return -1;
    }

// Now let her say something
    printf(pKathy->SayIt());
   
    // Okay, that's enough, kill'er
    kill(pKathy);
   
    // And now, get out
    FreeLibrary(hMod);
   
// That's it
    return 0;
}



If you want it as a complete MSVC Project, here it is:
http://www.alhexx.com/destroy.rar



The program execution goes well until the "kill(pKathy)" command,
that's where I receive that error message...
Any ideas???

 - Alhexx

Qhimm

  • Founder
  • *
  • Posts: 1996
    • View Profile
    • Qhimm.com
Creating Object in a DLL by new Operator (C++)
« Reply #10 on: 2004-11-18 01:16:17 »
Quote from: Error message
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

Code: [Select]
void APIENTRY KillPerson(CPerson* pPerson)
{
delete pPerson;
}

Code: [Select]
// Function Prototypes
typedef CPerson*(*PERSONPROC)();
typedef void(*KILLPROC)(CPerson*);


There's your problem. Your DLL defines KillPerson (and CreatePerson) as functions using the __stdcall calling convention, i.e. the callee removes its own arguments from the stack upon return. This is secretly hidden inside the APIENTRY macro, but is very important to know. Normal C/C++ functions without any specified calling convention uses __cdecl however, which means the caller removes the arguments from the stack upon return. What your declarations do is define the function in the DLL to remove its arguments (a pointer of 4 bytes) from the stack, but then your EXE thinks the function is a __cdecl function so it also removes the arguments (a pointer of 4 bytes) from the stack. The result is that the stack was pushed once but popped twice, triggering your runtime error.

Technically CreatePerson suffers from the same problem, but since it has no arguments the problem doesn't arise.

The solution is to declare your function pointers in the EXE to use the same calling convention. Typically you use the APIENTRY macro here as well, which in a non-DLL context should resolve to just "__stdcall". Thus:

Code: [Select]
// Function Prototypes
typedef CPerson*(APIENTRY *PERSONPROC)();
typedef void(APIENTRY *KILLPROC)(CPerson*);

...should fix your issue.

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Creating Object in a DLL by new Operator (C++)
« Reply #11 on: 2004-11-18 11:14:09 »
Ah, yes, I complete forgot that. I have read about that a long time ago, but I never took care of that. So finally I know what that APIENTRY call is good for... once again, Qhimm, your help has set an end to my suffering  :wink:

I hope there won't be any more problems like this in my Plugin Creation Process - or I'll go mad...

Thanx!

 - Alhexx

Cyberman

  • *
  • Posts: 1572
    • View Profile
Creating Object in a DLL by new Operator (C++)
« Reply #12 on: 2004-11-19 17:59:09 »
Quote from: Alhexx
Ah, yes, I complete forgot that. I have read about that a long time ago, but I never took care of that. So finally I know what that APIENTRY call is good for... once again, Qhimm, your help has set an end to my suffering  :wink:

I hope there won't be any more problems like this in my Plugin Creation Process - or I'll go mad...

Thanx!

 - Alhexx

What do you mean you'll 'go' mad..? I thought everyone here was mad.. oh dear

I believe you should declare the library interface functions in a header and use that identical header to define the DLL loading functions and function pointers in your main program.  This makes DLL interfacing a whole lot easier I've found.

Cyb -  :wicked: