Function Pointers, Delegates, Legacy Code...and some sympathy

M

Mark Olbert

First off, the sympathy is for all you poor buggers out there who have to figure out how to marry
Managed Extensions for C++ onto your legacy code. My condolences; my brief experience with the
process leads me to believe the Microsoft hates all of its VC++ developers :)

Now, the question. I'm getting a NullReferenceException in a __gc class that wraps functionality in
a legacy C/C++ app (it's actually part of the Nero burning rom api).

The legacy API expects to be given a configuration structure that contains, among other things, a
pair of function pointers which the codebase calls at various times. After much hair-pulling, I came
up with the following:

__delegate bool IdleCallbackHandler( void* pUserData );
__delegate NeroUserDlgInOut UserDialogCallbackHandler( void* pUserData, NeroUserDlgInOut type, void*
data );

[StructLayout(Layout::Sequential)]
__gc public struct NeroIdleCallback
{
public:
IdleCallbackHandler* ncCallbackFunction;
void* ncUserData;
};

[StructLayout(Layout::Sequential)]
__gc public struct NeroUserDialogCallback
{
public:
UserDialogCallbackHandler* ncCallbackFunction;
void* ncUserData;
};

[StructLayout(Layout::Sequential)]
__gc public struct NeroSettings
{
public:
const char *nstNeroFilesPath;
const char *nstVendor, *nstSoftware;
const char *nstLanguageFile;
NeroIdleCallback* nstIdle;
NeroUserDialogCallback* nstUserDialog;
bool nstEnableOverburn;
DWORD nstOverburnSize;
};

I configure this structure, and the callback substructures, in the constructor of my wrapper class
as follows:

theSettings = new NeroSettings();
theSettings->nstNeroFilesPath = "NeroFiles";
theSettings->nstLanguageFile = "Nero.txt";

theSettings->nstEnableOverburn = false;
theSettings->nstOverburnSize = 0;

theSettings->nstIdle = new NeroIdleCallback();
theSettings->nstIdle->ncCallbackFunction = new IdleCallbackHandler(this, &Nero::FireIdleEvent);
theSettings->nstIdle->ncUserData = idleData;

theSettings->nstUserDialog = new NeroUserDialogCallback();
theSettings->nstUserDialog->ncCallbackFunction = new UserDialogCallbackHandler(this,
&Nero::FireUserDialogEvent);
theSettings->nstUserDialog->ncUserData = userdlgData;

theSettings->nstVendor = "ahead";
theSettings->nstSoftware = "Nero - Burning Rom";

Everything works fine during initialization, and in retrieving certain basic information from the
legacy codebase.

But when I call a legacy function that triggers the IdleCallback, I get the NullReferenceException:

void AvailableDrives( NERO_MEDIA_TYPE mtFlags )
{
// this next line blows up... but the IdleCallback gets called first
NERO_SCSI_DEVICE_INFOS* drives = NeroGetAvailableDrivesEx(MEDIA_CD, NULL);
...
}

The interesting part is the part mentioned in that comment: the IdleCallback method gets called,
and returns, but then the exception gets thrown.

And what's really interesting is that setting the ncCallbackFunction delegate/function pointer to
NULL in the NeroIdleCallback structure -- which forces the legacy codebase to NOT call the
IdleCallback -- avoids the exception.

Sorry for being so long-winded.

What I'm wondering is, am I handling the function pointer concept correctly here? Is it possible
that I've done something wrong so that when the callback returns, it tries to return to a place that
doesn't exist?

- Mark
 
E

Ernesto Bascón

Hi Mark:

The idea is to write an inner __nogc class inside our __gc class. The inner
class will contain the callback function (as a static method).

This article was very helpful to me:

http://www.codeproject.com/managedcpp/cbwijw.asp

Best regards


Ernesto



First off, the sympathy is for all you poor buggers out there who have to figure out how to marry
Managed Extensions for C++ onto your legacy code. My condolences; my brief experience with the
process leads me to believe the Microsoft hates all of its VC++ developers :)

Now, the question. I'm getting a NullReferenceException in a __gc class that wraps functionality in
a legacy C/C++ app (it's actually part of the Nero burning rom api).

The legacy API expects to be given a configuration structure that
contains, among other things, a
pair of function pointers which the codebase calls at various times. After much hair-pulling, I came
up with the following:

__delegate bool IdleCallbackHandler( void* pUserData );
__delegate NeroUserDlgInOut UserDialogCallbackHandler( void* pUserData, NeroUserDlgInOut type, void*
data );

[StructLayout(Layout::Sequential)]
__gc public struct NeroIdleCallback
{
public:
IdleCallbackHandler* ncCallbackFunction;
void* ncUserData;
};

[StructLayout(Layout::Sequential)]
__gc public struct NeroUserDialogCallback
{
public:
UserDialogCallbackHandler* ncCallbackFunction;
void* ncUserData;
};

[StructLayout(Layout::Sequential)]
__gc public struct NeroSettings
{
public:
const char *nstNeroFilesPath;
const char *nstVendor, *nstSoftware;
const char *nstLanguageFile;
NeroIdleCallback* nstIdle;
NeroUserDialogCallback* nstUserDialog;
bool nstEnableOverburn;
DWORD nstOverburnSize;
};

I configure this structure, and the callback substructures, in the
constructor of my wrapper class
as follows:

theSettings = new NeroSettings();
theSettings->nstNeroFilesPath = "NeroFiles";
theSettings->nstLanguageFile = "Nero.txt";

theSettings->nstEnableOverburn = false;
theSettings->nstOverburnSize = 0;

theSettings->nstIdle = new NeroIdleCallback();
theSettings->nstIdle->ncCallbackFunction = new IdleCallbackHandler(this, &Nero::FireIdleEvent);
theSettings->nstIdle->ncUserData = idleData;

theSettings->nstUserDialog = new NeroUserDialogCallback();
theSettings->nstUserDialog->ncCallbackFunction = new UserDialogCallbackHandler(this,
&Nero::FireUserDialogEvent);
theSettings->nstUserDialog->ncUserData = userdlgData;

theSettings->nstVendor = "ahead";
theSettings->nstSoftware = "Nero - Burning Rom";

Everything works fine during initialization, and in retrieving certain basic information from the
legacy codebase.

But when I call a legacy function that triggers the IdleCallback, I get the NullReferenceException:

void AvailableDrives( NERO_MEDIA_TYPE mtFlags )
{
// this next line blows up... but the IdleCallback gets called first
NERO_SCSI_DEVICE_INFOS* drives = NeroGetAvailableDrivesEx(MEDIA_CD, NULL);
...
}

The interesting part is the part mentioned in that comment: the
IdleCallback method gets called,
 
Top