在c#Application中使用Win32 dll(将char *返回到c#问题)

时间:2010-02-01 11:21:41

标签: c# windows windows-mobile winapi

我正在研究c#应用程序(在我的apllication中使用win 32 dll)......我正在尝试这样的事情 在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);

但是数据的返回没有正确发生...... 请任何人都可以告诉我哪里出错了 提前致谢

5 个答案:

答案 0 :(得分:2)

TCHAR * in告诉我它是一个unicode输入(UNICODE是在CE下定义的),但是char *告诉我它可能是一个字节数组(或ascii字符串),并且创建这个API的人应该被打耳机混合两者,如这是非常非常糟糕的设计。

你当然不能将返回编组为宽字符串,因为它不是一个。在桌面上你会使用Tony的建议,但MSDN(和练习)clearly shows它在CF中不可用(不知道为什么MS认为我们不需要它)。

智能设备框架does have it。另一种选择是使用Marshal从返回的指针复制到字节数组,然后使用Encoding.ASCII将该数组转换为字符串。当然,这首先指出了这个API中的另一个显而易见的缺陷shouldn't be returning a string

编辑1

由于我看到了你应该做的其他建议,我并不是真的同意,我想我应该给你一个例子:

您的原生电话应该更像这样:

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;
} 

请注意,我只是返回成功代码。文本数据作为指针传入,并带有长度(因此API知道它可以使用多少空间并且可以返回它使用了多少空间)。同样不是我与我的字符串变量数据类型一致,而且我正在使用TCHAR宏,它将成为CE下的wchar_t,这与OS的其余部分(几乎没有ASCII API开始)一致。

大多数WIn32 API集都以完全相同的方式工作。

你的P / Invoke声明,非常简单:

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

使用它也很简单:

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

请注意,StringBuilder必须初始化为某个大小,并且该大小是您在第三个参数之前传递的大小。

答案 1 :(得分:2)

您正在尝试在堆栈上返回变量;这不会起作用。但是,您可以将缓冲区声明为静态,这只是为了使编组工作正常的概念证明。

答案 2 :(得分:1)

在您收到的各种回复中指出了几个缺陷。总而言之,这就是我的建议。

在本机方面,声明类似这样的内容(假设您使用C ++编写代码):

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

extern "C"使编译器清楚地知道它在导出时不应该破坏函数名称,而应该像处理C函数一样处理它。

__ceclspec(dllexport)使函数在DLL中可见,作为导出的入口点。然后,您可以从外部项目中引用它。

__cdecl确保您使用C方式在堆栈上传递参数。如果调用者没有采用相同的参数传递方案,那么你可能会把事情搞得一团糟。

始终使用char:生病到char(ANSI文本)或wchar_t(16位Unicode文本)。永远不会像在您的示例中那样返回指向局部变量的指针。一旦函数返回,该内存位置将不再有效并且可能包含垃圾。此外,如果用户正在修改存储在那里的数据,则可能导致程序崩溃,因为这会破坏调用堆栈。

在托管C#方面,我推荐以下内容:

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

将绑定到您的Connect入口点,使用ANSI字符串约定(每个字符8位,这是char期望的)并确保调用者期望{{1}呼叫对话。

您必须通过调用以下内容将__cdecl封送到IntPtr对象:

string

注意:在我的原始帖子中,我建议将string value = Marshal.PtrToStringAnsi (Connect (postData)); 声明为返回Connect,但作为评论者Mattias更正,这不是正确的方法。请参阅CLR Inside Out上的这篇文章,了解CLR如何处理string类型的retval。感谢您向我指出(并感谢romkyns)。

您还可以考虑将字符串从string复制到本机代码中通过buffer分配的内存块,并返回指向它的指针。然后,您可以简单地将CoTaskMemAlloc函数声明为返回DllImport,并且不需要在托管环境中进行任何进一步的编组。

答案 3 :(得分:0)

您的函数的返回类型应为IntPtr,然后使用Marshal.PtrToStringAnsiIntPtr中获取字符串。

答案 4 :(得分:0)

这是一个如何做Tony所建议的例子:

[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());
}

在非管理方面:

const char *my_test_func(void);

(这是从“test.dll”导入C函数)

P.S。正如ctacke所指出的 - 这可能不适合.NET CF。