views:

649

answers:

5

Hi, I am working on c# application(Using win 32 dll in my apllication)...I am trying something like this In DLL(test.dll):

char* Connect(TCHAR* lpPostData)
{
char buffer[1000];
.....
return buffer;
}

IN c# application:

[DllImport("test.dll", EntryPoint = "Connect", CharSet = CharSet.Unicode)]
 [return: MarshalAs(UnmanagedType.LPWStr)]
 public static extern string Connect(StringBuilder postdata);

string returnedData = Connect(postdata);

But returning of data is not happening properly.... Pls can any body tell where i am going wrong Thanks in advance

A: 

The return type of your function should be IntPtr and then use Marshal.PtrToStringAnsi to get the string out of your IntPtr.

Tony
Thank u very much for reply ...but i dont find Marshal.PtrToStringAnsi() in using System.Runtime.InteropServices but only Marshal.PtrToStringUni and Marshal.PtrToStringBstr
Santhosh
Should be there... look here: http://msdn.microsoft.com/en-us/library/7b620dhe.aspx
Tony
+2  A: 

TCHAR * in tells me it's a unicode input (UNICODE is defined under CE), but the char * tells me it's likely a byte array (or ascii string) and whoever created this API should be slapped for mixing the two, as it's really, really poor design.

You certainly can't marshal the return as a wide character string, because it isn't one. On the desktop you'd use Tony's suggestion, but MSDN (and practice) clearly shows that it's not available in the CF (no idea why MS thought we wouldn't need it).

The Smart Device Framework does have it. The other option is to use Marshal to copy from the returned pointer to a byte array and then use Encoding.ASCII to turn that array into a string. Of course that points out the other obvious flaw in this API that it shouldn't be returning a string in the first place.

EDIT 1

Since I'm seeing other suggestions on what you should do that I don't really agree with, I suppose I should give you an example:

Your native call should look more like this:

extern "C" 
__declspec(dllexport) 
const BOOL __cdecl Connect(TCHAR* lpPostData, 
                         TCHAR *returnBuffer, 
                         DWORD *returnSize) 
{ 
  // validate returnSize, returnBuffer, etc
  // write your data into returnBuffer

  TCHAR *data = _T("this is my data");

  _tcscpy(returnBuffer, data);
  *returnSize = (_tcslen(data) + 1) * sizeof(TCHAR);

  return succeeded;
} 

Note that I'm simply returning a success code. The text data is passed in as a pointer, along with a length for it (so the API knows how much space it can use and can return how much it did use). Also not that I'm consistent with my string variable data types, and I'm using the TCHAR macros which will become wchar_t under CE, which is consistent with the rest of the OS (which has almost no ASCII APIs to begin with).

Most of the WIn32 API set works this exact same way.

Your P/Invoke declaration, then is very simple:

[DllImport("test.dll", SetLastError=true)] 
private static extern bool Connect(string postData, 
                                   StringBuilder data, 
                                   ref int length);

Using it is also straightforward:

void Foo()
{
  int length = 260;
  StringBuilder sb = new StringBuilder(length);
  if(Connect("Bar", sb, ref length))
  {
    // do something useful
  }
} 

Note that the StringBuilder has to get initialized to some size, and that size is what you pass in fore the third parameter.

ctacke
+2  A: 

You are trying to return a variable on the stack; this is not going to work. You could declare buffer to be static just as a proof of concept to get the marshaling working, though.

Luke
A: 

Here's an example of how you might do what Tony has suggested:

[DllImport("test.dll", EntryPoint="my_test_func",
           CharSet=CharSet.Ansi, ExactSpelling=true,
           CallingConvention=CallingConvention.Cdecl)]
private static extern IntPtr my_test_func_imported();

public static string my_test_func()
{
    return Marshal.PtrToStringAnsi(my_test_func_imported());
}

On the unmanaged side:

const char *my_test_func(void);

(this is importing a C function from "test.dll")

P.S. As pointed out by ctacke - this is likely unsuitable for .NET CF.

romkyns
PtrToStringAnsi doesn't exist in the CF and you've way over-decorated the DllImport.
ctacke
romkyns
I'm not even sure all of those decorations are supported in the CF.
ctacke
@ctacke: well as you've already said, the biggest problem CF-wise is `PtrToStringAnsi` :) I've edited to mention CF explicitly.
romkyns
+1  A: 

There are several flaws as pointed out in the various replies you've got. To summarize it all, here is what I'd suggest.

On the native side, declare something like this (assuming you are coding in C++):

extern "C"
__declspec(dllexport)
const char* __cdecl Connect(const char* lpPostData)
{
    static char buffer[1000];
    ...
    return buffer;
}

The extern "C" makes it clear to the compiler that it should not mangle the function name when it gets exported, but should treat it just as it would treat a C function.

The __ceclspec(dllexport) makes the function visible in the DLL as an exported entry point. You can then reference it from outside projects.

The __cdecl ensures that you use the C way of passing arguments on the stack. You could badly mess things up if the caller does not assume the same argument passing scheme.

Use char consistently: either sick to char (ANSI text) or wchar_t (16-bit Unicode text). And never, ever, return a pointer to a local variable, as you did in your example. As soon as the function returns, that memory location won't be valid any more and could contain garbage. Furthermore, if the user is modifying the data stored there, it can cause the program to crash, because this would trash the call stack.

On the managed C# side, here is what I recommend:

[DllImport("test.dll", EntryPoint="Connect",
           CharSet=CharSet.Ansi, ExactSpelling=true,
           CallingConvention=CallingConvention.Cdecl)]
private static extern System.IntPtr Connect(string postData);

which will bind to your Connect entry point, use the ANSI string convention (8-bit per character, which is what char expects) and make sure the caller is expecting the __cdecl calling convetion.

You'll have to marshal the IntPtr to a string object by calling:

string value = Marshal.PtrToStringAnsi (Connect (postData));

Note: in my original post, I recommended declaring Connect as returning string, but as commenter Mattias corrected, this is not the proper way of doing it. See this article on CLR Inside Out to understand how the CLR is treating retvals of type string. Thanks for pointing this out to me (and thanks to romkyns too).

You could also consider copying the string from buffer to a piece of memory allocated through CoTaskMemAlloc in the native code, and returning a pointer to it. You could then simply declare the DllImport function as returning string and would not need to do any further marshalling in the managed world.

Pierre
I agree with you, except with your recommendation at the end. If you declare the return type as string, the CLR will try to free the returned memory using CoTaskMemFree. That is clearly not apporopriate if the returned buffer is static. Declare the return type as IntPtr instead and retrieve the string with Marshal.PtrToStringAnsi().
Mattias S
Thank you very much for educating me about this issue. I'll have to double check all my interop code to make sure it is misusing the `string` return type.
Pierre