用 VC2012 产生脱离VC运行库的 C/C++ 程序

该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2013/08/02/build-program-without-vc-runtime/

最近在研究如何使一个VC编译的程序脱离VC运行库,也就是msvcrXX.dll。经过多次尝试,总算有所收获。

首先,要把msvcrXX.dll脱离出来,第一步是取消链接库,代码是:

1#pragma comment (linker, "/nodefaultlib:msvcrt.lib") /*If _DEBUG, it should be msvcrtd.lib*/

这样就把msvcrXX.dll去掉了,但是接下来的就问题无限了,比如,mainCRTStartup 函数没了。(这是Console程序,对于Windows程序应该是WinMainCRTStartup)。写过Windows SDK程序的人都知道,对于UNICODE和非UNICODE程序,入口函数是不一样的,为了统一,我们先指定下入口函数。

1#pragma comment (linker, "/entry:mainCRTStartup")

此外,没有 CRT 的时候,没法进行基本运行时检查,所以打开“项目属性-C/C++-代码生成”,把“基本运行时检查”改为“默认值”。(Release配置不用改,只改Debug)。

下面是入口函数实现:

1#include <Windows.h>
2int __cdecl mainCRTStartup() {
3    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello World!\r\n", 14, NULL, NULL);
4    return 0;
5}

编译运行,Sorry,出错了。错误提示:

11>main.obj : error LNK2001: 无法解析的外部符号 @__security_check_cookie@4

这个错误提示比较简单解决,只需引入 bufferoverflowU.lib 即可。

1#pragma comment (lib,"bufferoverflowU.lib")

这下再编译运行,OK了,此时程序只有3.5KB。但是,试试使用mallocprintf?或者是使用C++类?不好意思,这是VC运行库的内容。那么,似乎这么做毫无意义了?那也未必,如果你只是要 C 编程,那么可以把部分函数重写。比如:

 1void *mem_alloc(ULONG_PTR uSize) {
 2    return (void *)HeapAlloc (GetProcessHeap(), 0,uSize);
 3}
 4
 5void mem_free (LPVOID pMemBlock) {
 6    HeapFree (GetProcessHeap(), 0, (LPVOID)pMemBlock);
 7}
 8
 9void *operator new (size_t uSize) {
10    return (void *)HeapAlloc (GetProcessHeap(), 0,uSize);
11}
12
13void operator delete (void *pMemBlock) {
14    HeapFree (GetProcessHeap(), 0, (LPVOID)pMemBlock);
15}
16
17void *operator new[] (size_t uSize) {
18    return (void *)HeapAlloc (GetProcessHeap(), 0,uSize);
19}
20
21void operator delete[] (void *pMemBlock) {
22    HeapFree (GetProcessHeap(), 0, (LPVOID)pMemBlock);
23}
24
25DWORD echo(char *pszOutput) { /* Output a string */
26    DWORD dwLen;
27    dwLen = lstrlenA(pszOutput);
28    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), pszOutput, dwLen, &dwLen, NULL);
29    return dwLen;
30}

此时测试结果是,C++异常无效(需要在工程设置里把C++异常关掉,否则会提示找不到__CxxFrameHandler3),也就是说,这时 C++ 几乎废了。

是否觉得这么做太烦了?其实,msvcrXX.dll是各个版本VC的运行库文件,VC2005开始都要独立安装,但是对于VC6,却几乎不存在这个问题。因为几乎Windows 2000开始的Windows系统都带有VC6的运行库,那么,用它取代掉就可以了!这个比较简单,在VC6安装包里,“VC\Lib\” 目录下,找到 MSVCRT.LIB 和 MSVCRTD.LIB。前者是 Release 版本,后者是 Debug 版本,复制到工程目录下,分别命名为 msvcrt98.lib 和 msvcrt98d.lib,然后在代码里写:

1#pragma comment (linker, "/nodefaultlib:msvcrt.lib") /* msvcrtd.lib If _DEBUG */
2#pragma comment (lib, "msvcrt98.lib") /* msvcrt98d.lib If _DEBUG */

这里,引入了VC6的运行库,那么mainCRTStartup也已经写好了,所以入口函数写 main 即可。而且由于VC6的静态链接库太旧,没有“SAFESEH安全异常处理程序”,因此要把“项目属性-链接器-高级-映像具有安全异常处理程序”设置为“否 (/SAFESEH:NO)”。

到此,替代完成。mallocprintf 等 C 标准函数都可以用了。newdelete 等 C++ 运算符也可以用了,但是 C++ 异常依旧无效。

下面是一份完整代码:

 1#ifdef _DEBUG
 2    #pragma comment (linker, "/nodefaultlib:msvcrtd.lib")
 3    #pragma comment (lib, "msvcrt98d.lib")
 4#else
 5    #pragma comment (linker, "/nodefaultlib:msvcrt.lib")
 6    #pragma comment (lib, "msvcrt98.lib")
 7#endif
 8
 9#pragma comment (lib,"bufferoverflowU.lib")
10
11#include <windows.h>
12#include <stdio.h>
13#include <tchar.h>
14
15class A {
16public:
17    A(char *pszStr) {
18        echo ("New A: ");
19        echo (pszStr);
20        echo ("\r\n");
21    }
22
23    ~A() {
24        echo ("Delete A\r\n");
25    }
26
27    void print() {
28        echo ("A::print()\r\n");
29    }
30};
31
32int main() {
33    A a("Static"), *pa;
34    DWORD c;
35    char *buf;
36    buf = (char*)malloc(123);
37    strcpy(buf, "Hello World\r\n");
38    printf(buf);
39    a.print();
40    c = strlen("233213");
41    pa = new A("Dynamic");
42    pa->print();
43    delete pa;
44    free(buf);
45    return 0;
46}

Over.

comments powered by Disqus