How to pass callback from C# to C correctly? - c++

I am working with Unity and I need to pass callback delegate from C# unity script to .dll
There is a api (MyApi.h) that I have on .dll side
typedef void(__stdcall * FuncPtr) (const char * str);
static FuncPtr DebugLog = nullptr;
static void debug_in_unity(std::string message)
{
if (DebugLog)
{
DebugLog(message.c_str());
}
}
extern "C"
{
DllExport void register_debug_callback(FuncPtr callback)
{
if (callback)
{
DebugLog = callback;
}
}
...
}
so, when I call register_debug_callback function from C# side I see that everything is ok and DebugLog assigned as expected.
Then in order to send my log message from .dll side to C# I need to call this function debug_in_unity()
So, I have another myfile.cpp file where I need to use this log function
#include "MyApi.h"
void MyClass::foo()
{
std::string log = "HERE!!!";
debug_in_unity(log);
}
So, everything looks fine, I have a global static method debug_in_unity and global func DebugLog that I assigned previously here register_debug_callback
But what actually is happen is - when I call this method register_debug_callback I see that I assigned DebugLog variable, but then when I call this method debug_in_unity I see that DebugLog is null. Looks like static variable is kind of not global like MyApi.h has instance of DebugLog and myfile.cpp has his own instance. I assume that it is a reason why I see assignment and then I see that the same value is null...
But how to use it properly? How to fix it?

You are breaking the one definition rule. You will need to have one translation unit (.c or .cpp file) defining DebugLog, and the header declaring it extern. Similarly your functions are also defined in multiple translation units.
MyApi.h
typedef void(__stdcall * FuncPtr) (const char * str);
extern FuncPtr DebugLog;
void debug_in_unity(std::string message);
extern "C"
{
DllExport void register_debug_callback(FuncPtr callback);
// ...
}
MyApi.cpp
#include "MyApi.h"
FuncPtr DebugLog = nullptr;
void debug_in_unity(std::string message)
{
if (DebugLog)
{
DebugLog(message.c_str());
}
}
extern "C"
{
DllExport void register_debug_callback(FuncPtr callback)
{
if (callback)
{
DebugLog = callback;
}
}
// ...
}

Related

C++ - How to bind a callback to a class method without being static?

I have my class:
class Foo
{
public:
(...)
private:
void mycallback(void* buff, wifi_promiscuous_pkt_type_t type);
void registerMyCallback();
};
The mycallback is the callback.
I want to use a method esp_wifi_set_promiscuous_rx_cb to register the mycallback so that when a WiFi packet is detected, this callback method will be executed.
The esp_wifi_set_promiscuous_rx_cb signature is:
esp_err_t esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb_t cb);
Where the wifi_promiscuous_cb_t definition is:
typedef void (* wifi_promiscuous_cb_t)(void *buf, wifi_promiscuous_pkt_type_t type);
I want to use the mycallback method inside my class, therefore I simply can't use like this:
void Foo::registerMyCallback()
{
esp_wifi_set_promiscuous_rx_cb(&mycallback);
}
I know that I could use something similar if I would just make my method as static.
Is there anyway that I bind mycallback to esp_wifi_set_promiscuous_rx_cb without making the callback static?
I have tried the following:
esp_wifi_set_promiscuous_rx_cb(std::bind(&Foo::mycallback, this, std::placeholders::_1, std::placeholders::_2));
But I am still having the following error:
cannot convert 'std::_Bind_helper<false, void (Foo::Foo::*)(void*, wifi_promiscuous_pkt_type_t),
Foo::Foo*, const std::_Placeholder<1>&, const std::_Placeholder<2>&>::type
to
'wifi_promiscuous_cb_t {aka void (*)(void*, wifi_promiscuous_pkt_type_t)}' for argument '1'
Th library you are using is C package.
Thus the only guaranteed way pass a valid function is to pass a C function with C linkage. This function can then call the method on your object.
If you want the callback method to be non static you need to store a pointer (ore reference) to the callback object somewhere that your callback function can find it. (in most C callback functions you can provide a void* object that is passed to your callback, but this interface does not seem to allow this so you will have to save the value yourself).
Foo* myCBObject = nullptr;
extern "C" void myCB(void *buf, wifi_promiscuous_pkt_type_t type)
{
try
{
myCBObject->mycallback(buff, type);
}
catch(...) {} // Don't allow exceptions to cross C linkage
}
...
// Your code.
void Foo::registerMyCallback()
{
myCBObject = this;
esp_wifi_set_promiscuous_rx_cb(myCB);
}
Note: You should NOT be registering static member functions with a C library. If this works it is only by chance. There is no guarantee that a static function has the same calling convention of a C function (they usually do but that is not guaranteed).
After some research, I hope I found the solution. The trick is to bind member function first and then obtain the function pointer from the std::function. Notice the usage of my_wifi_promiscuous_cb_t and std::function::target<>().
#include <iostream>
#include <functional>
using namespace std::placeholders;
// using fake definitions
extern "C"
{
enum wifi_promiscuous_pkt_type_t {};
typedef int32_t esp_err_t;
typedef void (*wifi_promiscuous_cb_t)(void* buf, wifi_promiscuous_pkt_type_t type);
typedef void my_wifi_promiscuous_cb_t(void* buf, wifi_promiscuous_pkt_type_t type);
esp_err_t esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb_t cb)
{
return 0;
}
}
class Class
{
public:
void mycallback(void* buff, wifi_promiscuous_pkt_type_t type) {}
void registerMyCallback() {
std::function<void(void*, wifi_promiscuous_pkt_type_t)> fun2 = std::bind(&Class::mycallback, this, _1, _2);
esp_wifi_set_promiscuous_rx_cb(fun2.target<my_wifi_promiscuous_cb_t>());
}
};
int main()
{
Class c;
c.registerMyCallback();
}

