【问题标题】:How to modify content of the original variable which is passed by value?如何修改传值的原始变量的内容?
【发布时间】:2012-04-13 20:24:57
【问题描述】:

现有的 API 函数只允许插件(DLL)接收三个参数并执行一些操作:

int ProcessMe(int nCommand, unsigned int wParam, long lParam);

现在,从主程序(exe),想传递两个变量给插件,并要求插件修改它们的内容,主程序将再次读取它们,以执行一些任务。

我的问题是,从上面的函数中,我可以在不改变函数参数的情况下执行这个吗?

例子:

int ProcessMe(int nCommand, unsigned int wParam, long lParam)
{
  // modify the parameters//
  return 0;
}

int main()
{
  BOOL bSave = TRUE;
  int nOption = 0;
  ProcessMe(0, (unsigned int)(&bSave), (long)(&nOption));
  if(FALSE==bSave)
    printf("bSave is modified!");
  return 1;
}

【问题讨论】:

  • 没有。您需要通过引用传递,如果不更改 API,您将无法做到这一点。你要么必须用指针重新实现接口,要么学会在没有指针的情况下生活。
  • 遗憾的是,我不允许更改接口,因为目前有许多其他 DLL 使用类似接口与 exe 一起运行....对我来说是向后兼容性问题 :(
  • 这没有意义。该函数不会期望使用参数来存储结果。它们不是为了输出。你需要声明一个新函数。
  • 可以做到:传递给函数的参数仅按值传递,但最后一个参数实际上可以是地址(按值传递),以便 ProcessMe 可以操作该地址处的数据: 在调用程序中将指针转换为 long,反之亦然。
  • 虽然将指针作为 lParam 传递会很小心,(请参阅所有其他有关问题的帖子),您应该注意此调用的格式与 Windows SendMessage/PostMessage 类似。如果此调用是 PostMessage 包装器,则调用返回时发出的请求可能尚未完成,因此调用中引用的任何 var 可能尚未写入。

标签: c++ c api parameter-passing


【解决方案1】:

将要修改的变量放在一个结构体中,并将​​指向该结构体的指针传递给插件:

struct MyStruct
{
    BOOL bSave;
    int nOption;
};

int ProcessMe(int nCommand, unsigned int wParam, long lParam)
{
    ((MyStruct*)lParam)->nOption = ...;
    return 0;
}

像这样使用它:

int main()
{
  MyStruct struct;
  struct.bSave = TRUE;
  struct.nOption = 0;
  ProcessMe(0, 0, (long)(&struct));
  if(FALSE==struct.bSave)
    printf("bSave is modified!");
  return 1;
}

严格来说,这是未定义的行为。您需要检查它是否适用于您的平台。

注意:我这里使用了一个结构体,因为这样你也可以将更多的变量或者更大的变量比如double传递给函数。

【讨论】:

  • 错了,不会修改原来的参数。 wParam 还是一样的。
  • @LuchianGrigore - 当然!目的是修改结构的内容。
  • 什么结构?他的代码中没有结构。你的答案回答了一个完全不同的问题。
  • @Luchian Grigore:作者的意思是调用者可以传递一个指向任意结构的指针,而不是lParam
  • 将指针转换为整数类型属于实现定义的行为。在这种情况下,如果 long 恰好不够大,无法在转换后保存指针值,则在这种情况下是未定义的。
【解决方案2】:

不,没有办法。

按值传递意味着创建原始值的副本。在方法范围内,您将没有关于原始变量的信息。

您需要通过引用或值传递。

【讨论】:

  • 这是不正确的。尽管您不能以类型安全的方式执行此操作,但您可以通过重新解释 lParam 的内容来执行此操作。这实际上在 Win32 API 中经常发生。 Henrik 已经展示了一个可行的解决方案。
  • @AlefSin 取消引用 NULL 指针也发生在 Win32 API 中。这就是为什么我们有一个标准来指导我们,而不是 MSDN 文档。
  • 我知道它不是类型安全的,我说过。这一切都源于这样一个事实,即一些较旧的 API 是在 C 程序员不太关心类型安全的时代设计的。当您无法更改 API 的代码时,您将不得不求助于丑陋、不安全、不可移植且可能很危险的解决方案。
  • +1 表示 AlefSin '丑陋、不安全、不可移植并且可能很危险',但也像内核 API 一样,通常是'不可避免的'。不可避免的胜利。
  • @AlefSin:标准保证通过足够大小的整数往返。如果您必须使用大小不足的整数来执行此操作,则可以使用指向全局指针数组的索引。
【解决方案3】:

试试这个...这是可能的!!!

包括

int ProcessMe(int nCommand, unsigned int wParam, long lParam)

{

int *nCommand_ptr = (int*)nCommand;
unsigned int *wParam_ptr = (unsigned int*)wParam;
long *lParam_ptr = (long*)lParam;
*nCommand_ptr = 10;
*wParam_ptr = 10;
*lParam_ptr = 10;
return 0;

}

int main(){

int nCommand = 5;
unsigned int wParam = 5;
long lParam = 5;
printf("\n %d %u %lu",nCommand,wParam,lParam);
ProcessMe((int)&nCommand,(unsigned int)&wParam,(long)&lParam);
printf("\n %d %u %lu",nCommand,wParam,lParam);
return 0;

}

输出:

$>g++ passbyref.cc

$>./a.out

5 5 5

10 10 10 美元>

【讨论】:

    【解决方案4】:

    为 32 位平台编译,您可以将指针转换为参数:

    #include <stdio.h>
    typedef int BOOL;
    #define FALSE 0
    #define TRUE 1
    
    int ProcessMe(int nCommand, unsigned int wParam, long lParam)
    {
      // cast to match what you passed in
      *((BOOL*)wParam) = FALSE;
      return 0;
    }
    
    int cast_arguments_main(int, char **)
    {
      BOOL bSave = TRUE;
      int nOption = 0;
      ProcessMe(0, (unsigned int)(&bSave), (long)(&nOption));
      if(FALSE==bSave)
        printf("bSave is modified!");
      return 1;
    }
    

    在 64 位平台上,编译器抱怨无法表示指针:

    cast_arguments.cpp:17:37: error: cast from ‘BOOL*’ to ‘unsigned int’ loses precision
    

    我不得不将unsigned int wParam 参数声明更改为unsigned long wParam,并在调用点进行类似更改:ProcessMe(0, (unsigned long)(&amp;bSave), (long)(&amp;nOption)); 但是您可以简单地为您的参数声明正确的类型,即BOOL *。因此可行性取决于您的目标机器架构...

    【讨论】:

    • 在 64 位 Windows 上(这是名称 lParam 弹出最多的地方),lParam 的类型不是 long。实际上,他可能应该只是使用intptr_t 开始。
    猜你喜欢
    • 2021-10-12
    • 1970-01-01
    • 2023-01-03
    • 1970-01-01
    • 2013-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-30
    相关资源
    最近更新 更多