Interfacing with C++ DLLs

Share your advanced PureBasic knowledge/code with the community.
Toni6
User
User
Posts: 45
Joined: Mon Apr 23, 2012 1:39 pm

Interfacing with C++ DLLs

Post by Toni6 »

Used tools: Zip file with the source: http://www.filedropper.com/interfacingwithcdlls

Warnning: This is not meant to be a tutorial!

This is how i demangle C++ symbols:

Image

This is the Helper.pbi you see being included:

Code: Select all

#DLL_PATH = #PB_Compiler_FilePath + "C_PLUS\Release\"
#DLL_NAME = #DLL_PATH + "C_PLUS.dll"

; http://msdn.microsoft.com/en-us/library/ek8tkfbw%28v=vs.80%29.aspx
Macro thiscall_inline(this)
  EnableASM
  MOV ECX, this
  DisableASM
EndMacro

Macro thiscall_var(this)
  !mov ecx, [p.v_#this]
EndMacro

Macro thiscall_ptr(this)
  !mov ecx, [p.p_#this]
EndMacro

; more info here -> http://www.codeproject.com/Articles/28969/HowTo-Export-C-classes-from-a-DLL#CppNaiveApproach
Example 1 - CMath

C++ DLL:

Code: Select all

/************************************************************************/
/* Example 1                                                            */
/************************************************************************/
class __declspec(dllexport) CMath
{
public:
	CMath() {};
	~CMath() 
	{ 
		char buf[1000];
		sprintf(buf , "Hello from destructor\nthis: 0x%X", this);
		MessageBoxA(0, buf, "hi", 0); 
	};

	int add(int a, int b) { return a + b; };
	int subtract(int a, int b) { return a - b; };
	void MsgBox(char * Title)
	{
		char buf[1000];
		sprintf(buf , "this: 0x%X", this);

		MessageBoxA(0, buf, Title, 0);
	}
private:
	int someval;
};
PB source:

Code: Select all

XIncludeFile "Helper.pbi"

Prototype.i P_New_CMath()
Prototype  P_Free_CMath()
Prototype.l P_CMath_add(a.l, b.l)
Prototype.l P_CMath_subtract(a.l, b.l)
Prototype P_CMath_MsgBox(Title.p-ascii)

Global New_CMath.P_New_CMath
Global Free_CMath.P_Free_CMath
Global CMath_add.P_CMath_add
Global CMath_subtract.P_CMath_subtract
Global CMath_MsgBox.P_CMath_MsgBox

Procedure.i LoadCDLL(Filename.s)
  Protected hLib.i
  
  hLib = OpenLibrary(#PB_Any, Filename)
  If hLib
    New_CMath = GetFunction(hLib, "??0CMath@@QAE@XZ")
    Free_CMath = GetFunction(hLib, "??1CMath@@QAE@XZ")
                             
    CMath_add = GetFunction(hLib, "?add@CMath@@QAEHHH@Z")
    CMath_subtract = GetFunction(hLib, "?subtract@CMath@@QAEHHH@Z")
    CMath_MsgBox = GetFunction(hLib, "?MsgBox@CMath@@QAEXPAD@Z")
  EndIf
  
  ProcedureReturn hLib
EndProcedure

Procedure Test_CMath()
  Protected pCMath.i
  Protected resu.l
  
  pCMath = New_CMath()
  Debug "pCMath: "+Hex(pCMath)
  
  ; let's test the add method
  thiscall_var(pCMath)
  resu = CMath_add(5, 9)
  Debug "resu - CMath_add(5, 5) : "+Str(resu)
  
  ; let's test the subtract method
  thiscall_var(pCMath)
  resu = CMath_subtract(5, 2)
  Debug "resu -  CMath_subtract(5, 2) : "+Str(resu)
  
  ; let's test the MsgBox Method
  thiscall_var(pCMath)
  CMath_MsgBox("hello world!")
  
  ; let's call the destructor
  thiscall_var(pCMath)
  Free_CMath()
  
  Debug "Test Complete!"
EndProcedure

If Not LoadCDLL(#DLL_NAME)
  Debug "Failed to load: "+Chr(34)+#DLL_NAME+Chr(34)
  CallDebugger
EndIf

Test_CMath()
Example 2 - CPlayer

C++ DLL:

Code: Select all

/************************************************************************/
/* Example 2                                                            */
/************************************************************************/
class CPosition
{
public:
	CPosition() {};
	CPosition(float x, float y, float z) : x(x), y(y), z(z) {};
	~CPosition() {};

	float GetX() { return x; };
	float GetY() { return y; };
	float GetZ() { return z; };
private:
	float x, y, z;
};

// Interface DLL
class CPlayer
{
public:
	CPlayer() {
		strcpy(Name, "Player1");
		score = 5;
	};

	virtual ~CPlayer() 
	{ 
		char buf[1000];
		sprintf(buf , "Hello from destructor\nthis: 0x%X", this);
		MessageBoxA(0, buf, "hi", 0); 
	};

	virtual CPosition GetPosRandom() { return CPosition(5.0, 3.0, 2.0); };
	virtual char * GetPlayerName() { return Name; };
	virtual int SetPlayerName(char * NewName) 
	{
		strcpy(Name, NewName);
		return 1;
	};
	virtual int GetScore() { return score; };

private:
	char Name[256];
	int score;
};

extern "C" __declspec(dllexport) CPlayer * __stdcall CreatePlayerInterface()
{
	return new CPlayer;
}
PB source:

Code: Select all

XIncludeFile "Helper.pbi"

Prototype.i P_CreatePlayerInterface()

Prototype P_CPlayer_Destructor(unknown.a=1)
Prototype.i P_CPlayer_GetPosRandom(*OutPos)
Prototype.i P_CPlayer_GetPlayerName()
Prototype.l P_CPlayer_SetPlayerName(*NewName) ; ascii
Prototype.l P_CPlayer_GetScore()

Global CreatePlayerInterface.P_CreatePlayerInterface

Structure CPlayer_vTable Align #PB_Structure_AlignC
  Destructor.P_CPlayer_Destructor
  GetPosRandom.P_CPlayer_GetPosRandom
  GetPlayerName.P_CPlayer_GetPlayerName
  SetPlayerName.P_CPlayer_SetPlayerName
  GetScore.P_CPlayer_GetScore
EndStructure

Structure CPlayer Align #PB_Structure_AlignC
  *vTable.CPlayer_vTable
  Name.a[256]
  score.l
EndStructure

Structure CPosition Align #PB_Structure_AlignC
  x.f : y.f : z.f
EndStructure


Procedure.i LoadCDLL(Filename.s)
  Protected hLib.i
  
  hLib = OpenLibrary(#PB_Any, Filename)
  If hLib
    CreatePlayerInterface = GetFunction(hLib, "_CreatePlayerInterface@0")
  EndIf
  
  ProcedureReturn hLib
EndProcedure

Procedure Test_CPlayer()
  Protected *pCPlayer.CPlayer
  Protected resu.I
  Protected MyPos.CPosition
  
  *pCPlayer = CreatePlayerInterface()
  Debug "*pCPlayer: "+Hex(*pCPlayer)
  
  ; let's get the player position
  thiscall_ptr(pCPlayer)
  *pCPlayer\vTable\GetPosRandom(@MyPos)
  
  Debug "My Position: x: "+StrF(MyPos\x) + " | y: "+StrF(MyPos\y)+ " | z: "+StrF(MyPos\z)
  
  ; let's get the player name
  thiscall_ptr(pCPlayer)
  resu = *pCPlayer\vTable\GetPlayerName()
  
  Debug "Player Name: "+PeekS(resu, -1, #PB_Ascii)
  
  ; let's set a new player name
  thiscall_ptr(pCPlayer)
  *pCPlayer\vTable\SetPlayerName(@"Toni6")
  
  ; let's print the new name directly from the structure
  Debug "New Player Name: "+PeekS(@*pCPlayer\Name, -1, #PB_Ascii)
  
  ; now let's get the player score
  thiscall_ptr(pCPlayer)
  resu = *pCPlayer\vTable\GetScore()
  
  Debug "Player score: "+Str(resu)
  
  ; let's call the destructor to free the memory
  ;thiscall_ptr(pCPlayer)
  thiscall_inline(*pCPlayer)
  *pCPlayer\vTable\Destructor()
  
  Debug "Test Complete!"
EndProcedure

If Not LoadCDLL(#DLL_NAME)
  Debug "Failed to load: "+Chr(34)+#DLL_NAME+Chr(34)
  CallDebugger
EndIf

Test_CPlayer()
Example 3 - CPlayerEx

C++ DLL:

Code: Select all

/************************************************************************/
/* Example 3                                                            */
/************************************************************************/

class __declspec(dllexport) CPlayerEx
{
public:
	CPlayerEx() : kills(0) {
		strcpy(Name, "Player9");
	};

	virtual ~CPlayerEx() 
	{ 
		char buf[1000];
		sprintf(buf , "Hello from destructor\nthis: 0x%X", this);
		MessageBoxA(0, buf, "hi", 0); 
	};

	int GetKills() { return kills; };
	virtual void SetKills(int val) { kills = val; };
	void KillPlayer() {
		MessageBoxA(0, "Player killed!", "dead", 0); 
	};
private:
	char Name[256];
	int kills;
};
PB source:

Code: Select all

XIncludeFile "Helper.pbi"

Prototype.i P_CPlayerEx_New()
Prototype P_CPlayerEx_Destructor(unknown.a=1)
Prototype.l P_CPlayerEx_GetKills()
Prototype P_CPlayerEx_SetKills(val.l)
Prototype P_CPlayerEx_KillPlayer()

Structure CPlayerEx_vTable Align #PB_Structure_AlignC
  Destructor.P_CPlayerEx_Destructor
  SetKills.P_CPlayerEx_SetKills
EndStructure

Structure CPlayerEx Align #PB_Structure_AlignC
  *vTable.CPlayerEx_vTable
  Name.a[256]
  kills.l
EndStructure

Global CPlayerEx_New.P_CPlayerEx_New
Global CPlayerEx_GetKills.P_CPlayerEx_GetKills
Global CPlayerEx_KillPlayer.P_CPlayerEx_KillPlayer

Procedure.i LoadCDLL(Filename.s)
  Protected hLib.i
  
  hLib = OpenLibrary(#PB_Any, Filename)
  If hLib
    CPlayerEx_New = GetFunction(hLib, "??0CPlayerEx@@QAE@XZ")
    CPlayerEx_GetKills = GetFunction(hLib, "?GetKills@CPlayerEx@@QAEHXZ")
    CPlayerEx_KillPlayer = GetFunction(hLib, "?KillPlayer@CPlayerEx@@QAEXXZ")
  EndIf
  
  ProcedureReturn hLib
EndProcedure

Procedure Test_CPlayerEx()
  Protected *pCPlayerEx.CPlayerEx
  Protected resu.i
  
  *pCPlayerEx = CPlayerEx_New()
  Debug "*pCPlayerEx: "+Hex(*pCPlayerEx)
  
  ; set the player kills
  thiscall_ptr(pCPlayerEx)
  *pCPlayerEx\vTable\SetKills(9)
  
  ; get the player kills
  thiscall_ptr(pCPlayerEx)
  resu = CPlayerEx_GetKills()
  Debug "Player Kills: "+Str(resu)
  
  ; kill the player
  thiscall_ptr(pCPlayerEx)
  CPlayerEx_KillPlayer()
  
  ; show the player name
  Debug PeekS(@*pCPlayerEx\Name, -1, #PB_Ascii) + " has been killed!"
  
  ; now let's free the class
  thiscall_ptr(pCPlayerEx)
  *pCPlayerEx\vTable\Destructor()
  
  Debug "Test Complete!"
EndProcedure

If Not LoadCDLL(#DLL_NAME)
  Debug "Failed to load: "+Chr(34)+#DLL_NAME+Chr(34)
  CallDebugger
EndIf

Test_CPlayerEx()
Example 4 - CDog

C++ DLL:

Code: Select all

/************************************************************************/
/* Example 4                                                            */
/************************************************************************/

class CDog
{
public:
	CDog() : m_nAge(123) {};
	virtual __stdcall ~CDog()
	{
		char buf[1000];
		sprintf(buf , "Hello from destructor\nthis: 0x%X", this);
		MessageBoxA(0, buf, "hi", 0); 
	}

	virtual void __stdcall SetAge(int age) { m_nAge = age; } ;
	virtual int __stdcall GetAge() { return m_nAge; };
	virtual void __stdcall Kill() { MessageBoxA(0,"The dog is dead!", "Dead", 0); };
private:
	int m_nAge;
};


extern "C" __declspec(dllexport) CDog * __stdcall CreateDogInterface()
{
	return new CDog;
}
PB source:

Code: Select all

XIncludeFile "Helper.pbi"

Interface CDog
  Destructor()
  SetAge(age.l)
  GetAge.l()
  Kill()
EndInterface

Prototype P_CreateDogInterface()

Global CreateDogInterface.P_CreateDogInterface

Procedure.i LoadCDLL(Filename.s)
  Protected hLib.i
  
  hLib = OpenLibrary(#PB_Any, Filename)
  If hLib
    CreateDogInterface = GetFunction(hLib, "_CreateDogInterface@0")
  EndIf
  
  ProcedureReturn hLib
EndProcedure

Procedure Test_CDog()
  Protected myDog.CDog
  
  myDog = CreateDogInterface()
  Debug "myDog: "+Hex(myDog)
  
  ; let's set the dog age
  myDog\SetAge(9)
  
  ; get the dog age
  Debug "Dog age: "+Str(myDog\GetAge())
  
  ; kill the poor dog...
  myDog\Kill()
  Debug "The doog his dead!"
  
  ; let's get age directly from memory
  Debug "The doog has died at: "+Str(PeekL(myDog + 4))
  
  ; free the class
  myDog\Destructor()
  
  Debug "Test Complete!"
EndProcedure

If Not LoadCDLL(#DLL_NAME)
  Debug "Failed to load: "+Chr(34)+#DLL_NAME+Chr(34)
  CallDebugger
EndIf

Test_CDog()
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Interfacing with C++ DLLs

Post by luis »

Absolutely crazy and impressive.
And Daniel is a true monster, the good kind of monster :)
Thanks for sharing !
"Have you tried turning it off and on again ?"
A little PureBasic review
Fred
Administrator
Administrator
Posts: 16664
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Interfacing with C++ DLLs

Post by Fred »

I have to admit that is quite some hacks ! :)
User avatar
Danilo
Addict
Addict
Posts: 3037
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: Interfacing with C++ DLLs

Post by Danilo »

Toni6 wrote:This is how i demangle C++ symbols:
[...SNIP...]
Try this to un-mangle Visual C++ names, so you can remove that CFF-Explorer step:

Code: Select all

CompilerIf #PB_Compiler_Unicode

    Prototype proto_UnDecorateSymbolNameW(*DecoratedName, *UnDecoratedName, UndecoratedLength.l, Flags.l)
    
    Procedure UnDecorateSymbolNameW_(*DecoratedName, *UnDecoratedName, UndecoratedLength.l, Flags.l)
        Protected lib, result
        lib = OpenLibrary(#PB_Any,"Dbghelp.dll")
        If lib
            func.proto_UnDecorateSymbolNameW = GetFunction(lib, "UnDecorateSymbolNameW")
            If func
                result = func(*DecoratedName, *UnDecoratedName, UndecoratedLength, Flags)
            EndIf
            CloseLibrary(lib)
        EndIf
        ProcedureReturn result
    EndProcedure

    Macro UnDecorateSymbolName_(a,b,c,d)
        UnDecorateSymbolNameW_(a,b,c,d)
    EndMacro

CompilerEndIf


Procedure.s UnMangle(mangledName.s)
    Protected unmangledName.s = Space(2048)
    mangledName = Trim(mangledName)
    If mangledName <> ""
        If UnDecorateSymbolName_(@mangledName, @unmangledName, 2048, 0)
            ProcedureReturn Trim(unmangledName)
        EndIf
    EndIf
    ProcedureReturn ""
EndProcedure

Debug UnMangle("?subtract@CMath@@QAEHHH@Z")
Debug UnMangle("??0CMath@@QAE@XZ")
Debug UnMangle("?MsgBox@CMath@@QAEXPAD@Z")

Debug UnMangle("?KillPlayer@CPlayerEx@@QAEXXZ")
Debug UnMangle("??0CPlayerEx@@QAE@XZ")
Debug UnMangle("_CreateDogInterface@0")
You can use it with OpenLibrary() + NextLibraryFunction() etc. for automation.
Post Reply