从 dll 返回多个字符串

Posted

技术标签:

【中文标题】从 dll 返回多个字符串【英文标题】:Return multiple strings from dll 【发布时间】:2017-06-07 08:35:17 【问题描述】:

我们正在讨论什么是从一个 dll 函数返回多个字符串的好方法。目前我们有 8 个字符串,但还会有更多。为简单起见,我现在认为所有字符串的长度都相同。

extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);

在哪里

struct TestResults

    int stringLengths;
    char* string1;
    char* string2;
    char* string3;
    char* string4;
    ...
;

或第二个选项:哪里

struct TestResults

    int stringLengths;
    char string1[64];
    char string2[64];
    char string3[64];
    char string4[64];
    ...
;

第三个选项: extern "C" int DLLNAME_ _stdcall GetResult(int stringLengths, char* string1, char* string2, char* string3, ...);

dll 将通过串行线路进行通信并检索将填充到字符串中的信息。需要分配内存的地方有待讨论,可以作为答案的一部分。

背景是我们有一个喜欢第二种方法的 VB6 应用程序团队和一个喜欢第一种方法的 C++/C# 团队。最后一种方法看起来适合两个团队,但对我来说有这么多参数看起来有点奇怪。

也许还有更多选择。 Windows下的常见做法是什么?来自 Windows API 的任何示例或用于选择其中一个的参数?

编辑:字符串的含义与名字、姓氏、电子邮件一样。我们目前有八个,但将来我们可能会添加一对,例如地址。数组不是正确的选择,但从原始上下文中并不清楚。

【问题讨论】:

使用char*,内存分配/释放是如何管理的?还将BSTR 视为一种跨语言类型(具有定义的内存管理)。 我更新了问题以回答您的评论。我们没有严格的规则来说明分配需要在哪里进行。这可能是我们最终不知道什么是好的/常见的想法的部分原因。 如果您正在与 VB 6 进行互操作,您绝对应该使用BSTR。这就是 VB 6 使用的字符串类型。在 .NET 应用程序中也能正常工作,因为它是标准 COM 类型。 或者您可以以REG_MULTI_SZ 的形式返回它 - 以空字符结尾的字符串序列,以空字符串 (\0) 结尾。下面是一个例子:String1\0String2\0String3\0LastString\0\0 【参考方案1】:

最好的方法可能是使用一个安全数组来存储BSTR 字符串。

VB 和 C# 都非常了解安全数组:在 C# 中,BSTR 字符串的安全数组会自动转换为string[] 数组。

在 C++ 方面,您可以使用 ATL::CComSafeArray 帮助类来简化安全数组编程。

您会在this MSDN Magazine article 中找到有趣的材料(特别是,请查看段落生产一个安全的字符串数组)。


从上述文章中:在 C++ 端,您可以实现一个 C 接口 DLL,导出如下函数:

extern "C" HRESULT MyDllGetStrings(/* [out] */ SAFEARRAY** ppsa)

  try   
    // Create a SAFEARRAY containing 'count' BSTR strings
    CComSafeArray<BSTR> sa(count);

    for (LONG i = 0; i < count; i++) 
      // Use ATL::CComBSTR to safely wrap BSTR strings in C++
      CComBSTR bstr = /* your string, may build from std::wstring or CString */ ;

      // Move the the BSTR string into the safe array
      HRESULT hr = sa.SetAt(i, bstr.Detach(), FALSE);

      if (FAILED(hr)) 
        // Error...
        return hr;
      
    

    // Return ("move") the safe array to the caller
    // as an output parameter (SAFEARRAY **ppsa)
    *ppsa = sa.Detach();

   catch (const CAtlException& e) 
    // Convert ATL exceptions to HRESULTs
    return e;
  

  // All right
  return S_OK;

在 C# 端,您可以使用此 PInvoke 声明:

[DllImport("MyDll.dll", PreserveSig = false)]
public static extern void MyDllGetStrings(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
  out string[] result);

【讨论】:

【参考方案2】:

当您将函数声明为 extern "C" 时,我想您不能使用 std::vector&lt;std::string&gt; 作为返回类型。

另一种可能是:

struct String

   int         size; /* size of string */
   const char* str;  /* actual string  */

struct TestResults

   int     size; /* number of strings             */
   String* arr;  /* pointer to an array of String */
;

然后和之前一样:

extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);

这样,您可以灵活地返回任意数量的字符串。还可以轻松循环您的TestResults

编辑 #1: 如 cmets 所述:使用 BSTR。所以你的结构看起来像:

struct TestResults

   int   size; /* number of strings           */
   BSTR* arr;  /* pointer to an array of BSTR */
;

BSTR 将通过:BSTR MyBstr = SysAllocString(L"I am a happy BSTR"); 变为 allocated。此分配还设置包含字符串长度的成员。您必须使用以下命令释放分配的内存:SysFreeString(MyBstr);。您还需要分配整个数组BSTR*

【讨论】:

比较你的String类型和MS-Windows类型BSTR(注意BSTR已经定义了跨语言内存管理) 无论如何,您都不应该将 STL 类型用作 DLL 中的返回值。它们不能在不同版本的运行时库中互换使用。 我强烈建议提供一个单独的 freeResult 函数。无论需要释放多少个不同的数组,这都使用户可以轻松地释放。这样,也不再需要 BSTR,我们可以再次回到简单的 C 字符串。我们甚至可以将结构作为指针返回(错误时为 NULL)...这两个函数将等效于 Sys(Alloc|Free)String 对... ...而且不用担心如何字符串是如何分配的,我们甚至可以使用操作符new[]和delete[]。 这很好用,@Aconcagua,当您想使用任何需要内存管理的东西(包括 STL 类型)时,这是推荐的模式。它也是一个真正的“通用”API,因为它适用于所有语言,从汇编和 C 到 Python 和 Haskell。不幸的是,它给程序员带来了更多的负担,这在通常不需要手动内存管理的语言中是一个特别的缺点,程序员没有纪律。 BSTR 的优点是它是一种 COM 类型,在 Windows 中无处不在,并在 VB 和 .NET 中自动管理。方便,错误少。

以上是关于从 dll 返回多个字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Ruby 中使用 Win32API 从 DLL 返回字符串

在不同的返回类型上将字符串从 C# 传递到 C++ DLL 不同的文本编码

调用从 c# 返回 char* 的 c++ dll 函数。无法使用 DllImport()

DllImport 返回空终止字符串 AccessViolationException

C#编组来自C++ DLL的无符号字符返回值[重复]

C#从返回char指针的c++函数中获取字符串/字符值