【问题标题】:Changing the string to which an IntPtr is pointing更改 IntPtr 指向的字符串
【发布时间】:2014-02-12 11:59:54
【问题描述】:

在我的 C# 应用程序中,我有一个 IntPtr 类型的变量 lpData(从对非托管代码的调用中接收),它指向一个字符串。

我必须用另一个值替换这个字符串。

我试过了:

int RegQueryValueExW_Hooked(
        IntPtr hKey,
        string lpValueName,
        int lpReserved,
        ref Microsoft.Win32.RegistryValueKind lpType,
        IntPtr lpData,
        ref int lpcbData)
{ 
    lpData = Marshal.StringToHGlobalUni("new string"); 
    ...
}

但这似乎并不能取代实际的字符串。

有人可以为我指出正确的方向吗?

谢谢

【问题讨论】:

  • 替换什么实际的字符串?您是否尝试更改注册表中的值?字符串是不可变的,因此您只能将字符串替换为新的引用。但是,对于特定的 API 调用,您可以改用 StringBuilder - 也许这对您想要做的事情有用?
  • 在我的情况下 StringBuilder 崩溃(请参阅stackoverflow.com/questions/21722555/…),所以我尝试使用 IntPtr 代替,它不会崩溃;但后来我不知道如何替换字符串。
  • 我在这里看到你的另一篇文章:stackoverflow.com/questions/21722555/… .... 问题在于,分配缓冲区(及其大小)并提供指针的是该 API 的“调用者”。由于它不是指向指针/引用参数的指针,因此您无法将指针更改回调用者。

标签: c# interop marshalling unmanaged intptr


【解决方案1】:

当然,它不会替换字符串 - 您将获得指向调用者具有值的字符串的指针。您只是替换“局部变量”(参数)的值,这不会改变调用方的任何内容。

如果您想修改原始指针上的值(并确保您确实需要,这就是“奇怪的错误”潜伏的地方 - 很容易覆盖周围的变量,忘记空终止符等) ,可以使用Marshal.Copy,例如:

var bytes = Encoding.Unicode.GetBytes("your string\0");

Marshal.Copy(bytes, 0, lpData, bytes.Length);

再说一遍 - 这是一种非常危险的行为,您不应该这样做。您违反了参数传递等隐含的几项合同。

现在我已经回答了你的问题,让我谈谈你实际上需要这样做有多么错误(这与你关于使用 StringBuilder 的另一篇文章非常相关)。

您正在尝试修改作为参数传递给您的值。但是,该字符串是由调用者分配的。你甚至不知道它有多长!如果您开始将数据复制到该指针,您将覆盖例如的数据。完全不同的变量,只是随机恰好在字符串之后分配。这被认为是“非常糟糕”。

相反,您想要做的是遵循 RegQueryValueEx 拥有的正确流程 (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724911(v=vs.85).aspx)。这意味着您首先必须检查 lpcbData 值。如果它大到足以容纳您要写入的所有字节,您只需将数据写入 lpData 并将 lpcbData 值设置为适当的长度。如果没有,您仍然设置 lpcbData,但返回 ERROR_MORE_DATA。然后调用者应再次调用 RegQueryValueEx,并使用更大的缓冲区。

示例代码如下所示:

string yourString = "Your string";

int RegQueryValueExW_Hooked(
        IntPtr hKey,
        string lpValueName,
        int lpReserved,
        ref Microsoft.Win32.RegistryValueKind lpType,
        IntPtr lpData,
        ref int lpcbData)
{ 
    var byteCount = Encoding.Unicode.GetByteCount(yourString);

    if (byteCount > lpcbData)
    {
        lpcbData = byteCount;

        return ERROR_MORE_DATA;
    }

    if (lpData == IntPtr.Zero)
    {
        return ERROR_SUCCESS;
    }

    lpcbData = byteCount;

    var bytes = Encoding.Unicode.GetBytes(yourString);   
    Marshal.Copy(bytes, 0, lpData, bytes.Length);

    return ERROR_SUCCESS;
}

请注意,这只是我在快速浏览文档后所写的内容 - 您应该进一步调查,并确保您处理了所有可能的情况。在这种情况下,.NET 无法保护您,您可能造成重大问题!

【讨论】:

  • @L-Three 您不能像打印字符串一样打印出字节数组。您必须使用Encoding.GetString() 来转换它。
  • 谢谢!!!!现在我更好地理解了它应该如何完成。您的实现似乎有效,但还有一件奇怪的事情:如果调用者获取注册表值(已被覆盖),它将以字节数组的格式获取。如果我执行 Encoding.Default.GetString(registryvalue),那么我会得到“y o u r s t r i n g”。你知道如何把它变成一个字符串而不是一个字节数组吗?
  • @L-Three 我已经用 unicode 编码了字符串,所以为了得到正确的字符串,你也必须用 unicode 解码,例如。 Encoding.Unicode.GetString(registryvalue)。在任何情况下,您都应该使用lpType 参数报告正确的编码,并在调用方读取它。
  • 是的,但问题是我无法更改原始应用程序。它读取注册表值并需要一个字符串;不是字节数组:(
  • @L-Three 它当然不需要字符串。内存中没有字符串这样的东西——它只是一堆有人决定赋予特殊含义的字节。 RegQueryValueExW 应该输出一个 unicode 字符串,这就是名称中的 W 的意思(宽)。如果你想要一个 ANSI 字符串(在大多数情况下是 Encoding.Default),你必须调用(并挂上)RegQueryValueExA。然后你将使用Encoding.Default而不是Encoding.Unicode对字符串进行编码,你就完成了:)
猜你喜欢
  • 2012-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-18
  • 1970-01-01
  • 2013-04-26
  • 1970-01-01
相关资源
最近更新 更多