作者zlw (www.eJob.gov.tw)
看板C_and_CPP
标题Re: [问题] VC2005 ShellExecute();
时间Wed Jun 17 12:45:01 2009
看了你後来讲的,追踪一下大概知道你的意思了。
你用VC新增专案,选了很新奇的 Visual C++ -> CLR (Common Language Runtime) ->
Windows Form 应用程式。也就是要写 C++/CLI 的程式。
然後你以前有写 console 介面的程式跑一些处理後,会输出一个文字档 readme.txt。
你新增完专案後,想要试看看能不能用函数去开启 readme.txt。
於是你找到两个 Windows API 函数:ShellExecute 跟 WinExec
其实你可以用 <stdlib.h> 里简单的 system("notepad C:\xxx.txt");
根据msdn,WinExec 是为了跟 16位元 Windows 相容才提供,建议使用 CreateProcess()
而 ShellExecute() 则比较有趣,以 txt 来说,你用我的电脑->工具->资料夹选项->
档案类型->txt->进阶後,可以设定,当你在档案总管点选txt按右键时的行为。
比如 .txt 你可以新增一个「用madedit开启」或简单点新增「edit」,然後对应执行此
动作的应用程式 D:\madedit.exe。当然 edit 会被翻译成编辑。
以後你在txt档,按右键选编辑,就会用 madedit 开启该文字档。
同理,ShellExecute()就是把 "open" 改成 "edit" 再选择一个 txt 档的路径即可。
linux 桌面 GNOME 在 终端机 下有个指令,好像是 gnome-open 123.avi 就是做同样
的事,跑这个指令後,就会等於你在 gnome 桌面环境点两下 123.avi 去开启他。
--
根据 msdn 的叙述,使用 ShellExecute() 你需要
Header shellapi.h
Import library shell32.lib
上面这是最低需求,我的习惯是明知道要呼叫的是 Windows API ,那就先
#include <windows.h> 再按下 F7 先编译一次。
然後打函数名称,不要打完就用 alt + → 叫出自动完成。
如果叫得出名字,代表你已经 include 原型宣告成功,不用再加 shellapi.h
windows.h 已经包含该档。亦可看出此函数是否是巨集。Windows API 很多都是
有分 ANSI 跟 UNICODE 两种版本,也就是根据专案设定是否支援 UNICODE 来
决定要呼叫 ShellExecuteA() 或 ShellExecuteW() 目前新版的 VC 预设值都是
UNICODE 支援开启,所以你的字串常数一律都要改成 wchar_t 资料型态。
但可使用包含於 windows.h 里的巨集 TEXT("open") ,这样就能根据情况转成
char 或 wchar_t。
故你应该写 ShellExecute ( NULL, TEXT("open"), TEXT("C:\\sample.txt"), NULL,
NULL,SW_SHOWNORMAL );
然後你编译,发生 lnk 2028 错误,但没有编译错误。这代表所有程式码基本上语法正确
可通过编译器,但连结时会错误。
很容易判断出是 ShellExecute 的连结没有成功,你要更改专案设定里关於连结器
的选项,加入 shell32.lib 的连结设定。
或者不要更改专案设定,而是在 ShellExecute 所在的 cpp 档假设是 main.cpp 里,
加入编译器前置处理指令 #pragma comment(lib, "shell32")
pragmatic info: 编译器在 main.obj 加入注解,告知连结器增加连结 shell32 的参数
不用打全名 shell32.lib,连结器会到预设目录去搜寻此静态连结程式库档案。
※ 引述《lytn》之铭言:
: 不过我发现,要是当初专案用 主控台程式建立 ,就不会有ERROR,
: DEV C++ 也可以空专案直接跑,
: 所以......还是卡关了.
: 作者: lytn (sapphira) 看板: C_and_CPP
: 标题: [问题] VC2005 ShellExecute();
: 时间: Tue Jun 16 17:29:35 2009
: 我用 学校授权的 VS2005 ,应该是 .NET 架构吧
: 专案用 Windows From 开启的
: 网路上找很多范例 例如 MSDN
: http://msdn.microsoft.com/en-us/library/bb762153(VS.85).aspx
: 虽然是看不太懂,不过我照着写,或是网路上范例码直接COPY用
: #include<windows.h>
: #include<shellapi.h>
: ShellExecute ( NULL, "open", "C:\\path\\to\\readme.txt", NULL, NULL,
: SW_SHOWNORMAL );
: 编译时都会
: Error C2644:'ShellExecuteW':无法将参数2 从 'const char[5]' 转换成 'LPCWSTR'
: 这要怎麽搞阿?
: ----原程式码
: #include "stdafx.h"
: #include <windows.h>
: #include <shellapi.h>
: #include<vector>
: #include "Form1.h"
: #include "GloVar.h"
: #include "CivilClass.h"
: #include "BasicExcel.hpp"
: using namespace YExcel;
: using namespace VC_Project;
: [STAThreadAttribute]
: int main(array<System::String ^> ^args)
: {
: // HWND hwnd;
: // LPCWSTR filpat="open";
: WinExec("Notepad.exe", SW_SHOW); //这个会跑
: ShellExecute ( NULL, "open", "C:\\path\\to\\readme.txt", NULL, NULL,
: SW_SHOWNORMAL ); //这会错
: Application::EnableVisualStyles();
: Application::SetCompatibleTextRenderingDefault(false);
: // 建立主视窗并执行
: Application::Run(gcnew Form1());
: return 0;
: }
: 另外想顺便问, WinExec 跟 ShellExecute 有什麽差别?
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 124.8.131.252
1F:推 lytn:太棒, 06/17 13:36
2F:→ lytn:我还以为把 shell32.dll 加入参考就可以了,原来还有 linker 06/17 13:37
3F:推 VictorTom:推一下z大:) 06/17 13:39
4F:推 lytn:改完 可以 开 EXE 跟 TXT罗,大感谢阿,不然还不知道要拖多久 06/17 13:39
整理
Visual C++ 相对於 GNU 的 gcc 或 g++,命令列指令用的是 cl.exe,提供完整编译
C, C++, C++/CLI 成目的档 .obj 的编译器功能,再加上连结成 .exe 的功能。
在 Visual C++ 选工具 -> VS 200X Command Prompt 叫出命令提示字元後
打 cl src.cpp /c 就会只做编译器的功能输出 src.obj。
而不加 /c 则会输出 src.obj 跟 src.exe。
若指令 cl 加上参数 /MT,则编译选项为 multithread support & static link
/MD 同上但 dynamic link,故/MD输出的执行档较小,但缺所需dll的电脑就不能跑。
/MTd 与 /MDd 为等於在 debug 模式编译出结果,会附加侦错资讯。
而执行 link src.obj 可连结出 src.exe 但似乎无法做 /MT /MD 的指定,比较不方便。
--
obj 档是 COFF (Common Object File Format) 格式,可用 VC 附的指令 dumpbin 解读
或上网抓把 dumpbin 包装成 GUI 的 wumpbin 软体。
dumpbin 查询 src.obj 可发现加入 #pragma comment (lib,"shell32") 後
在 .drectve section 里有个 Linker Directive 多出一行 /DEFAULTLIB:"shell32"。
因此不管用 cl.exe 或 link.exe 都会记得在连结 src.obj 时,顺便加上 shell32.lib
一起连结成 exe。
--
obj 档做 dumpbin:
.bss section 用来放没有初始化的变数,如以下的 count
#include <stdio.h>
int count; //就算加了 static 使其变成内部连结,还是在.bss区
int data = 2;
int main(){
int i;
int j = 2;
return 0;
}
※
http://blog.linux.org.tw/~jserv/archives/002030.html
.data 放资料,通常是有初始化的全域变数,比如上面的 int data = 2;
你在 .data 区就会看到四个16进位的Byte写 02 00 00 00
(因为CPU是little endian,所以不是 00 00 00 02)
就算是变成 static int data = 2; 其差别也只是变成内部连结,其他都一样。
而上面的 i 跟 j 是当程式执行到函数 main 时才随便从属於堆叠的那块记忆体
区域,找 4 个 Byte 决定当成 i 或 j,一旦此函数结束,函数里的
auto 变数所占的区域可能就变成另外一个 auto (local) 变数拿去用。
.rdata 放唯读资料,比如 "C:\\123.txt" 这样的字串常数。在程式执行後
如果写入这个区域所在的记忆体,则 Linux 上应该会回报 Segment fault,
Windows 好像是跳出 Runtime error 视窗。
.text 除了资料以外的东西,应该就是指令。
symbol table 每个 obj 都有一份,有好几笔,某笔可能写
00000000 SECT3 External ?count@@3HA (int count)
告诉我们当初那个未初始化的全域变数 int count; 被放在 Section 3
此例也就是 .bss section,放在里面偏移位置 00000000 的地方,
其实就是第一个 Byte 所在。
它是 external (外部连结)。因为它的宣告没有加 static,
所以其他的 obj 档只要透过 extern int count; 就能呼叫他。
而也因为是外部连结,所以 C++ 编译器对 count 的符号名称装饰的
比较用心,变成比较复杂的 ?count@@3HA
如果当初是宣告成 static (内部连结),其他 obj 无法呼叫的话,
名称就只会简单的改成 _count 而已。
--
exe 档是 PE 格式 (Portable Executable),Windows 32/64 位元用的可执行档格式
Linux 是 ELF,DOS有个有名的com,写组语的话会发现它很平易近人,只要用组合语言
写完,把程式起始位址假定为 0x100 然後组译,改成 .com 就已经是合格的执行档。
没有relocation顾虑也没有header,因为点两下 OS 就把它载入到 0x100 记忆体去,
就好像在写 boot loader 可以直接假定BIOS会把你写的程式载入到 0000:7c00 一样。
exe 应该是改良自 COFF,总之用 dumpbin 就可以解读。
打开exe後发现多出一个 Imports,里面会写这个执行档需要配合哪个dll,而且
是用到这个dll的哪个函数。也可以用功能更强的 Dependcy Walker 来看。
通常都是程式写完要拿到别台电脑执行,结果被VC专案设定预设的选项 /MD 表了
不能跑後才想到要来看 Imports 了什麽 dll。
而那这 ShellExecute() 其实用 /MD 或 /MT 都一样到最後还是要用到 SHELL32.dll,
而且编译的时候一定要用到 lib,我猜 lib 里面是定义用来呼叫dll的程式码,不确定。
直接在 runtime 呼叫 dll 取得其函数所在,就可以躲过 Dependency Walker 等的侦测
当然 dll 还是必须存在才能执行其功能的。如下:
#include <windows.h>
//略
HINSTANCE hPDLL;
hPDLL = LoadLibrary(TEXT("shell32.dll"));
//呼叫 Windows API,故指定函数「呼叫惯例」为 __stdcall
typedef HINSTANCE (__stdcall *ptr)(HWND, LPCSTR, LPCSTR, LPCSTR, LPCSTR, INT);
ptr ptrFun;
ptrFun = (ptr)GetProcAddress(hPDLL, "ShellExecuteA");
ptrFun(0, "edit", "C:\\windows\\winnt.bmp", 0, 0, SW_SHOWMAXIMIZED);
※ 编辑: zlw 来自: 124.8.131.252 (06/17 17:54)
5F:→ zlw:打错,不是 boot loader 是 boot sector 06/17 18:06