【问题标题】:What is the correct way to pass arguments to a C++ Dll from C# and back?从 C# 将参数传递给 C++ Dll 并返回的正确方法是什么?
【发布时间】:2014-11-21 23:00:29
【问题描述】:

我正在开发一个 C++ DLL,它应该能够接收一些参数并将其传递回 C# 应用程序。

我能够做到这一点,效果很好。至少我是这么认为的。该代码在我的电脑上运行良好,但在我同事的电脑上却不行。在他的 PC 上,相同的代码(在我的 PC 上可以正常工作)产生不同的输出,或者产生错误。所以基本上它的行为很奇怪。

C++ 函数:

extern "C"
{
   __declspec( dllexport ) BOOL __stdcall MyFunction( char * StringIn, char *StringOut, BOOL bState );
}

我在 C# 中这样使用它:

    [DllImport( @"PathToMyDll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall )]
    public static extern bool MyFunction( string StringToDLL, StringBuilder StringFromDLL, bool bState );

    static void Main(string[] args)
    {
        int bufferSize = 16384;

        string StringToDLL = "This is a sample string";
        StringBuilder StringFromDLL = new StringBuilder( bufferSize );

        Console.WriteLine( "Return value      = " + MyFunction( StringToDLL, StringFromDLL, true ).ToString() );
        Console.WriteLine( "Sent to DLL       = " + StringToDLL.ToString() );
        Console.WriteLine( "Returned from DLL = " + StringFromDLL.ToString() );

        Console.ReadLine();
    }

所以我的问题是:
难道我做错了什么?或者 Visual Studio 中是否有一些设置可能导致这种行为?另外,我应该如何在 C# 中为 StringOut 正确分配内存?有一种情况可能是 StringOut 会大于缓冲区大小,但我不知道它到底有多大?(我只会在 DLL 中知道它) 有谁知道为什么相同的代码在不同的 PC 上表现不同?

提前致谢!

【问题讨论】:

  • 是否允许将 C# 中声明的字符串直接转换为 char* 隐式?有一次,我使用 C++/CLI 为原生 C++ 类编写了包装器(遵循本教程:codeguru.com/cpp/cpp/cpp_managed/interop/article.php/c6867/…),我必须非常小心地在原生 C++ 端和 C++/CLI 托管端之间编组类型。
  • @Ian 是的,你可以。这是 P/Invoke 层的一部分。
  • 这段代码看起来没什么问题。可能您应该在其他地方寻找问题。顺便说一句,您可以省略调用约定 - stdcall 是默认值。
  • @Nikolay:不,我不能。如果我省略它,代码将不起作用。我在某处读到 C# 的默认调用约定是 cdecl,而不是 stdcall。
  • 顺便说一句,“bool”也是正确的,它默认编组为 Int32,即“BOOL”。即我敢打赌,问题不在于您发布的代码。

标签: c# c++ visual-studio pinvoke


【解决方案1】:

根据问题中的信息,您的代码是正确的。问题可能出在其他地方。显而易见的地方是非托管代码。我建议你调试一下。

您的代码是等待发生的缓冲区溢出。如果被调用者不知道缓冲区的大小,它如何避免溢出缓冲区?选项包括:

  1. 将缓冲区长度传递给非托管代码。
  2. 使用 BSTR 通过分配共享 COM 堆来允许任意长的字符串。

【讨论】:

  • 另一个选项是有一个版本的 DLL 函数,它返回需要分配的缓冲区的最大大小。然后使用适当大小的缓冲区完成对该函数的后续调用。有点老套,但这是少数 API 的工作方式。
  • @Paul 这是实现选项 1 的正常方式
  • @DavidHeffernan:所以基本上你是说,我应该重写我的代码,拥有一个额外的参数来保存字符串的长度。那我应该叫它两次。第一次,它应该返回长度,第二次我应该传递正确的长度,然后才应该返回字符串?我理解正确吗?感谢您的想法,但这仍然不能解释它可以在我的 PC 上运行。我返回的字符串肯定比 buffersize 短。
  • 问题中的代码没问题。你需要做一些调试。
  • 您应该很容易编写一个简单的测试 DLL 来证明互操作性很好。然后你就可以继续调试真正的问题了。
【解决方案2】:

如果这有帮助,这就是我将基于 C++ 的 DLL 连接到 C# 的方式。

[DllImport("MyDLL.DLL", CharSet=CharSet.Ansi,ExactSpelling=true, 
           CallingConvention=CallingConvention.StdCall)]
public static extern int MyFunction 
(MarshalAs(UnmanagedType.LPTStr)]StringBuilder strinIn, 
[MarshalAs(UnmanagedType.LPStr)]StringBuilder stringOut);

所以不同的是我指定了 marshal 类型。

C++ DLL(导出为“C”函数)将具有以下原型:

LONG WINAPI MyFunction(const char *In, char *Out);

注意:我不是 C# 专家。这就是让 C# 应用程序调用我的 C++ DLL 函数所需要做的事情。

【讨论】:

  • 您的 MarshalAs 声明在这里毫无意义。此外,您应该使用字符串作为 in 参数。
  • 使用string 而不是StringBuilder 作为输入参数。 FWIW,问题指出本机代码是 C++。当然对于互操作,std::string 是不合适的。
猜你喜欢
  • 2021-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
  • 1970-01-01
  • 1970-01-01
  • 2011-04-26
  • 1970-01-01
相关资源
最近更新 更多