acc*_*777 5 c# c++ marshalling
我目前正在开发一个 C# (.NET Framework 4.7.2) 应用程序,使用非托管 C++ 库中的一些业务逻辑。我尝试在 C# 和 C++ 之间来回传递数据(互操作)。我可能不会使用 C++/CLI,我的项目中不允许使用公共语言运行时。
对于 int 来说效果很好。不幸的是,一旦我尝试发送另一种数据类型,我就会收到转换错误,例如float 4.2f 变为 1,字符串"fourtytwo" 变为 -1529101360。
我的 C# 代码如下所示:
// works fine, creates an instance of TestClass
var test = TestProxy.Wrapper_Create("test");
// int, works fine, a = 42
var a = TestProxy.TryInt(test, 42);
// float, problem, b = 1
var b = TestProxy.TryFloat(test, 4.2f);
// string, problem, c = -159101360
var c = TestProxy.TryString(test, "fourtytwo");
Run Code Online (Sandbox Code Playgroud)
我的 C# Interop 代理类调用本机(非托管)C++ 代码如下所示:
public static class TestProxy
{
private const string coreDLL = "test.core.dll";
[DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrapper_Create(string name);
[DllImport(coreDLL, EntryPoint = "?TryInt@TestClass@@XXX@X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryInt(IntPtr instance, int n);
[DllImport(coreDLL, EntryPoint = "?TryFloat@TestClass@@XXX@X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryFloat(IntPtr instance, float n);
[DllImport(coreDLL, EntryPoint = "?TryString@TestClass@@XXX@X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryString(IntPtr instance, string n);
}
Run Code Online (Sandbox Code Playgroud)
我的本机(非托管)C++ 看起来像这样:头文件:
#ifdef TESTCORE_EXPORTS
#define TESTCORE_API __declspec(dllexport)
#endif
#pragma once
extern "C"
{
class TESTCORE_API TestClass
{
private:
char* name;
public:
TestClass(char*);
int TryInt(int);
float TryFloat(float);
char* TryString(char*);
};
TESTCORE_API TestClass* Wrapper_Create(char* name);
}
Run Code Online (Sandbox Code Playgroud)
实施文件:
#include "stdafx.h"
#include "TESTCore.h"
TestClass::TestClass(char* n)
{
name = n;
}
int TestClass::TryInt(int n)
{
return n; // works fine
}
float TestClass::TryFloat(float n)
{
return n; // something goes wrong here
}
char* TestClass::TryString(char* n)
{
return n; // something goes wrong here
}
extern "C"
{
TESTCORE_API TestClass * Wrapper_Create(char* name)
{
return new TestClass(name);
}
TESTCORE_API int TryInt(TestClass * instance, int n)
{
if (instance != NULL)
{
return instance->TryInt(n);
}
}
TESTCORE_API float TryFloat(TestClass * instance, float n)
{
if (instance != NULL)
{
return instance->TryFloat(n);
}
}
TESTCORE_API char* TryString(TestClass * instance, char* n)
{
if (instance != NULL)
{
return instance->TryString(n);
}
}
}
Run Code Online (Sandbox Code Playgroud)
您知道如何正确地将浮点数、字符串从 C# 编组到 C++ 并返回吗?
谢谢你!
C++ 没有标准 ABI。即使双方使用相同的语言,跨 DLL 使用 C++ 类也不是一个好主意。
还有更好的方法。
将类方法替换__thiscall为您喜欢的全局函数、cdecl 或 stdcall(但请注意,C# 和 C++ 有不同的默认值,如果您什么都不做,C++ 将使用 cdecl,C# 将作为 stdcall 导入)。您可以在第一个参数(C# 中的 IntPtr)中传递类的“this”指针,就像您现在所做的那样。此外,如果您要编写extern "C"或使用模块定义文件,它们将具有人类可读的名称。
如果需要对象,请使用 COM。声明一个继承自 IUnknown 的接口,用 C++ 实现它(我通常使用 ATL),并导出一个全局函数来创建该对象的实例(ATL 中的 2 行,CComObject<T>::CreateInstance后跟AddRef)。无需注册类型库,您只需要实现 IUnknown (但如果您想从多个线程使用它们,请参阅此内容)
更新:弦确实更难。适用[MarshalAs(UnmanagedType.LPTStr)]于论证。应用于[return: MarshalAs(UnmanagedType.LPTStr)]函数。PreserveSig=true在您的 DllImport.xml 中指定 最后,修改 C++ 代码以返回字符串的副本,即调用strlenthen CoTaskMemAlloc(不要忘记'\0')then strcpy。
处理字符串的更简单方法如下:
HRESULT TryString( TestClass *instance, BSTR i, BSTR *o )
Run Code Online (Sandbox Code Playgroud)
至少有CComBSTR内置_bstr_t类来处理内存管理。