Call a function in the main program from an imported DLL

I am trying to create a program that uses plugins in C++ to get some experience with importing and exporting functions from .dll and .so libraries. For simplicity's sake let's only use .dll libraries here.
What I'm trying to do is to make the communication between the plugin and the main program that loaded it "two-way", meaning the main program can call functions from the plugin (this is solved) and the plugin should be able to call functions from the main program (this I'm having trouble with).
I currently am able to create a .dll where I exported the functions with extern "C" {} and __declspec(export).
TestPlugin.h
#pragma once
extern "C" {
__declspec(dllexport) const char* pluginName();
__declspec(dllexport) void onLoad();
__declspec(dllexport) void onShutdown();
}
TestPlugin.cpp
#include "TestPlugin.h"
#include <iostream>
const char * pluginName()
{
return "Test Plugin";
}
void onLoad()
{
std::cout << "onLoad() called!" << std::endl;
}
void onShutdown()
{
std::cout << "onShutdown() called!" << std::endl;
}
I am then loading this test plugin with the following (shortened) code. I removed the error checking and console output.
Plugin.h
#pragma once
#include <filesystem>
#include <iostream>
#include <windows.h>
class Plugin
{
private:
typedef const char*(*pluginNameType)();
typedef void(*onLoadType)();
typedef void(*onShutdownType)();
HINSTANCE m_lib;
public:
Plugin(std::filesystem::path filename);
~Plugin();
pluginNameType pluginName;
onLoadType onLoad;
onShutdownType onShutdown;
};
Plugin.cpp
#include "Plugin.h"
Plugin::Plugin(std::filesystem::path filename)
{
m_lib = LoadLibrary(filename.wstring().c_str());
pluginName = (pluginNameType)GetProcAddress(m_lib, "pluginName");
onLoad = (onLoadType)GetProcAddress(m_lib, "onLoad");
onShutdown = (onShutdownType)GetProcAddress(m_lib, "onShutdown");
}
Plugin::~Plugin()
{
FreeLibrary(m_lib);
}
What I can do now is to call the functions in the plugin (TestPlugin.cpp) from my main program.
main.cpp
Plugin *plugin = new Plugin("pathToDLLGoesHere");
plugin->onLoad();
plugin->onShutdown();
What I would like to do now is to also enable the test plugin I just loaded to have access to functions that are defined in the main program. So let's say in my main.cpp I have something like this ...
main.cpp
int testCall(int val) {
return val + 1;
}
int main()
{
...
return 0;
}
... how would I be able to call the testCall() from the test plugin?
Would it be as simple as to send the function pointer to the plugin and use it? Or do I need to take a different approach here? Thank you for your help!
I have figured out how this works. You can also use extern "C" {} and __declspec(dllexport) to export functions from your main program so the DLLs can see them and when you get the handle of your main program in the DLL, the functions can be called.
In one of your headers in your main program you export the function.
main.h
extern "C" {
__declspec(dllexport) int testCall(int val);
}
main.cpp
int testCall(int val) {
return val + 1;
}
In my test plugin header I created a handle for the main program and a definition for the function I am trying to call from main.
TestPlugin.h
#pragma once
#include <windows.h>
HINSTANCE app;
int(*testCall)(int val);
...
In the body I then assign the handle (calling GetModuleHandle with a nullptr will give you the handle of your program) and then get the exported function from my main program.
TestPlugin.cpp
app = GetModuleHandle(nullptr);
testCall = (int(*)(int val))GetProcAddress(app, "testCall");
After that, I can just call the function.
std::cout << testCall(5) << std::endl;

