【问题标题】:Using Win32 dll in c# Application(returning char* to c# problem)在 c# 应用程序中使用 Win32 dll(将 char* 返回到 c# 问题)
【发布时间】:2011-01-11 16:28:23
【问题描述】:

我正在开发 c# 应用程序(在我的应用程序中使用 win 32 dll)...我正在尝试这样的事情 在 DLL(test.dll) 中:

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

在 c# 应用程序中:

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

string returnedData = Connect(postdata);

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

【问题讨论】:

    标签: c# windows windows-mobile winapi


    【解决方案1】:

    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,这与操作系统的其余部分一致(几乎没有 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 必须初始化为某个大小,并且该大小是您在第三个参数之前传递的大小。

    【讨论】:

      【解决方案2】:

      您正试图在堆栈上返回一个变量;这是行不通的。不过,您可以将缓冲区声明为静态的,作为让编组工作正常工作的概念证明。

      【讨论】:

        【解决方案3】:

        正如您在收到的各种回复中指出的那样,存在几个缺陷。总而言之,这是我的建议。

        在本机端,声明如下内容(假设您使用 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 所期望的)并确保调用者期待 __cdecl 调用约定。

        您必须通过调用将IntPtr 编组为string 对象:

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

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

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

        【讨论】:

        • 我同意你的看法,除了最后你的建议。如果将返回类型声明为字符串,CLR 将尝试使用 CoTaskMemFree 释放返回的内存。如果返回的缓冲区是静态的,这显然是不合适的。将返回类型声明为 IntPtr 并使用 Marshal.PtrToStringAnsi() 检索字符串。
        • 非常感谢您对我进行了有关此问题的教育。我必须仔细检查我所有的互操作代码,以确保它滥用了 string 返回类型。
        【解决方案4】:

        你的函数的返回类型应该是IntPtr,然后使用Marshal.PtrToStringAnsi从你的IntPtr中取出字符串。

        【讨论】:

        • 非常感谢您的回复...但我在使用 System.Runtime.InteropServices 时找不到 Marshal.PtrToStringAnsi() 但只有 Marshal.PtrToStringUni 和 Marshal.PtrToStringBstr
        • 应该在那里...看这里:msdn.microsoft.com/en-us/library/7b620dhe.aspx
        【解决方案5】:

        下面是一个例子,说明你可以如何按照 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函数)

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

        【讨论】:

        • PtrToStringAnsi 在 CF 中不存在,并且您过度装饰了 DllImport。
        • @ctacke:说明为什么我认为标签不是问题的一部分——如果是 CF,那么问题最好提一下。至于 DllImport 装饰 - 我宁愿明确;除此之外,这是从自动生成的文件中复制和粘贴的。
        • 我什至不确定 CF 是否支持所有这些装饰。
        • @ctacke:正如你已经说过的,CF 方面最大的问题是PtrToStringAnsi :) 我已经编辑明确提到了 CF。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-11
        相关资源
        最近更新 更多