Passing C++ class to DLL for callbacks from D (Steam)

evilrat evilrat666 at gmail.com
Sat Jun 9 14:11:13 UTC 2018


On Saturday, 9 June 2018 at 03:14:13 UTC, cc wrote:
> On Saturday, 9 June 2018 at 03:07:39 UTC, cc wrote:
>> I've put together a simplified test program here (124KB):
>
> Here is a pastebin of the D source file updated with some 
> additional comments at the end with the callback class 
> definitions from the original header files
> https://pastebin.com/M8hDXt6L

The proper bare minimum signature for call result callback is
------------------
extern(C++) abstract class CCallbackBase {
   abstract void Run( void *pvParam ) { writeln("Run()"); }
   abstract void Run( void *pvParam, bool bIOFailure, 
SteamAPICall_t hSteamAPICall );
   final int GetICallback() { return m_iCallback; } // this is 
actually optional
   abstract int GetCallbackSizeBytes();
  protected:
   uint8 m_nCallbackFlags; // probably can be left for some 
specific use cases
   int m_iCallback;
}
------------------
and the rest templates and stuff is purely for C++ convenience. 
Run(void*, bool, SteamAPICall_t) is what actually called for call 
result, then CallResult<> template overrides it to call 
appropriate pointer member in class with void (T::fun)(Data*, 
bool bIOFailure) signature.  You can mimic it on D side, or just 
ignore and make own.

There is one catch however, for it to be called flags should be 
equal 1, and iCallback must match struct enum, like 
NumberOfCurrentPlayers_t.k_iCallback for 
GetNumberOfCurrentPlayers()


However steam devs decided to shield actual pointer and return 
pointer sized integer when C API is used(or they just screw up?). 
Anyway, the pointers for subsystems returned by context calls on 
C++ API and mirrored C API calls are different, but they also 
have some mechanism for filtering this stuff, that way both 
integer handle and pointer calls the same underlying 
implementation, but C API call again is shielded so setting up 
CallResult and CCallback are ignored.


So my solution was just to make simple wrapper around C++ context 
calls and pass that real pointer to D side.

------------------- C++ code
// build as static library & link with your project
#ifdef _WIN32
// sorry, I just hardcoded it...
#pragma comment (lib, "steam_api64.lib")
#define _CRT_SECURE_NO_WARNINGS 1
#endif
   #include "public/steam/steam_api.h"
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS 1
#endif

extern "C" void* D_GetSteamUserStats()
{
   return SteamUserStats();
}

------------------- D code
// yes, I messed up with the signature to avoid casting, this may 
or may not be dangerous
extern(C) ISteamUserStats D_GetSteamUserStats();


This of course involves making C++ wrapper, but I don't really 
see other ways because they inlined everything ...


More information about the Digitalmars-d-learn mailing list