喵哥最近在做一个需要远程控制相机的项目,其中一个环节就是需要用一个控制端的exe去调用相机运行的exe,还需要根据控制端提供的参数对相机作出相应的控制动作。另外,如果有做类似项目的朋友,希望可以交流一下经验,共同进步。
目录
WinExec
ShellExecute
接收参数
根据以往的经验,需要在外部执行文件开启一个程序的方法就是使用WinExec()函数,这个函数的参数简单,用起来也十分方便:
UINT WINAPI WinExec(_In_ LPCSTR lpCmdLine,_In_ UINT uCmdShow
);
WinExec
WinExec()的第一个参数是执行文件的路径,第二个参数是执行文件打开后的显示方式。在使用WinExec()时需要注意的事情:
************返回值****************
大于 31 {调用成功}
等于 0 {内存不足}
ERROR_FILE_NOT_FOUND = 2; {文件名错误}
ERROR_PATH_NOT_FOUND = 3; {路径名错误}
ERROR_BAD_FORMAT = 11; {EXE 文件无效}
(请参考FindExecutable函数) *************uCmdShow 参数可选值**************
SW_HIDE = 0; {隐藏, 并且任务栏也没有最小化图标} SW_SHOWNORMAL = 1; {用最近的大小和位置显示, 激活}
SW_NORMAL = 1; {同 SW_SHOWNORMAL}
SW_SHOWMINIMIZED = 2; {最小化, 激活}
SW_SHOWMAXIMIZED = 3; {最大化, 激活}
SW_MAXIMIZE = 3; {同 SW_SHOWMAXIMIZED}
SW_SHOWNOACTIVATE = 4; {用最近的大小和位置显示, 不激活}
SW_SHOW = 5; {同 SW_SHOWNORMAL}
SW_MINIMIZE = 6; {最小化, 不激活}
SW_SHOWMINNOACTIVE = 7; {同 SW_MINIMIZE}
SW_SHOWNA = 8; {同 SW_SHOWNOACTIVATE}
SW_RESTORE = 9; {同 SW_SHOWNORMAL}
SW_SHOWDEFAULT = 10; {同 SW_SHOWNORMAL}
SW_MAX = 10; {同 SW_SHOWNORMAL}
另外,winexec() 必须有GetMessage或超时之后才返回!
ShellExecute
WinExec的一大缺点就是没有参数的传递机制,其实如果目标执行文件的参数不多,并且调用目标函数时使用的参数是有规律的,那么其实可以把参数放在一个文件中保存,然后顺序读取参数即可,也可以实现根据参数来控制目标执行文件的启动,但是喵哥的项目需要不按参数顺序来启动执行文件,或者说考虑到以后程序的扩展性,所以放弃了这个函数,然后在网上寻找合适的替代品。然后找到一说WinExec就不得不说的ShellExecute。
ShellExecute()函数的参数表要比WinExec大,有6个参数:
HINSTANCE ShellExecute(_In_opt_ HWND hwnd, _In_opt_ LPCTSTR lpOperation, _In_ LPCTSTR lpFile, _In_opt_ LPCTSTR lpParameters, _In_opt_ LPCTSTR lpDirectory, _In_ INT nShowCmd
);
各个参数的说明如下:
hWnd:用于指定父窗口句柄。当函数调用过程出现错误时,它将作为Windows消息窗口的父窗口。例如,可以将其设置为应用程序主窗口句柄,即Application.Handle,也可以将其设置为桌面窗口句柄(用GetDesktopWindow函数获得)。
Operation:用于指定要进行的操作。其中“open”操作表示执行由FileName参数指定的程序,或打开由FileName参数指定的文件或文件夹;“print”操作表示打印由FileName参数指定的文件;“explore”操作表示浏览由FileName参数指定的文件夹。当参数设为nil时,表示执行默认操作“open”。
FileName:用于指定要打开的文件名、要执行的程序文件名或要浏览的文件夹名。
Parameters:若FileName参数是一个可执行程序,则此参数指定命令行参数,否则此参数应为nil或PChar(0)。
Directory:用于指定文件开始运行的默认目录,即FileName为NULL时,打开该文件夹,若不对默认目录做出设置(NULL),默认打开文档(环境为Windows 10)。
ShowCmd:若FileName参数是一个可执行程序,则此参数指定程序窗口的初始显示方式,否则此参数应设置为0。这个参数可用的值如下所列:
1 SW_HIDE 隐藏这个窗体,并激活其他窗体。
2 SW_MAXIMIZE 最大化指定的窗体。
3 SW_MINIMIZE 最小化指定的窗体,并按顺序激活最上层的窗体。
4 SW_RESTORE 激活并显示窗体。如果窗体为最小化或者最大化,窗体恢复到原始大小和位置。应用程序当恢复一个最小化的窗体时将指定标记。
5 SW_SHOW 以当前的大小和位置激活并显示窗体。
6 SW_SHOWDEFAULT
7 SW_SHOWMAXIMIZED 激活并最大化显示窗体。
8 SW_SHOWMINIMIZED 激活并最小化现实窗体。
9 SW_SHOWMINNOACTIVE 最小化窗体,保持其激活状态。
10 SW_SHOWNA 以当前状态显示窗体,保持其激活状态。
11 SW_SHOWNOACTIVATE 以当前的大小和位置显示窗体,并保持其激活状态。
12 SW_SHOWNORMAL 激活并显示一个窗体。如果窗体为最大化或者最小化,窗体恢复到原始的大小和位置。当窗体第一次显示的时候,应用程序记录标记。
若ShellExecute函数调用成功,则返回值为被执行程序的实例句柄。若返回值小于32,则表示出现错误。
ShellExecute的功能远不止这些,它还可以做一些特别酷的事情:
1. 如果将FileName参数设置为“http:”协议格式,那么该函数将打开默认浏览器并链接到指定的URL地址。若用户机器中安装了多个浏览器 ,则该函数将根据Windows 9x/NT注册表中http协议处理程序(Protocols Handler)的设置确定启动哪个浏览器。
格式一:http://网站域名。 如:ShellExecute(handle, ‘open’, http://www.neu.edu.cn’, nil, nil, SW_SHOWNORMAL);
格式二:http://网站域名/网页文件名。
如:ShellExecute(handle, ‘open’, http://www.neu.edu.cn/default.htm’,nil,nil,SW_SHOWNORMAL);
2.如果将FileName参数设置为“mailto:”协议格式,那么该函数将启动默认邮件客户程序,如Microsoft Outlook(也包括Microsoft Outlook Express)或Netscape Messanger。若用户机器中安装了多个邮件客户程序,则该函数将根据Windows 9x/NT注册表中mailto协议处理 程序的设置确定启动哪个邮件客户程序。
格式一:mailto:
如:ShellExecute(handle,‘open’, ‘mailto:’, nil, nil, SW_SHOWNORMAL);打开新邮件窗口。
格式二:mailto:用户账号@邮件服务器地址
如:ShellExecute(handle, ‘open’,‘ mailto:who@mail.neu.edu.cn’, nil, nil, SW_SHOWNORMAL);打开新邮件窗口,并自动填入收件人地址。若指定多个收件人地址,则收件人地址之间必须用分号或逗号分隔开(下同)。
格式三:mailto:用户账号@邮件服务器地址?subject=邮件主题&body=邮件正文
如:ShellExecute(handle, ‘open’, ‘ mailto:who@mail.neu.edu.cn?subject=Hello&Body=This is a test’, nil, nil,
SW_SHOWNORMAL);打开新邮件窗口,并自动填入收件人地址、邮件主题和邮件正文。若邮件正文包括多行文本,则必须在每行文本之间加入换行转义字符%0a。
还有其他一些有意思的操作可以参考:深入浅出ShellExecute(总结)。
除了ShellExecute()之外,还有一个比较复杂、又强大的函数——CreateProcess(),其函数原型如下:
BOOL WINAPI CreateProcess(_In_opt_ LPCTSTR lpApplicationName,_Inout_opt_ LPTSTR lpCommandLine,_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In_ BOOL bInheritHandles,_In_ DWORD dwCreationFlags,_In_opt_ LPVOID lpEnvironment,_In_opt_ LPCTSTR lpCurrentDirectory,_In_ LPSTARTUPINFO lpStartupInfo,_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
由于CreateProcess比较复杂,而ShellExecute还可以满足需求,所以暂时没有认真去了解它。有兴趣的朋友可以去看:https://blog.csdn.net/duck04551/article/details/4312260。
接收参数
所以喵哥最终选用了ShellExecute()函数来控制目标执行文件,但是还有一个问题需要解决,那就是目标执行文件怎么接收ShellExecute发过去的参数呢,为此喵哥又去研究了一下main函数的参数传递规律。
先从最简单的 int main(int argc, char * argv)开始,喵哥最开始接触C/C++时,对于这个主函数的参数定义十分不屑(主要是无知...)觉得我用main()不一样用得挺好嘛,也一直没用到过这两个参数,直到最近几天,我突然意识到外部跟程序的参数传递可以通过这俩参数,真是惊醒梦中人。
为了测试传递参数的规律,我写了如下简单的测试代码:
int main(int argc, char *argv[])
{int i;for (i = 0; i < argc; i++)printf("Argument %d is %s.\n", i, argv[i]);getchar();return 0;
}
然后在cmd中执行生成的exe文件:
根据cmd中的运行结果,可以得出一些结论。第一个参数argc,指明有多少个参数将被传递给主函数main(),真正的参数以字符串数组(即第2个参数argv[])的形式来传递。需要注意的是,argc代表参数的数量, main()函数本身是在索引0为的第一参数。 所以, argc总是至少为1,它的数值是argv列阵的元素数目。 所以, argv[0]的值是至关重要的。 如果用户在控制台环境中程序名称后键入含参数的指令, 那么随后的参数将传递给argv[1]。
熟悉MFC的朋友都知道,APP源代码中的InitInstance()相当于C++main函数,即程序的入口,在这里就可以接收命令传递过来的参数,并作出相应的操作。不过在MFC中argc和argv分别变成__argc和__argv,以下是我的部分代码。
/*****************接收ShellExecute的消息************************/CString str;for (int i = 1; i < __argc; i++){str += __argv[i]; }/***************************设置INDEX以选择需要的相机**********************/if (str == "openCamera1") INDEX = 0;else if (str == "openCamera2") INDEX = 1;else if (str == "openCamera3") INDEX = 2;else if (str == "openCamera4") INDEX = 3;else if (str == "openCamera5") INDEX = 4;else if (str == "openCamera6") INDEX = 5;else INDEX = 0;