如何通过引用发送字符串来修改该字符串的非托管C库?

时间:2022-04-18 07:33:27

I am new to the world of interacting with unmanaged libraries. I have an unmanaged C function that modifies a string by reference within the function. I'm having trouble passing a string from C# and getting it modified by the C function.

我是与非托管库交互的新手。我有一个非托管的C函数,它通过函数内的引用修改字符串。我在从C#传递字符串并通过C函数修改它时遇到了麻烦。

Here's the C function:

这是C函数:

__declspec(dllexport) void __stdcall Test(char* name)
{
    *name = "Bar";
}

This is the C# DLL import code:

这是C#DLL导入代码:

[DllImport(@"C:/blah/mylibrary.dll")]
public extern static string Test(string name);

This is the code I'm using to call the function:

这是我用来调用函数的代码:

string s = "foo";
Test(s);
//I want s to be "Bar" after the above line

I have tried using "ref" and "out" on the string parameter, and tried Marshalling as an LPStr. Depending on what I try, I either get an error like

我尝试在字符串参数上使用“ref”和“out”,并尝试将编组作为LPStr。根据我的尝试,我得到一个错误

"The pointer passed in as a String must not be in the bottom 64K of the process's address space."

“作为String传入的指针不能位于进程地址空间的底部64K。”

or

"Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

“试图读取或写入受保护的内存。这通常表明其他内存已损坏。”

I'm sure I'm just doing something stupid with my pointers. Can someone help me determine the appropriate C# code to get "s" to equal "bar"?

我确定我只是用我的指针做一些愚蠢的事情。有人可以帮我确定合适的C#代码,使“s”等于“bar”吗?

Thank you

1 个解决方案

#1


11  

Your C Test function doesn't do anything like you said it does. All it does it takes a local variable (name) and assigns it to a fixed string. To do what you said it does it would had to do a copy operation into the address pointed to by name:

你的C测试功能没有像你说的那样做。它只需要一个局部变量(名称)并将其分配给一个固定的字符串。要做你所说的,它必须在名称指向的地址中执行复制操作:

__declspec(dllexport) void __stdcall Test(char* name)
{
    strcpy(name, "Bar");
}

Of course, such an operation is a disaster in waiting since you have incorrect function signature (buffer lengths are not specified).

当然,由于您的函数签名不正确(未指定缓冲区长度),因此这样的操作是等待的灾难。

Considering that the C function is as above, then you should follow the rules specified at Default Marshaling for Strings:

考虑到C函数如上所述,那么您应该遵循Default Marshaling for Strings中指定的规则:

In some circumstances, a fixed-length character buffer must be passed into unmanaged code to be manipulated. Simply passing a string does not work in this case because the callee cannot modify the contents of the passed buffer. Even if the string is passed by reference, there is no way to initialize the buffer to a given size.

在某些情况下,必须将固定长度的字符缓冲区传递给非托管代码以进行操作。在这种情况下,简单地传递字符串不起作用,因为被调用者无法修改传递的缓冲区的内容。即使字符串是通过引用传递的,也无法将缓冲区初始化为给定大小。

The solution is to pass a StringBuilder buffer as the argument instead of a string. A StringBuilder can be dereferenced and modified by the callee, provided it does not exceed the capacity of the StringBuilder. It can also be initialized to a fixed length. For example, if you initialize a StringBuilder buffer to a capacity of N, the marshaler provides a buffer of size (N+1) characters. The +1 accounts for the fact that the unmanaged string has a null terminator while StringBuilder does not.

解决方案是将StringBuilder缓冲区作为参数而不是字符串传递。 StringBuilder可以被被调用者解除引用和修改,前提是它不超过StringBuilder的容量。它也可以初始化为固定长度。例如,如果将StringBuilder缓冲区初始化为N的容量,则封送程序会提供大小为(N + 1)个字符的缓冲区。 +1表示非托管字符串具有空终止符而StringBuilder不具有空终止符。

So your DLL should be like this:

所以你的DLL应该是这样的:

[DllImport(@"C:/blah/mylibrary.dll")]
public extern static string Test(StringBuilder name);

and call it by passing a properly sized StringBuilder:

并通过传递适当大小的StringBuilder来调用它:

StringBuilder foo = new StringBuilder(256);
Test(foo);

Some sanity would be added to the C interface if you add a length parameter.

如果添加长度参数,则会在C接口中添加一些完整性。

#1


11  

Your C Test function doesn't do anything like you said it does. All it does it takes a local variable (name) and assigns it to a fixed string. To do what you said it does it would had to do a copy operation into the address pointed to by name:

你的C测试功能没有像你说的那样做。它只需要一个局部变量(名称)并将其分配给一个固定的字符串。要做你所说的,它必须在名称指向的地址中执行复制操作:

__declspec(dllexport) void __stdcall Test(char* name)
{
    strcpy(name, "Bar");
}

Of course, such an operation is a disaster in waiting since you have incorrect function signature (buffer lengths are not specified).

当然,由于您的函数签名不正确(未指定缓冲区长度),因此这样的操作是等待的灾难。

Considering that the C function is as above, then you should follow the rules specified at Default Marshaling for Strings:

考虑到C函数如上所述,那么您应该遵循Default Marshaling for Strings中指定的规则:

In some circumstances, a fixed-length character buffer must be passed into unmanaged code to be manipulated. Simply passing a string does not work in this case because the callee cannot modify the contents of the passed buffer. Even if the string is passed by reference, there is no way to initialize the buffer to a given size.

在某些情况下,必须将固定长度的字符缓冲区传递给非托管代码以进行操作。在这种情况下,简单地传递字符串不起作用,因为被调用者无法修改传递的缓冲区的内容。即使字符串是通过引用传递的,也无法将缓冲区初始化为给定大小。

The solution is to pass a StringBuilder buffer as the argument instead of a string. A StringBuilder can be dereferenced and modified by the callee, provided it does not exceed the capacity of the StringBuilder. It can also be initialized to a fixed length. For example, if you initialize a StringBuilder buffer to a capacity of N, the marshaler provides a buffer of size (N+1) characters. The +1 accounts for the fact that the unmanaged string has a null terminator while StringBuilder does not.

解决方案是将StringBuilder缓冲区作为参数而不是字符串传递。 StringBuilder可以被被调用者解除引用和修改,前提是它不超过StringBuilder的容量。它也可以初始化为固定长度。例如,如果将StringBuilder缓冲区初始化为N的容量,则封送程序会提供大小为(N + 1)个字符的缓冲区。 +1表示非托管字符串具有空终止符而StringBuilder不具有空终止符。

So your DLL should be like this:

所以你的DLL应该是这样的:

[DllImport(@"C:/blah/mylibrary.dll")]
public extern static string Test(StringBuilder name);

and call it by passing a properly sized StringBuilder:

并通过传递适当大小的StringBuilder来调用它:

StringBuilder foo = new StringBuilder(256);
Test(foo);

Some sanity would be added to the C interface if you add a length parameter.

如果添加长度参数,则会在C接口中添加一些完整性。