VC生成不依赖高版本msvcrtXX.dll程序之方法一——完全抛弃CRT库

只使用Win32API,完全抛弃CRT库

如果我们的程序只使用C语言基本语法、调用Win32API而不依赖C库的函数(《Windows程序设计》(petzold著)中的代码基本就是如此,因此国外也有人称这类程序是petzold-style),则完全可以抛弃掉msvcrt库,使得最终生成的可执行文件体积接近Win32汇编编写的可执行程序大小。

修改程序入口点

要想彻底摆脱CRT库的依赖,第一步就是要让程序入口点指向我们自己的代码,而不再采用CRT库默认的启动函数。为此,我们定义一个void类型的无参函数作为程序入口:

#include .h>

void EntryMain()
{
    MessageBox(NULL, TEXT("Hello world!"), TEXT("hi"), MB_OK);
    ExitProcess(0);
}

然后在链接时将EntryMain指定为程序入口:

link.exe附加参数:/ENTRY:"EntryMain"

在IDE中,也可以通过链接器设置进行修改:

VC生成不依赖高版本msvcrtXX.dll程序之方法一——完全抛弃CRT库_第1张图片

值得注意的是,该入口函数无参,如果需要使用命令行参数的话,请使用GetCommandLine函数获取后自行进行解析,如果要获取应用程序的句柄,也请自行调用GetModuleHandle。

不调用任何C库函数

要脱离C库的依赖,就不能再调用任何C库函数,比如大家耳熟能详的printf、malloc、strlen、memcpy等等。

  • 如果是要编写Win32GUI程序,则用于命令行I/O的函数本身就没有太大用处,需要格式化输出,也可以使用kernel32提供的wsprintf函数;

  • 要分配堆内存,可以使用GetProcessHeap、HeapAlloc、HeapFree等函数,如果上述函数不能满足要求,还有大名鼎鼎的VirtualAlloc和VirtualFree函数可供使用。

  • 要进行文件操作,有以下两种方式:

    • 可以使用CreateFile、ReadFile、WriteFile、CloseHandle等API进行传统意义上的读写操作
    • 也可以选择用文件内存映射的方式完成,调用CreateFileMapping、MapViewOfFile、UnmapViewOfFile等API,我比较喜欢用这种方式。
  • 如果要对字符串进行处理,kernel32中其实提供了lstrlen、lstrcmpi等一系列类似于C库的函数,使用起来也很方便。

  • 如果一定要调用C库中的函数,那也不是没有办法,只要不依赖C库初始化代码的函数,可以使用LoadLibrary+GetProcAddress动态加载大法,以C库中的strcmp为例(其实直接用kernel32提供的lstrlen即可,这里只是为了起到演示效果)

    
    #include 
    
    
    typedef ULONG(__cdecl *PFNSTRLEN)(PCHAR pszText);
    
    void EntryMain()
    {
    static TCHAR szBuff[0x256];
    
    PFNSTRLEN strlen;
    HMODULE hCRTDll = LoadLibrary(TEXT("msvcrt.dll"));
    if (hCRTDll)
    {
        strlen = (PFNSTRLEN)GetProcAddress(hCRTDll, "strlen");
    }
    
    ULONG ulLen = strlen("Abcdefgh");
    
    wsprintf(szBuff, TEXT("The lens of str is %u"), ulLen);
    MessageBox(NULL, szBuff, TEXT("hi"), MB_OK);
    
    ExitProcess(0);
    }

    关于函数指针这里,其实还有另外一种风格,熟悉汇编的朋友肯定觉得这不都一样嘛。可是在C中,GetProcAddress返回的类型是FARPROC,必须想办法进行转换,不然直接赋值肯定会报错。当然,觉得代码不够简洁的也可以内联汇编直接开搞。

    
    #include 
    
    
    ULONG(__cdecl *_strlen)(PCHAR pszText);
    
    void EntryMain()
    {
    static TCHAR szBuff[0x256];
    
    HMODULE hCRTDll = LoadLibrary(TEXT("msvcrt.dll"));
    if (hCRTDll)
    {
        *PDWORD(&_strlen) = (DWORD)GetProcAddress(hCRTDll, "strlen");
    }
    
    ULONG ulLen = _strlen("Abcdefgh");
    
    wsprintf(szBuff, TEXT("The lens of str is %u"), ulLen);
    MessageBox(NULL, szBuff, TEXT("hi"), MB_OK);
    
    ExitProcess(0);
    }

  • 对于进程、线程相关的函数,C/C++中的beginthread等等本来就是对Win32API的一个封装而已,完全可以用Win32API中相应的函数即可。

  • 对于异常处理,很遗憾不能使用__try、__except和__finally等关键字,因为这些关键字的背后需要,c库中的except_handlerX等函数(X是数字,表示版本)进行处理和支持。但别忘了,SEH机制是Windows提供的,因此,依然可以采用Windows原生的SEH机制进行异常处理和捕获。这部分我会另起一篇博文进行讲解,等文章出来以后再补上链接。

取消所有附加的链接库

一般经过上述处理后,编译生成的应用程序的导入表中就已经没有msvcrt库的依赖了。如果发生问题,则需要在链接设置中取消C库的链接操作,一般为了偷懒,我选择忽略掉所有默认库的链接选项:

3.忽略所有默认库

对于自己需要的库,可以在链接选项中加上,或者使用#pragma comment(lib, “库名”)的方式加入。

如果进行了上述操作,则还需要取消编译选项中的安全检查/GS功能,因为这些功能插入的代码依赖了msvcrt库。

VC生成不依赖高版本msvcrtXX.dll程序之方法一——完全抛弃CRT库_第2张图片

经过上述设置,就可以编写出不依赖于crt库、体积超小的可执行程序。但是,这样使用的弊端也很明显:

  • 无法使用C库的功能,习惯于调用C库函数的朋友们可能会觉得很不爽
  • 无法使用某些紧密依赖C库功能的特性,如VC++的异常处理等功能
  • 使用C++编程时功能受限,无法创建全局类(构造函数不能在初始化时被调用),也无法进行new、delete操作(如果不考虑构造函数和析构函数的调用,可以重载这两个操作符,但不建议使用)
  • 无法使用各种依赖C++的类库,如MFC、QT、STL、boost等等

因此,除了在某些特殊的场合,这种方式并不经常被使用。在下一篇中,我们会进行另一种尝试:让高版本的VS生成的程序链接到msvcrt.dll上。

你可能感兴趣的:(Win32,SDK编程)