C++ 在MASM中调用标准库函数

C++ 在MASM中调用标准库函数,c++,visual-studio,assembly,x86,masm,C++,Visual Studio,Assembly,X86,Masm,我想以混合C++/汇编的方式开始使用MASM。 我现在试图从一个进程中调用一个标准库函数(例如PrtTf),然后调用C++。 在我的cpp文件中声明printf的签名后,我的代码开始工作。但我不明白我为什么要这样做,如果我能避免的话 我的cpp文件: #include <stdio.h> extern "C" { extern int __stdcall foo(int, int); } extern int __stdcall printf(const char*, .

我想以混合C++/汇编的方式开始使用MASM。 我现在试图从一个进程中调用一个标准库函数(例如PrtTf),然后调用C++。 在我的cpp文件中声明printf的签名后,我的代码开始工作。但我不明白我为什么要这样做,如果我能避免的话

我的cpp文件:

#include <stdio.h>

extern "C" {
    extern int __stdcall foo(int, int);
}

extern int __stdcall printf(const char*, ...); // When I remove this line I get Linker-Error "LNK2019: unresolved external symbol"

int main()
{
    foo(5, 5);
}
#include <stdio.h>

extern "C" {
    extern int __cdecl foo(int, int);
}
extern int __cdecl printf(const char*, ...); // omiting the extern results in a linker error

int main()
{
    //printf("\0"); // this would replace the declaration
    foo(5, 5);
    return 0;
}
一些更新

作为对这些评论的回应,我试图重新编写代码以符合cdecl调用约定。不幸的是,这并没有解决问题(代码在
extern
声明中运行良好,但抛出了一个错误,没有任何错误)

但通过反复试验,我发现,
extern
似乎强制外部链接,即使不需要关键字,因为外部链接应该是函数声明的基础

