指向非托管代码的整形指针

4

我是一个新手,对于C#还不太熟悉,有一个简单(?)的问题。我从非托管代码中收到一个int指针:

public foo(ref IntPtr state)
{
   _myState = state;
}

_myState是该类的IntPtr成员。现在,我想通过_myState与非托管C++代码交换状态。如果我写下以下内容,一切都能正常工作:

public foo(ref IntPtr state)
{
   _myState = state;
   ....do some stuff
   state = 7;
}

在未经管理的应用程序中,我可以看到新值7。但如果我写成这样:
public foo(ref IntPtr state)
{
   _myState = state;
   ...do some stuff
   _myState = 7;
}

那么什么都不会发生。state的初始值为0,当将myState更改为7时,在未经管理的应用程序中它不会被更新。 如何将成员变量_myState分配给状态参数作为“指针”,这样当状态更新时,_myState也会更新? 在C++中,使用指针就没有问题了...
好的,这是真正的代码:
[DllExport("QFX_InitializeInterfaceObject", CallingConvention = CallingConvention.StdCall)]
    public static void QFX_InitializeInterfaceObject(
        long windowHandle,
        ref IntPtr processState)
    {
        ChartWindowHandle = (IntPtr)windowHandle;
        OrderCommandProcessState = processState;
    }

我希望的是OrderCommandProcessState和它的值能够得到与processState相同的引用。


1
很明显你在这里放了伪代码(因为它不是合适的C#代码)。你能提供正确的代码并描述你想要实现什么吗?抱歉,从你的描述中无法确定。 - elder_george
1
IntPtr是一个值类型。你正在更新该值的副本 - Hans Passant
1
一个指针,IntPtr*。需要使用不安全代码。你最好使用Marshal.WriteIntPtr()。除了杀手锏行为之外,至少有一个黑暗的云,这很不可能是本地代码中的IntPtr。 - Hans Passant
@Hans:在本地代码中,它是一个 C++ 整数。 - Juergen
2
@Ramhound:如果整数在不同的架构上大小不同怎么办? - Eric Lippert
显示剩余3条评论
1个回答

12

首先,我希望确保这一点很清楚:IntPtr只是一个整数,在该机器体系结构上恰好与本机指针大小相同 - 例如,在x64系统上是一个64位整数。它不一定具有指针的语义,尽管当然常见的是,Interop代码将指针插入IntPtr中以安全地进行Marshalling。

现在来看您的具体问题,暂且忽略它是一个IntPtr。假装它只是一个int,因为基本上就是这样:

public void Foo(ref int x) // x is an alias to another variable of type int.
{
    int y = x; // y is a copy of the contents of x
    y = 7; // The contents of y are no longer a copy of the contents of x
}
改变 y 不会以任何方式改变 x;x 是指向另一个变量的别名,而 y 短暂地拥有该变量内容的副本。它并不是指向同一变量的别名。
在C++中这个问题不是问题,可以使用指针解决。今天,在安全子集中,您只能通过将“ref”和“out”参数传递给方法来实现这一点。 “ref”参数成为所给定变量的别名。这是您直接将一个变量变成另一个变量的别名的唯一安全方式。 CLR 也支持引用本地变量。我们可以实现这样的特性,并且事实上我已经在 C# 中制作了原型。在我的原型中,您可以这样说:
public void Foo(ref int x) // x is an alias to another variable of type int.
{
    ref int y = ref x; // y is now an alias to the same variable that x aliases!
    y = 7; // changing y now changes x, and whatever x 
           // aliases, because those are all the same variable
}

但是我们还没有在C#中添加这个功能,而且暂时也没有计划这样做。如果您有一个令人信服的使用案例,我很乐意听取。 (更新:该功能已添加到C# 7中。)

(CLR还允许“ref”返回类型。但是,CLR不允许创建一个变量的别名,然后将该别名存储在字段中!该字段的生命周期可能比链接的变量更长,CLR设计人员希望避免困扰C和C ++的整个错误类。)

如果您知道该变量被固定在内存中的特定位置,则可以关闭安全系统并将指针指向该变量;然后,您会得到一个完全普通的指针,可以像在C++中一样使用它。(也就是说,如果指针ptr引用一个变量,则*ptr是该变量的别名。)

unsafe public void Foo(int* x) // *x is an alias to a variable of type int.
{
    int* y = x; // *y is now an alias to the same variable that *x aliases
    *y = 7; // changing *y now changes *x, and whatever *x 
            // aliases, because those are all the same variable
}

CLR并没有限制指针的使用方式,如果需要,你可以把它们存储在字段中。然而,如果你关闭了安全系统,那么就需要确保垃圾收集器(或者拥有该存储的内存管理器,可能不是托管内存)在指针的生命周期内不会更改别名变量的位置。除非你确实知道自己在做什么,否则不要关闭这个安全系统;这个安全系统是用来保护你的。


1
@pst,你是在误读答案还是我误读了你的评论?除非微软已经发布了Eric目前只是原型的代码,否则是不可能的,而Eric也表示他们在不久的将来没有计划这样做。 - Anthony Pegram
@Anthony Pegram 我看错了,我更容易陷入代码块中,不幸的是 :( - user166390
1
你是否认为闭包是另一种让一个变量看起来像是另一个别名的方法?显然,闭包背后的机制与ref/out不同,但对开发人员来说,它的外观是一个变量已被“捕获”,并且可以在另一个上下文中进行修改。 - LBushkin
1
@LBushkin: 我想那是对的!当然,还有其他方法来“捕获变量”的引用。例如,数组只是一个按索引排列的变量集合,可以通过引用传递。然而,似乎原帖作者有一个指向非托管内存的引用,因此尝试使用托管构造(如数组、闭包等)来解决他的问题是不可能的。 - Eric Lippert
提醒各位读者,从C# 7.0开始,C#支持ref locals12,因此现在可以编写ref int y = ref x;而不会出错。显然,“不会很快”由@EricLippert表示,在接下来的6年内可能会实现。 - Alex Essilfie
@AlexEssilfie:做出预测很难,特别是关于未来的。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接