Callback function with function pointer through DLL with Run-Time Check Failure #0 error

I had two projects which are written by C/C++.
Project 1 output is exe file and named MyProject.
Project 2 output is dll file and named Bridge.
I try to let Bridge to execute the function in MyProject. The function seems work, but I encounter an error
"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 doubt that the root cause may happen in __cdecl __stdcall convention, but still don't know how to solve it. All the project's Calling convention setting are __cdecl(/Gd), and VS IDE is VS 2013.
DLL Project (Bridge) Code
Header
typedef void(*sfcTrace)(const char *logLevel, const char *logMessage);
class OnRequestHandler
{
public:
virtual void writeLog(sfcTrace callback) = 0;
};
class GenericInfoHandler : public OnRequestHandler
{
public:
GenericInfoHandler();
~GenericInfoHandler() { delete this; };
void writeLog(sfcTrace callback);
};
extern "C" __declspec (dllexport) OnRequestHandler* __cdecl oneBridgeCallBack()
{
return new GenericInfoHandler;
}
CPP
void GenericInfoHandler::writeLog(sfcTrace callback)
{
const char *level = "DEBUG";
const char *message = "TEST";
callback(level, message);
}
MyProject Source code:
typedef OnRequestHandler* (__cdecl *test)();
HINSTANCE getDLL = LoadLibrary("Bridge.dll");
if (!getDLL)
{
cout << "Cannot not load DLL." << endl;
}
test func = (test)::GetProcAddress(getDLL, "oneBridgeCallBack");
if (!func)
{
cout << "Cannot not locate the function." << endl;
}
OnRequestHandler* instance = func();
instance->writeLog(&MyProject::TestCallBack); <----- Error Occurs here
Function implementation in MyProject
void MyProject::TestCallBack(const char *level, const char *message)
{
if (strcmp(level, "INFO") == 0){
// do something
}
else if (strcmp(level, "DEBUG") == 0){
// do something
}
}
Header:
typedef void(MyProject::*TestCallBack)(const char *logLevel, const char *logMessage);
class OnRequestHandler
{
public:
virtual void writeLog(sfcTrace callback) = 0;
};
class GenericInfoHandler : public OnRequestHandler
{
public:
GenericInfoHandler();
~GenericInfoHandler() { delete this; };
void writeLog(sfcTrace callback);
};
Try the following: (I am presuming that MyProject::TestCallback is not a static method, and it is it's use as a plain function that causes the stack corruption)
Update
typedef void(*sfcTrace)(const char *logLevel, const char *logMessage); to take an additional void * argument that will be supplied alongside the callback. I.e.:
typedef void(*sfcTrace)(void *cb_arg, const char *logLevel, const char *logMessage);
^^^^^^^^^^^^
Then update OnRequestHandler::writeLog(sfcTrace callback) and its overrides to take the additional argument from the caller. I.e.,
virtual void OnRequestHandler::writeLog(sfcTrace callback, void *cb_arg) = 0;
^^^^^^^^^^^^
void GenericInfoHandler::writeLog(sfcTrace callback, void *cb_arg);
^^^^^^^^^^^^
Update the implementation of writeLog to pass this new arg to the callback:
void GenericInfoHandler::writeLog(sfcTrace callback, void *cb_arg)
{ ^^^^^^^^^^^^
const char *level = "DEBUG";
const char *message = "TEST";
callback(cb_arg, level, message);
} ^^^^^^
Now we can write a new non-member callback function that is capable of calling a MyProject object's methods, so long as a MyProject object is passed via cb_arg:
void myprj_method_callback(void *arg, const char *logLevel, const char *logMessage) {
MyProject *mp = (MyProject *)arg;
mp->TestCallBack(level, message);
}
Finally, we can update the call to pass the new call-back function, and argument:
instance->writeLog(myprj_method_callback, (void *)this);
As an aside, it is generally good practice on all callbacks to let the callback supplier pass an argument that will in turn be passed to the callback when it is called. This lets the user of a callback mechanism convey the relevant data-structures to the callback function without having to store them in global variables.

How to create a shared_ptr in dll and export it via a factory function?

I have this pieces of code:
class DLL_API MyClassWrapper
{
private:
MyClass * m_myClass;
public:
MyClassWrapper(SIZE inputSize);
~MyClassWrapper();
inline int OutputSize();
}
typedef std::shared_ptr<MyClassWrapper> MyClassWrapperPtr;
extern "C"
{
DLL_API MyClassWrapperPtr CreatreMyClassWrapper(SIZE inputSize)
{
return std::make_shared<MyClassWrapper>(inputSize);
}
}
But it doesn't work, with error:
Error 1 error C2526: CreatreMyClassWrapper: C linkage function cannot return C++ class 'std::shared_ptr<_Ty>'
I understand the problem, but how can I fix it?
The options that I can see are:
1- Don't pass a shared pointer. which means that DLL user should delete the pointer after they used it.
2- Don't use extern "C" : which means that I must use mangled names.
Is there any other solution?
Straight to the point, to return C++ object from C function - just returns it via output arguments:
extern "C"
{
DLL_API void CreatreMyClassWrapper(SIZE inputSize, SomeClass* outputPtr)
{
*outputPtr = SomeClass(....);
}
}
In your example SomeClass == MyClassWrapperPtr, so:
extern "C"
{
DLL_API void CreatreMyClassWrapper(SIZE inputSize, MyClassWrapperPtr* outputPtr)
{
*outputPtr = make_shared<MyClassWrapper>(inputSize);
}
}
Consider however to change your interface a little, because in current shape you need to be sure that your applications and DLLs shall use the same compiler, linker, settings, libraries...*
You might want to export Create and Delete from your DLL to be sure memory management will occur in your DLL (this is based on this answer:
DLL
extern "C"
{
DLL_API MyClassWrapper* CreateMyClassWrapper(SIZE inputSize)
{
return new MyClassWrapper(inputSize);
}
DLL_API void DeleteMyClassWrapper(MyClassWrapper* wrapper)
{
delete wrapper;
}
}
Application
shared_ptr<MyClassWrapper> myClassWrapper(CreateMyClassWrapper(inputSize),
DeleteMyClassWrapper);

What is the right way to return a reference to a class static data member? (I'm using Qt, in case it makes a difference)

I'm trying to get the address of a class static data member from a DLL and keep it around in host code. However, I'm loosing the pointer / reference to the member the minute I exit the method in the dll-manager which opens all the (Windows typedef) HINSTANCE s, even though I'm keeping them open.
My setup is:
A Qt GUI application, which includes a class that loads plugins from dlls. This dll-manager class doesn't use Qt stuff but for Qdir and Qstrings here and there...
The dll-manager should issue a bunch of LoadLibrary() calls to open DLLs and for each, call an exported function which returns the address of a static "info" struct inside the class the DLL exports.
For instance, The DLL class looks like this:
BlackNWhite.h
#ifdef BLACKNWHITE_EXPORTS
#define BLACKNWHITE_API __declspec(dllexport)
#else
#define BLACKNWHITE_API __declspec(dllimport)
#endif
// This class is exported from the BlackNWhite.dll
class BLACKNWHITE_API CBlackNWhite : PCOperatorBase
{
public:
CBlackNWhite(void);
virtual ~CBlackNWhite(void);
virtual int process(int* inBuffer, int* outBuffer, int bufferSize);
void getParametersInfo(const std::vector<ParameterDescriptor>*& outParameters);
static const OperatorInfo& info();
protected:
static OperatorInfo operatorInfo;
};
extern "C" __declspec(dllexport) PCOperatorBase* getOperatorInstance();
extern "C" __declspec(dllexport) const PCOperatorBase::OperatorInfo& getOperatorInfo();
BlackNWhite.cpp
#include "stdafx.h"
#include "BlackNWhite.h"
PCOperatorBase::OperatorInfo CBlackNWhite::operatorInfo = {L"Black N White", L"modifier", L"color"};
const PCOperatorBase::OperatorInfo& CBlackNWhite::info()
{
return CBlackNWhite::operatorInfo;
}
extern "C" __declspec(dllexport) PCOperatorBase* getOperatorInstance()
{
return (PCOperatorBase*)(new CBlackNWhite());
}
extern "C" __declspec(dllexport) const PCOperatorBase::OperatorInfo& getOperatorInfo()
{
return CBlackNWhite::info();
}
CBlackNWhite::CBlackNWhite()
: PCOperatorBase()
{
ParameterDescriptor newParameter;
newParameter.label = L"Parameter 1";
parameters.push_back(newParameter);
}
CBlackNWhite::~CBlackNWhite()
{
}
int CBlackNWhite::process(int* inBuffer, int* outBuffer, int bufferSize)
{
while(bufferSize--)
*outBuffer++ = *inBuffer++;
return 0;
}
void CBlackNWhite::getParametersInfo(const std::vector<ParameterDescriptor>*& outParameters)
{
outParameters = &parameters;
}
And this class inherits from a base class:
PCOperatorBase.h
#pragma once
#include "PCOperatorParameters.h"
#include <vector>
class PCOperatorBase
{
public:
typedef struct OperatorInfo
{
wchar_t* name;
wchar_t* type;
wchar_t* subtype;
} OperatorInfo;
PCOperatorBase(void){};
virtual ~PCOperatorBase(void){};
virtual void getParametersInfo(const std::vector<ParameterDescriptor>*& outParameters) = 0;
virtual int process(int* inBuffer, int* outBuffer, int bufferSize) = 0;
protected:
std::vector<ParameterDescriptor>parameters;
};
And the DLL-manager has two relevant methods. One builds the list of available plugins and the other one just returns the string names of the plugins.
void PCOperatorManager::buildOperatorList(const QString path)
{
QDir operatorDirectory(QDir::currentPath() + path);
if(operatorList.size())
operatorList.clear();
QStringList operatorNameList = operatorDirectory.entryList(QStringList("*.dll"));
typedef PCOperatorBase::OperatorInfo*(*PCOClassInfoFunction)();
for(QStringList::iterator PCOClassName = operatorNameList.begin();
PCOClassName != operatorNameList.end();
PCOClassName++)
{
HINSTANCE PCOClassHandle;
if((PCOClassHandle = LoadLibrary((operatorDirectory.absolutePath() + "/"+ *PCOClassName).toStdWString().c_str())))
{
OperatorDescriptor newPCOClassDescriptor;
newPCOClassDescriptor.handle = PCOClassHandle;
newPCOClassDescriptor.info = (*((PCOClassInfoFunction)GetProcAddress(PCOClassHandle, "getOperatorInfo")))();
operatorList.push_back(newPCOClassDescriptor);
printf("\n we have: %ls", operatorList[0].info->name);
}
}
}
QStringList PCOperatorManager::getOperatorNameList()
{
QStringList operatorNameList;
printf("\n the list length is: %i", operatorList.size());
for(int i = 0; i < operatorList.size(); i++)
printf("\n we have again: %ls", operatorList[0].info->name);
//operatorNameList << QString::fromWCharArray((*PCOClass).info.name);
return operatorNameList;
}
What's happening is: inside buildOperatorList() I can access the static member of the DLL-class and assign it to the info member in the OperatorDescriptor struct. That is, the "test" printf statement that reads "we have" does print out the right value for that field.
However, inside getOperatorNameList() the info member is not valid anymore.
My line of thought is that what I'm doing is:
I have a pointer to a OperatorInfo struct, called info.
I get the address of a static OperatorInfo struct in the DLL-class,
called operatorInfo.
I assign the address of the class' operatorInfo to the pointer
called info. That is info = &CBlackNWhite::operatorInfo;
At this point, the pointer should stay valid as long as I don't issue a FreeLibrary() on the DLL HINSTANCE
So what's going on?
I see here operatorList is not a member variable of PCOperatorManager once it is added and constructed as in buildOperatorList I think you should ave access in getOperatorNameList()