当前位置: 代码迷 >> 综合 >> 实现外部程序根据参数调用、控制目标程序——ShellExecute()函数的详解及相关知识的拓展
  详细解决方案

实现外部程序根据参数调用、控制目标程序——ShellExecute()函数的详解及相关知识的拓展

热度:18   发布时间:2023-12-22 13:19:22.0

喵哥最近在做一个需要远程控制相机的项目,其中一个环节就是需要用一个控制端的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;