I've searched around and not found quite what I'm looking for so I'm really hoping that someone here can help me.
I need to access a DLL written in C++ from my c# desktop application. Whilst having a few years experience with c# and .NET i've absolutely no experience with C and pointers and memory allocation.
The dll is called UPTool.dll and the header file (and i presume the dll) contains this...
EXTERN int CALLTYPE CP( double * ranking, int Nobj, int Nalternatives , double * Y, double * w);
This is the function I'm trying to execute from my app. ranking, Y and w are all double vector arrays and i know their size. ranking gets written to in the function and is what i need to get back out of the function. As you can see ranking, Y and w are sent as pointers.
So i can set up a blank double[] ranking and populated double[] Y and double[] w and the two int's in the c# app no problem.
I need to call the DLL and from searching around it seems i need to use pinvoke and i got something like this - i believe the refs take care of the pointers but i'm not sure and to be honest even at this point I'm confused:
[DllImport("UPTool.dll", CharSet = CharSet.Auto)]
public static extern int CP(ref double[] ranking, ref int Nobj, ref int Nalternatives, ref double[] Y, ref double[] w);
To handle the inputs and outputs and any errors i then created another function that the application calls to run the DLL with the correct data:
public bool RunCP(int nscen, int natrib, double[] inputs, double[] weighting, out ArrayList CPRanking)
{
CPRanking = new ArrayList();
try
{
double[] results = new double[nscen];
CP(ref results, ref natrib, ref nscen, ref inputs, ref weighting);
foreach (double result in results)
{
CPRanking.Add(result);
}
return true;
}
catch
{
return false;
}
}
As you can imagine it didn't work and i receive the following error (when not using the try/catch) " AccessViolationException was unhandled - Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
Not having any clues about C or tried anything like this before I'm now completely baffled! I'm also worried that even if it worked the garbage collector might move my memory anyway.
Loading
Martyn JonesPosted May 13, 2008, 5:17 AM
Once again, sorry for messing you around. I'm extremely happy as its now working great!
AlanPosted May 12, 2008, 10:00 AM
Yes that's definitely STDCALL so you don't want to specify CDECL as the calling convention - I'd therefore remove that from the signature.
Also, it sounds like the double arrays are being initialized properly as well.
However, if the dll is exporting an Initialize() function, that does suggest that it's intended to be called by the client before using any of its other functions. I'd therefore try:
[DllImport("UPTool.dll")]
public static extern void UPTool_Initialize();
and then call that before you do anything else.
If it still doesn't work from C# but it does work when you call if from C, then you could try rewrapping the exported functions in another C dll and then see if you can P/Invoke it from that.
Martyn JonesPosted May 12, 2008, 6:42 AM
#define CALLTYPE __stdcall
so im guessing that the call type is ok. I tired your suggestion anyway and got the same error again.
I've double checked the dimensions of the various double[]'s. Yes, they are related to the two ints, result is double[nscen], w is double[natrib] and Y is double[nscen*natrib]. I've checked whats going in and it all seems to be correct.
One other question if thats ok. The DLL includes
EXTERN void CALLTYPE UPTool_Initialize();
EXTERN void CALLTYPE UPTool_Terminate();
I've been told i need to run the UPTool_Initialize() - would that likely be the case, just wondering if its something that c# takes care of automatically.
If i add:
[DllImport("UPTool.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void UPTool_Initialize();
to the class, and insert
UPTool_Initialize();
just before the CP(...) call in the try statement i still get the same error.
AlanPosted May 12, 2008, 5:58 AM
I doubt whether the problem lies with the dll which I'd leave in the application folder.
The difference between the int and double[] parameters is that the former are value types and the latter are reference types. So, the values of the ints are contained in the variables' memory slots but pointers to where the array elements are stored (elsewhere on the heap) are held in the array variables' memory slots.
In the C function, the ints are passed by value so you can do exactly the same from C#. Similarly you can obtain a double * by just passing the double arrays by value.
So, I'm reasonably sure that we're matching the parameter types correctly.
You said in your original post that you knew the sizes of the other two arrays, Y and w, and possibly the size is being passed in the other int parameter NObj. Are you sure that you're initializing these arrays to the correct size?
The only other point is CALLTYPE which I imagine must be a typedef for a calling convention. Do you know how this defined? if it's equivalent to CDECL rather than STDCALL/WINAPI then we'd need to alter the calling convention in the P/Invoke signature to avoid unbalancing the stack (the default is WINAPI):
[DllImport("UPTool.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int CP([In][Out] double[] ranking, int Nobj, int Nalternatives, double[] Y, double[] w);
Incidentally, no need for CharSet here as we're not using any strings though it does no harm if you want to leave it in.
Martyn JonesPosted May 12, 2008, 5:03 AM
Having implemented your suggestions - thanks for tidying up the Arraylist as well! - its still throwing the same error with a green arrow on this line 'CP(results, natrib, nscen, inputs, weighting);':
AccessViolationException was unhandled
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
If i remove the DLL from the c# apps folder then it throws a different error complaining that its not found - not sure if that suggests the problem is in the DLL itself - although it does work using a seperate exe written in C.
One other thing, in your text you say that the two ints (natrib and nscen) should be sent by value as they're not pointers, however i dont see any differece in how they're sent (other than the [in][out] for double[] ranking) in either...
public static extern int CP([In][Out] double[] ranking, int Nobj, int Nalternatives, double[] Y, double[] w);
CP(results, natrib, nscen, inputs, weighting);I tried adding ref before the three doubles[] in both lines but it still threw the same error.
AlanPosted May 9, 2008, 12:21 PM
P/Invoke is a bit of a black art and it can be difficult to get stuff to work first time (or even at all!) but, from what you've said:
1. The arrays don't need to be passed by reference as they're already pointers to where the elements are stored.
2. The array which receives the data should now be marshalled as an 'In/Out' parameter as the default is 'In' for arrays passed by value.
3. The two 'int' parameters should be passed by value rather than by reference as they're not pointers.
So, I'd try for your P/Invoke signature:
[DllImport("UPTool.dll", CharSet = CharSet.Auto)]
public static extern int CP([In][Out] double[] ranking, int Nobj, int Nalternatives, double[] Y, double[] w);
and call with:
public bool RunCP(int nscen, int natrib, double[] inputs, double[] weighting, out ArrayList CPRanking)
{
CPRanking = new ArrayList();
try
{
double[] results = new double[nscen];
CP(results, natrib, nscen, inputs, weighting);
CPRanking.AddRange(results);
return true;
}
catch
{
return false;
}
}