C# - C++ Callbacks

PhilBayley

Well-known member
Joined
Jan 10, 2003
Messages
69
Location
UK
Ummm - I am getting very annoyed at the mo with the c# rubbish. I am now trying to use callbacks to my c# application from my c++ dll. For various reasons I need the dll to be compiled using the __cdecl (/Gd) calling convention. I have my c# code as follows

[DllImport("mydll.dll", EntryPoint="SetAddresses", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl)]
public static extern bool SetAddresses(PercentCallBack w,MessageCallBack x,MessageCallBack y, FinishedCallBack z);


This in theory should work as both the dll and the function are set to cdecl. The only way I can get this to work is to compile the c++ dll in __stdcall (/Gz). It doesnt seem to matter about the c# code as it seems to ignore the calling convention bit anyway.

HELP! - I need to do it using __cdecl.


PhilBayley
 
If you try to call a __cdecl function with the StdCall convention, it will still work, but the parameters will remain on the stack after the function has completed, meaning your stack space will quickly run out if you need to use the function many times.

Perhaps you could show us more code so that we can see what each of your delegates look like and such.
 
[c++ code in dll]

#define DLLEXPORT extern "C" __declspec (dllexport)
#define DLLImp __declspec (dllimport)
#define DLLExp __declspec (dllexport)

typedef void (* TUpdatePercent)(int);
typedef void (* TAddMessage)(LPCSTR);
typedef void (* TProcessFinished) (void);

DLLExp bool SetFunctionAdd(TUpdatePercent lUpdatePercentAdd, TAddMessage lMessageAdd, TAddMessage lErrorAdd, TProcessFinished lFinishedCallbackAdd)
{
long Result = 1;

gINI.UpdatePercent = NULL;
gINI.NonErrorMsg = NULL;
gINI.ErrorMsg = NULL;
gINI.FinishedProcess = NULL;

gINI.UpdatePercent = (TUpdatePercent)lUpdatePercentAdd;
if (gINI.UpdatePercent == NULL)
Result = 0;
else
gINI.UpdatePercent(50);

gINI.NonErrorMsg = (TAddMessage)lMessageAdd;
if (gINI.NonErrorMsg == NULL)
Result = 0;
else
gINI.NonErrorMsg("HELLO");

gINI.ErrorMsg = (TAddMessage)lErrorAdd;
if (gINI.ErrorMsg == NULL)
Result = 0;
else
gINI.ErrorMsg("Errors Initialised");

gINI.FinishedProcess = (TProcessFinished)lFinishedCallbackAdd;
if (gINI.FinishedProcess == NULL)
Result = 0;

if (Result == 0)
return false;
else
return true;
}

[c# code in application]

public class DLLInfo
{
public delegate void PercentCallBack (int iPercent);
public delegate void MessageCallBack (String sMessage);
public delegate void FinishedCallBack ();

[DllImport("cmdrip.dll", EntryPoint="SetAddresses", SetLastError=false, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl)]
public static extern bool SetAddresses(PercentCallBack w,MessageCallBack x,MessageCallBack y, FinishedCallBack z);
}

... // code in other part of program
DLLInfo.MessageCallBack AddMessage = new DLLInfo.MessageCallBack(AddNonErrorMsg);

DLLInfo.MessageCallBack AddError = new DLLInfo.MessageCallBack(AddErrorMsg);

DLLInfo.PercentCallBack DoPercent = new DLLInfo.PercentCallBack(UpdatePercent);

DLLInfo.FinishedCallBack FinishedCB = new DLLInfo.FinishedCallBack(FinishedProcessing);
DLLInfo.SetAddresses(DoPercent, AddMessage, AddError, FinishedCB);

This is a bit fragmented as the program has a few classes but you should get the general idea. I am getting a problem saying.

"The value of ESP was not properly saved across a function call"

This kinda put me onto the calling methods. I may have to cut my losses and write it all in c# but I am distributing the code and dont want anyone else to read the dll stuff (which is very easy even when obfuscated) unless anyone knows a better way.

Phil
 
Try replacing these three lines:
Code:
typedef void (* TUpdatePercent)(int);
typedef void (* TAddMessage)(LPCSTR);
typedef void (* TProcessFinished) (void);
with:
Code:
typedef void (__cdecl * TUpdatePercent)(int);
typedef void (__cdecl * TAddMessage)(LPCSTR);
typedef void (__cdecl * TProcessFinished) (void);
 
Volt,

its getting close though - I have changed just those functions to be __stdcall and it has stopped the error message. My only problem now is that once the dll has finished its thread the whole application closes and dies.

Do you know anything about stopping the garbage collection as this may be the reason its not working.

Phil
 
Garbage collection only affects managed code, which your DLL is not.

Is there a reason you need this DLL to be __cdecl and not __stdcall? If you say it works when you use __stdcall, why not just use that way? Or is it for some assignment or something?
 
Volte,

The only reason is to do with what I know about threading in c++. It is slightly different when you compile as __stdcall. I fixed the problem with the app shutting down, it was something to do with releasing the dll. My solution was not to release it and let windows take care of it (cowboy programming).

I then got another problem with not being able to debug the c++ dll when it is called from the c# app. This is because it loads a new version of the dll into memory instead of using the one I want to debug. Microsoft dont have a solution for this so I went with the solution I should have taken from the start and wrote the whole thing in c#.

I now just have to learn how to secure it so that the code doesnt get nicked if I ever release it to our customers.

Thanks for your help tho.

Phil
 
Unfortunately, you cant "secure" it per se, but you can obfuscate it so that all variables, procedure names, and calls are replaced with more confusing names (instead of mycounter it might be int_00483657095 or something, and all variables are similarly named). Anyone with too much time on their hands could deobfuscate it, but it would be easier for them to just rewrite the app.

You really shouldnt worry about trying to keep people from getting the source, as anyone who wants it bad enough with take it (as cluttered and useless as it may be) and anyone else will just write it themselves.
 
Back
Top