我正在尝试编写一些从非托管DLL调用方法的C#代码.dll中函数的原型是:
extern "C" __declspec(dllexport) char *foo(void);
Run Code Online (Sandbox Code Playgroud)
在C#中,我首先使用:
[DllImport(_dllLocation)]
public static extern string foo();
Run Code Online (Sandbox Code Playgroud)
它似乎在表面上工作,但我在运行时遇到内存损坏错误.我想我指的是记忆恰好是正确的,但已经被释放了.
我尝试使用名为"P/Invoke Interop Assistant"的PInvoke代码生成实用程序.它给了我输出:
[System.Runtime.InteropServices.DLLImportAttribute(_dllLocation, EntryPoint = "foo")]
public static extern System.IntPtr foo();
Run Code Online (Sandbox Code Playgroud)
它是否正确?如果是这样,我如何将此IntPtr转换为C#中的字符串?
Jar*_*Par 75
您必须将此作为IntPtr返回.从PInvoke函数返回System.String类型需要非常小心.CLR必须将内存从本机表示转移到托管表示.这是一种简单且可预测的操作.
但问题是如何处理从foo()返回的本机内存.CLR假定以下两个关于PInvoke函数的项目直接返回字符串类型
因此,它将封送字符串,然后在本机内存blob上调用CoTaskMemFree.除非您实际使用CoTaskMemAlloc分配此内存,否则这最多会导致应用程序崩溃.
为了在这里获得正确的语义,您必须直接返回IntPtr.然后使用Marshal.PtrToString*以获取托管String值.您可能仍需要释放本机内存,但这取决于foo的实现.
Str*_*lok 23
您可以使用Marshal.PtrToStringAuto方法.
IntPtr ptr = foo();
string str = Marshal.PtrToStringAuto(ptr);
Run Code Online (Sandbox Code Playgroud)
目前的答案并不完整,所以我只想将完整的解决方案发布在一处。返回 IntPtr 而不是 string 根本不能解决任何问题,因为您仍然必须释放 C 脚本中分配的本机内存。最好的解决方案是在托管端分配字节缓冲区,并将内存传递给 C 脚本,该脚本将字符串写入该缓冲区,根本不分配内存。
从 C 返回一个字符串就像这样:
//extern "C" __declspec(dllexport) uint32_t foo(/*[out]*/ char* lpBuffer, /*[in]*/ uint32_t uSize)
uint32_t __stdcall foo(/*[out]*/ char* lpBuffer, /*[in]*/ uint32_t uSize)
{
const char szReturnString[] = "Hello World";
const uint32_t uiStringLength = strlen(szReturnString);
if (uSize >= (uiStringLength + 1))
{
strcpy(lpBuffer, szReturnString);
// Return the number of characters copied.
return uiStringLength;
}
else
{
// Return the required size
// (including the terminating NULL character).
return uiStringLength + 1;
}
}
Run Code Online (Sandbox Code Playgroud)
C#代码:
[DllImport(_dllLocation, CallingConvention=CallingConvention.StdCall, CharSet=CharSet.Ansi)]
private static extern uint foo(IntPtr lpBuffer, uint uiSize);
private static string foo()
{
// First allocate a buffer of 1 byte.
IntPtr lpBuffer = Marshal.AllocHGlobal(1);
// Call the API. If the size of the buffer
// is insufficient, the return value in
// uiRequiredSize will indicate the required
// size.
uint uiRequiredSize = foo(lpBuffer, 1);
if (uiRequiredSize > 1)
{
// The buffer pointed to by lpBuffer needs to be of a
// greater size than the current capacity.
// This required size is the returned value in "uiRequiredSize"
// (including the terminating NULL character).
lpBuffer = Marshal.ReAllocHGlobal(lpBuffer, (IntPtr)uiRequiredSize);
// Call the API again.
foo(lpBuffer, uiRequiredSize);
}
// Convert the characters inside the buffer
// into a managed string.
string str = Marshal.PtrToStringAnsi(lpBuffer);
// Free the buffer.
Marshal.FreeHGlobal(lpBuffer);
lpBuffer = IntPtr.Zero;
// Display the string.
Console.WriteLine("GetString return string : [" + str + "]");
return str;
}
Run Code Online (Sandbox Code Playgroud)
使用 StringBuilder 在 C# 端管理内存分配/释放有更简单的方法:
[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
private static extern uint foo(StringBuilder lpBuffer, UInt32 uiSize);
private static string foo()
{
StringBuilder sbBuffer = new StringBuilder(1);
uint uiRequiredSize = foo(sbBuffer, (uint)sbBuffer.Capacity);
if (uiRequiredSize > sbBuffer.Capacity)
{
// sbBuffer needs to be of a greater size than current capacity.
// This required size is the returned value in "uiRequiredSize"
// (including the terminating NULL character).
sbBuffer.Capacity = (int)uiRequiredSize;
// Call the API again.
foo(sbBuffer, (uint)sbBuffer.Capacity);
}
return sbBuffer.ToString();
}
Run Code Online (Sandbox Code Playgroud)
有一个很好的主题解释了从 C/C++ 代码返回字符串的不同方法。
| 归档时间: |
|
| 查看次数: |
19013 次 |
| 最近记录: |