我用VC++2005写了一个动态链接库,导出了如下函数:void SetVarialbels(char * VarList,...){
string VarNameList=string(VarList);
string VarName;
double VarValue=0.0;
va_list arg_ptr;
va_start(arg_ptr,VarList);
do
{
VarName = GetVarName(VarNameList);
if(VarName.length()>0)
{
VarValue = va_arg(arg_ptr,double);
map<string,double>::iterator p;
p=vars.find(VarName);
if(p!=vars.end())
p->second = VarValue;
else
vars.insert(pair<const string,const double>(VarName,VarValue));
}
}
while(VarName.length()>0);
va_end(arg_ptr);
}
现在使用Delphi调用这个导出函数,在Delphi中声明如下:
procedure SetVariables(VarList:pChar);stdcall;varargs;external 'MyDll.dll';
调用时可以这样调用:var
    A,B,C:double;
begin
    A:=1;
    B:=2;
    C:=4;
    SetVariables('A,B,C',A,B,C);
end;在动态链接库中,是按照double对A,B,C进行取值的,上面的调用方法没有问题,传递的值很正确,传到DLL中的1,2,4分别各占8个字节的内存.
但是如果将函数参数中的A,B,C变量换成常量就不行了,如下代码:    SetVariables('A,B,C',1,2,4);
    //或者:
    SetVariables('A,B,C',1.0,2.0,4.0);并且,这两句的效果也不相同,前者每个数占4个字节的内存,而后者却是每个数占12个字节.请问我的动态链接库的代码该怎么写才能使这个函数可以正确接受这三种方式的调用时传到DLL中的值?

