0

Could someone please help me figure out what is wrong with the following code snippet. I tried to call an old DLL and have spent about 3 days but couldn't find why my returned array and the string is always junk.

Unmanaged Codes:


typedef struct
{
    double a;
    double b;
}UnmanagedStruct, far *lpUnmagedStruct;

//.h file:
__declspec( dllexport ) double far pascal Process(lpUnmagedStruct lpStruct, int size, char far szMessage[]);

//.cpp
extern double far pascal Process(lpUnmagedStruct lpStruct, int size, char far szMessage[])
{
    //Please pay attention here. It's being seen as a pointer to an array
    lpStruct[0] = ...
    lpStruct[2] = ...
    _fstrcpy (szMsg, "Welcome");
}

And here's my codes in the .NET:


[StructLayout(LayoutKind.Sequential)]
public struct ManagedStruct 
{
    public double a;
    public double b;
}

[DllImport("MyDll.dll", EntryPoint="Process", CharSet = CharSet.Ansi)]
public static extern double Process([In, Out]ManagedStruct[] myStruct, int size, string message);

//Implementation
ManagedStruct[] arrayOfStruct = new ManagedStruct[3];
string message;

//Assign values to arrayOfStruct

//Call interop
Process(arrayOfStruct, 3, message);

TexasCoder
  • 85
  • 1
  • 9
  • How does lpStruct[0] = 1 even compile? – David Heffernan Mar 24 '11 at 20:38
  • In fact there's lots more that doesn't compile. I think you need to be more precise in the Q. – David Heffernan Mar 24 '11 at 20:42
  • The string parameter is marshalled as an IN parameter by the .net marshaller. If you want to return a string from C++ to C# you need to use StringBuilder, or BSTR. – David Heffernan Mar 24 '11 at 20:43
  • I tried to copied and made it simple. lpStruct[] = 1 is a typo. – TexasCoder Mar 24 '11 at 20:44
  • The question is how do I pass the array to the DLL: Process(arrayOfStruct, 3, message); Should I pass and array of struct to the DLL, or should I use Marshal.IntPtr? If use IntPtr, I can't access the array once returned. If I pass a struct, I guess that the unmanaged code only process the very first struct. – TexasCoder Mar 24 '11 at 20:45
  • Does the struct array part work if you stop calling the string copy function in your C++? The struct bit looks OK to me. The string bit is totally wrong and perhaps you are stomping on the struct. – David Heffernan Mar 24 '11 at 20:46
  • I think the struct bit is fine. If I were you I'd concentrate on just one aspect at once. Get that bit working and move on to the string. The string needs to be StringBuilder allocated to some agreed size. Or a BSTR if you want to be more dynamic. But get the struct bit working first. – David Heffernan Mar 24 '11 at 20:48
  • 1
    Take a look at this for the string: http://stackoverflow.com/questions/5308584/ My answer outlines the BSTR approach. Another answer lower down illustrates StringBuilder. – David Heffernan Mar 24 '11 at 20:50
  • David, I cannot test the struct alone by itself. Thanks for the link. The function accepts 2 parameters. Once I called the function, the returned values are totally wrong: both struct and message. I'll fix the string in a moment – TexasCoder Mar 24 '11 at 20:53
  • @Texas In that case use the StringBuilder approach in the question I linked to and allocate a large buffer for the string. – David Heffernan Mar 24 '11 at 20:55
  • any idea on how I should pass the struct to the unmanaged code? Should I use an array or struct? – TexasCoder Mar 24 '11 at 20:57
  • Yeah, I was being dim about the struct, that's not going to work that way. Take a look at jaredpar's answer here: http://stackoverflow.com/questions/188299/ – David Heffernan Mar 24 '11 at 21:07

1 Answers1

1

The pascal calling convention is your problem. It is ancient, the pinvoke marshaller doesn't support it. It passes arguments left-to-right, the pinvoke marshaller is assuming stdcall so passes them right-to-left. Use __stdcall instead:

__declspec( dllexport ) 
double __stdcall Process(lpUnmagedStruct lpStruct, int size, char* szMessage, int messageSize);

Next problem is the string, returning one requires you to use a StringBuilder in the declaration and pass an initialized one in the call:

[DllImport("MyDll.dll", EntryPoint="Process", CharSet = CharSet.Ansi)]
public static extern double Process(ManagedStruct[] myStruct, int size, StringBuilder message, int messageSize);

...
var buffer = new StringBuilder(666);
double retval = Process(arrayOfStruct, 3, buffer, buffer.Capacity);
var message = buffer.ToString();

I took the liberty of adding the messageSize argument, required to safely copy the string into the message buffer without risking destroying the garbage collected heap.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • @Hans will the default marshalling of `ManagedStruct[] myStruct` work? I rather thought not. I thought pascal in C++ header files now meant stdcall. Looks like I might struggle to get a BSTR out of this one?!! – David Heffernan Mar 24 '11 at 21:10
  • Yes, it's fine. Do not use *ref*, that's a UnmanagedStruct** on the C side. – Hans Passant Mar 24 '11 at 21:14
  • @Hans If you want IN_OUT then what do you do? I'll upvote now although you're obviously at the cap!! – David Heffernan Mar 24 '11 at 21:18
  • On an array? Nothing special, it is the default marshaling. – Hans Passant Mar 24 '11 at 21:27
  • @Hans, changing from FAR PASCAL to __stdcall doesn't help. The result is still the same with junk values. – TexasCoder Mar 24 '11 at 22:07
  • I dunno, should have worked. Do it slow, one argument at a time. Start with something simple like just an int. That lets you test some basic assumptions about how this is supposed to work. – Hans Passant Mar 24 '11 at 22:35
  • @Hans @Texas FAR is defined to far which is defined as nothing. It's a relic from Win16. PASCAL, another such relic is defined as __stdcall. – David Heffernan Mar 25 '11 at 07:38
  • LATEST UPDATE: Changing to StringBuilder works. Tte struct passing isn't. – TexasCoder Mar 25 '11 at 14:15