我可以通过在我的cpp代码中使用函数来省略声明(即,如果在源文件中的某个地方添加一个
printf(“\0”);
,则链接器可以使用它,并且一切正常

新的(但不是更好的)cpp文件:

#include <stdio.h>

extern "C" {
    extern int __stdcall foo(int, int);
}

extern int __stdcall printf(const char*, ...); // When I remove this line I get Linker-Error "LNK2019: unresolved external symbol"

int main()
{
    foo(5, 5);
}
#include <stdio.h>

extern "C" {
    extern int __cdecl foo(int, int);
}
extern int __cdecl printf(const char*, ...); // omiting the extern results in a linker error

int main()
{
    //printf("\0"); // this would replace the declaration
    foo(5, 5);
    return 0;
}

这确实有点毫无意义,不是吗

链接器通常是非常愚蠢的东西。他们需要被告知对象文件需要
printf
。链接器无法从缺少的
printf
符号中看出这一点,这已经够愚蠢了

<> > C++编译器将告诉你在写代码“>代码> >外接int tsddLeLINK PRINTF(const char *,…);,或者,这是正常的方式,编译器会告诉链接器,所以当你真的调用<代码> Prtff<代码>时,C++的代码不调用它。 <>汇编程序也很笨。你的汇编程序显然无法告诉它需要从C++中链接到代码> Prtff<代码>的链接器。


一般的解决方案是不在汇编中做复杂的事情。汇编并不适合这样做。从C到汇编的调用通常工作得很好,以其他方式调用是有问题的。

我最好的猜测是,这与Microsoft从VS2015开始重构C库,并且一些C库现在是内联的这一事实有关d(包括
printf
),实际上不在默认的
.lib
文件中

我的猜测是这样的:

extern int __cdecl printf(const char*, ...);

<代码> ExtXu/<代码>强制将旧的遗留库包含在链接过程中。这些库包含非内联函数<代码> Prtff<代码>。如果C++代码不强制MS链接器包含遗留C库,则MASM代码使用的<代码> Prtff < /C>将不解决。< / P> < P> >我相信这与StAcExcel和2015有关。如果您想从C++代码中删除<代码> Extn int -CyDel-Prtuf(const char *,…);< /Cord> >,您可能希望考虑将这行添加到您的MASM代码:

includelib legacy_stdio_definitions.lib
如果使用CDECL调用约定并将C/C++与汇编语言混合使用,则MASM代码将如下所示:

.model flat, C      ; Default to C language
includelib legacy_stdio_definitions.lib

EXTERN printf :PROC ; declare printf

.data

tstStr db "Mult: %i",0Ah,"Add: %i",0 ; 0Ah is the backslash - escapes are not supported

.code

foo PROC x:DWORD, y:DWORD   
    mov eax, x
    mov ebx, y
    add eax, ebx
    push eax
    mov eax, x
    mul ebx
    push eax
    push OFFSET tstStr
    call printf
    ret
foo ENDP

END
<>你的C++代码是:

#include <stdio.h>

extern "C" {
    extern int foo(int, int); /* __cdecl removed since it is the default */
}

int main()
{
    //printf("\0"); // this would replace the declaration
    foo(5, 5);
    return 0;
}
使用EBX
告诉MASM生成额外的开场白和尾声代码,以便在开始时保存EBX,并在函数执行
ret
指令时恢复EBX。生成的指令类似于:


函数声明前面的
extern
可能不会执行您认为它会执行的操作,因为它没有效果。@KonradRudolph:它没有效果是一个相当强的语句,因为代码使用该声明编译,而不使用该声明编译。对于纯(标准)函数,您的注释可能是正确的C++程序,但这是C++和汇编的混合。很明显,编译器、这个汇编程序和这个链接器之间的交互作用是:<代码> ExtNe>代码>必须做一些事情。@ MsAlter删除代码< ExtNe>代码>不会改变任何东西(这里是一个红色鲱鱼)。因为函数声明总是隐式的外部声明。所以,是的,它没有任何效果。@MSalters Konrad谈论的是
外部声明,而不是整个声明。我自己确实有点困惑。因为包含
,所以已经有了
:std::printf
::printf
的声明,视情况而定。Bu澄清这一点确实是件好事。@AnonymousAnonymous,如果你只删除
extern
,它仍然有效吗?不是我的反对票,我很久没有使用MSVC了,但是源代码中的声明真的告诉链接器什么了吗?在GCC/clangg/…这是错误的,因为符号声明和库链接大多是或不正确的“C++”源文件中的<代码> Prtff<代码>声明将不起作用。一方面,在任何库中,不存在代码“>>STDCALL PrTFF < /C>”,编译器应警告,变量函数参数不能与约定方面,它确实更改了导出的名称,但仍然是一个非常好的答案,谢谢!我不知道必须恢复ebx-我只是希望在cdecl的文档中说明这一点,而不是在其他页面上,列出所有调用约定…我的意思是,来吧Microsoft:)
foo PROC uses EBX x:DWORD, y:DWORD    
    mov eax, x
    mov ebx, y
    add eax, ebx
    push eax
    mov eax, x
    mul ebx
    push eax
    push OFFSET tstStr
    call printf
    add esp, 12               ; Remove the parameters pushed on the stack for
                              ;     the printf call. The stack needs to be
                              ;     properly restored. If not done, the function
                              ;     prologue can't properly restore EBX
                              ;     (and any registers listed by USES)
    ret
foo ENDP
0000                    _foo:
0000  55                        push            ebp
0001  8B EC                     mov             ebp,esp
0003  53                        push            ebx
0004  8B 45 08                  mov             eax,0x8[ebp]
0007  8B 5D 0C                  mov             ebx,0xc[ebp]
000A  03 C3                     add             eax,ebx
000C  50                        push            eax
000D  8B 45 08                  mov             eax,0x8[ebp]
0010  F7 E3                     mul             ebx
0012  50                        push            eax
0013  68 00 00 00 00            push            tstStr
0018  E8 00 00 00 00            call            _printf
001D  83 C4 0C                  add             esp,0x0000000c
0020  5B                        pop             ebx
0021  C9                        leave
0022  C3                        ret