解决方案 »

  1.   

    变参函数需要__cdcel约定的支持,据我所知,delphi不支持它
      

  2.   

    __cdcel约定表示调用者自己管理参数的入栈和出栈,因此可以实现变参。
    而stdcall约定表示被调用者管理参数的入栈和出栈,不能实现变参。
    pascal、vb等的调用约定都是stdcall
      

  3.   

        请问五岭散人,那么有没有办法变通一下,使我可以实现所需功能?是不是必须传递相关参数的类型信息才行?因为在动态链接库中,我可以将语句:
    VarValue = va_arg(arg_ptr,double);
    中的double改为float,int,char,short等等其他类型,但需要知道类型信息才可以,不过这样一来,调用的方法就要改变了,可这是用户所不希望的.
        您可以发现,我在动态链接库中使用的可变参数的方法是从C语言继承而来的,那么C++有没有特定的或更好的方法呢?注:分不够可以再加,希望得到正确解决方案.
      

  4.   

    我只是问C++的问题,而不是问Delphi的问题.
      

  5.   

    Why does function which has variable-argument must use __cdecl?
    The function which has variable-argument does not exactly know the arguments’ type in the argument list. It only changes the arguments into its expecting types. So the function does not know how to release the argument stack.  On the other hand,the rule of __cdecl is the invoker put the arguments to the stack, after the function returns, the invoker clear the arguments itself; the rule of __stdcall is the invoker put the arguments to the stack, the function who is invoked will clear the arguments before it returns. So if the function does not know the arguments’ type, it can not use __stdcall to clear the arguments stack.does the statement upwards make sense?
    if you want to pass a variable amount of arguments with different types i dont think there is any good solution other than variable arguments 
      

  6.   

    办法肯定是有的
    在delphi中使用内联汇编调用函数,调用完后,手工恢复堆栈,肯定可以
      

  7.   

    but the DLL LZ designed is used to provide some functions to other user. In some sense, the DLL is the product. LZ dont want to bother his customers to use assembles only because the fault of his product.and could you guys give us your own analysis of why function  with variable-argument   must   use   __cdecl? do you agree with me?(my opinion:because the function dont know the type of the arguments)
      

  8.   

    先不管C++如何,建议楼主先在Delphi中找到cdecl字样的调用声明,如果找到了,就可以直接调用C中的可变参数。如果没有,就没戏了。
      

  9.   

    Muf: 在DELPHI编程中,可以按照下面的声明使用动态链接库中的函数:procedure   SetVariables(VarList:pChar);cdecl;varargs;external   'MyDll.dll';如此看来是可以的了,那接下来我该怎么做呢,在C++里面怎么做?Delphi里怎么做? 
    只要说方法就可以了,谢谢!
      

  10.   

    见Delphi Help:When importing a C function that takes a variable number of parameters, use the varargs directive. For example,function printf(Format: PChar): Integer; cdecl; varargs;The varargs directive works only with external routines and only with the cdecl calling convention.你的dll函数,修改成:void __cdecl SetVarialbels(char * VarList,...){另外,不要以为Delphi不支持cdecl,Delphi支持各种调用约定,cdecl, fastcall(register)/safecall, stdcall, pascal
    Delphi不支持的只是写Delphi过程使用cdecl调用约定+可变参数,Delphi有自己实现可变参数的方式,在[]内部写参数,是类型安全的。
      

  11.   

    我现在在Delphi中改成这样:
    procedure AddVariables(VarList:pChar);cdecl;varargs;external 'MyDll.dll';
    在VC++中改成这样:
    void __cdecl AddVariables(const char * VarList,...)
    {
    string VarNameList=string(VarList);
    string VarName;
    double VarValue=0.0;
    va_list arg_ptr;
    va_start(arg_ptr,VarList);
    do
    {
    VarName = GetVarName(VarNameList);
    if(VarName.length()>0)
    {
    VarValue = va_arg(arg_ptr,double);
    map<string,double>::iterator p;
    p=vars.find(VarName);
    if(p!=vars.end())
    p->second = VarValue;
    else
    vars.insert(pair<const string,const double>(VarName,VarValue));
    }
    }
    while(VarName.length()>0);
    va_end(arg_ptr);
    }
    然后在Delphi中这样使用:
    AddVariables('A,B,C',1,2,3);
    可是在用VC++调试时发现,依然是按每个数4个字节传入的.内存如下:
    0x0012F60C  01 00 00 00 02 00 00 00 03 00 00 00  ............
    但是,如果这样调用:
    AddVariables('A,B,C',1.0,2.0,3.0);
    则每个数字占12个字节,内存如下:
    0x0012F5F4  00 00 00 00 00 00 40 9c 0c 40 00 00  ......@..@..
    0x0012F600  00 00 00 00 00 00 40 9c 0d 40 00 00  ......@..@..
    0x0012F60C  00 00 00 00 00 00 60 ea 0d 40 00 00  ......`..@..
    如果使用变量进行调用:var
        a,b,c:double;
    begin
        a:=1;
        b:=2;
        c:=3;
        AddVariables('A,B,C',a,b,c);
    end;
    则是正确的,内存如下:
    0x0012F5E8  00 00 00 00 00 00 f0 3f  .......?
    0x0012F5F0  00 00 00 00 00 00 00 40  .......@
    0x0012F5F8  00 00 00 00 00 00 08 40  .......@
    取得的结果也是正确的(监视器):
    vars [3](("a",1.0000000000000000),("b",2.0000000000000000),("c",3.0000000000000000))
    现在问题只有一个,那就是我怎么才能让数据总是按照double的浮点格式,也就是最后这种格式进行传递呢?
    也许我太啰嗦了,但希望能把问题说清楚.
      

  12.   

    强制类型转换
    AddVariables('A,B,C',double(1),double(2),double(3));
      

  13.   

    阿呆,你的办法不好使,DELPHI好像不支持这样的强制类型转换,编译出错呀.
      

  14.   

    这是因为可变数量的参数,并不明了类型,Delphi对浮点立即数总是作为extended类型(80位扩展精度)传入的。
    目前还没发现可以强制作为double参数的方法。解决的方法:
    1. 使用double类型变量作为参数,不要使用立即数;2. 对C++函数稍加修改,适应Delphi的OpenArray方式的可变参数:
    extern "C"  __declspec(dllexport) void __cdecl AddVariables(char *, double *, int);Delphi中的声明:
    procedure AddVariables(VarList: PChar; Data: array of double); cdecl; external 'xxx.dll';调用:
    AddVariables('A,B,C', [a, 2, 3.0]);
    传变量、整型立即数、浮点立即数,包括计算表达式都可以。
      

  15.   

    DelphiGuy,你好:
        真是太谢谢你了,你的方法是有效的.同时,我把cdecl 改成 stdcall 也没有问题.看来这两种方法区别不大.
      

  16.   

    DelphiGuy 强人。 
    涨见识了。
      

  17.   

    似乎在VC++中,用stdcall调用约定则extern   "C"声明无效,总是生成C++风格的引出名(name@123abc之类的)。
    我没有仔细研究,也许有办法避免。
      

  18.   

    现在是用VC++2005做的,没有使用 extern "C",而只要如下声明即可:
    void __fastcall AddVariables(char *,double *Data,int iCount);
    总之,你帮我解决了这个问题, 十分感谢.
      

  19.   

    ms的fastcall和borland的fastcall是不一样的(bcc除了fastcall,还有一个msfastcall),前者左边两个参数用ecx、edx传送,后者左边三个参数用eax、edx、ecx传送。
    这样居然可以?:)
    我试试看。
      

  20.   

    再次感谢DelphiGuy,受益了,不过我的代码的确是可用的,是正确的.也许微软改了这些细节没有向全世界公布,真他妈的不是东西!呵呵.