Post List

2014년 12월 25일 목요일

CreateProcess 함수를 이용한 VLC 수행

원문 : http://blog.naver.com/hongsukil81/110027376940

이번에 MFC코딩을 하던 중 MFC코드에서 VLC의 제어를 하게 되었다.

단순히 Play/Stop에 대한 내용이지만.... 수행키기위해 4시간의 삽질을 하였다 ㅡㅡ;;;

간단히 요약하자면 vlc player를 이용하여 stream service를 제공하는 프로그램이다.

vlc에서 stream을 기본적으로 지원해 주기 때문에 따로 코덱을 사용하지 않아도 된다.

(훌륭하다 -_-)

이유는... CreateProcess 함수에 대한 정확한 설명 부족이었다. (내가 못찾은건가 ㅡㅡ)

그래서 기록으로 남겨놓을까 한다.

참고로 VLC는 미디어 플레이어로써 오픈소스 기반의 프로젝트 프로그램이다.

윈도우/리눅스 이외에도 많은 운영체제를 지원하며 강력한 기능을 가지고 있다.

http://www.videolan.org/

위의 사이트에서 프로그램 및 개발 문서 및 소스 등을 구할 수 있다.

자 그럼 일단 CreateProcess의 MSDN 내용을 보자.

/////////////////////////////////////////////////////////////////////////////////////////////

CreateProcess


The CreateProcess function creates a new process and its primary thread. The new process runs the specified executable file in the security context of the calling process.
If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token. To run the new process in the security context of the user represented by the impersonation token, use the CreateProcessAsUser or CreateProcessWithLogonW function.

BOOL CreateProcess(
  LPCTSTR lpApplicationName,
  LPTSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL bInheritHandles,
  DWORD dwCreationFlags,
  LPVOID lpEnvironment,
  LPCTSTR lpCurrentDirectory,
  LPSTARTUPINFO lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

Parameters

lpApplicationName
[in] Pointer to a null-terminated string that specifies the module to execute. The specified module can be a Windows-based application. It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer. The string can specify the full path and file name of the module to execute or it can specify a partial name. In the case of a partial name, the function uses the current drive and current directory to complete the specification. The function will not use the search path. If the file name does not contain an extension, .exe is assumed. Therefore, if the file name extension is .com, this parameter must include the .com extension.
The lpApplicationName parameter can be NULL. In that case, the module name must be the first white space-delimited token in the lpCommandLine string. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin; otherwise, the file name is ambiguous. For example, consider the string "c:\program files\sub dir\program name". This string can be interpreted in a number of ways. The system tries to interpret the possibilities in the following order:
c:\program.exe files\sub dir\program name
c:\program files\sub.exe dir\program name
c:\program files\sub dir\program.exe name
c:\program files\sub dir\program name.exe
If the executable module is a 16-bit application, lpApplicationName should be NULL, and the string pointed to by lpCommandLine should specify the executable module as well as its arguments.
To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the name of the batch file.
lpCommandLine
[in, out] Pointer to a null-terminated string that specifies the command line to execute. The maximum length of this string is 32K characters.
<b>Windows 2000:  </b>The maximum length of this string is MAX_PATH characters.
The Unicode version of this function, CreateProcessW, will fail if this parameter is a const string.
The lpCommandLine parameter can be NULL. In that case, the function uses the string pointed to by lpApplicationName as the command line.
If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.
If lpApplicationName is NULL, the first white-space – delimited token of the command line specifies the module name. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin (see the explanation for the lpApplicationName parameter). If the file name does not contain an extension, .exe is appended. Therefore, if the file name extension is .com, this parameter must include the .com extension. If the file name ends in a period (.) with no extension, or if the file name contains a path, .exe is not appended. If the file name does not contain a directory path, the system searches for the executable file in the following sequence:
  1. The directory from which the application loaded.
  2. The current directory for the parent process.
  3. The 32-bit Windows system directory. Use the <mshelp:link tabindex="0" errorurl="../common/badjump.htm" keywords="base.getsystemdirectory">GetSystemDirectory</mshelp:link> function to get the path of this directory.
    <b>Windows Me/98/95:  </b>The Windows system directory. Use the <b>GetSystemDirectory</b> function to get the path of this directory.
  4. The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is System.
  5. The Windows directory. Use the <mshelp:link tabindex="0" errorurl="../common/badjump.htm" keywords="base.getwindowsdirectory">GetWindowsDirectory</mshelp:link> function to get the path of this directory.
  6. The directories that are listed in the PATH environment variable.
The system adds a null character to the command line string to separate the file name from the arguments. This divides the original string into two strings for internal processing.
lpProcessAttributes
[in] Pointer to a <mshelp:link tabindex="0" errorurl="../common/badjump.htm" keywords="security.security_attributes">SECURITY_ATTRIBUTES</mshelp:link> structure that determines whether the returned handle can be inherited by child processes. If lpProcessAttributes is NULL, the handle cannot be inherited. The lpSecurityDescriptor member of the structure specifies a security descriptor for the new process. If lpProcessAttributes is NULL or lpSecurityDescriptor is NULL, the process gets a default security descriptor. The ACLs in the default security descriptor for a process come from the primary token of the creator.
<b>Windows XP/2000/NT:  </b>The ACLs in the default security descriptor for a process come from the primary or impersonation token of the creator. This behavior changed with Windows XP SP2 and Windows Server 2003.
lpThreadAttributes
[in] Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpThreadAttributes is NULL, the handle cannot be inherited. The lpSecurityDescriptor member of the structure specifies a security descriptor for the main thread. If lpThreadAttributes is NULL or lpSecurityDescriptor is NULL, the thread gets a default security descriptor. The ACLs in the default security descriptor for a thread come from the process token.
<b>Windows XP/2000/NT:  </b>The ACLs in the default security descriptor for a thread come from the primary or impersonation token of the creator. This behavior changed with Windows XP SP2 and Windows Server 2003.
bInheritHandles
[in] If this parameter TRUE, each inheritable handle in the calling process is inherited by the new process. If the parameter is FALSE, the handles are not inherited. Note that inherited handles have the same value and access rights as the original handles.
dwCreationFlags
[in] Flags that control the priority class and the creation of the process. For a list of values, see Process Creation Flags. This parameter also controls the new process's priority class, which is used to determine the scheduling priorities of the process's threads. For a list of values, see GetPriorityClass. If none of the priority class flags is specified, the priority class defaults to NORMAL_PRIORITY_CLASS unless the priority class of the creating process is IDLE_PRIORITY_CLASS or BELOW_NORMAL_PRIORITY_CLASS. In this case, the child process receives the default priority class of the calling process.
lpEnvironment
[in] Pointer to an environment block for the new process. If this parameter is NULL, the new process uses the environment of the calling process. An environment block consists of a null-terminated block of null-terminated strings. Each string is in the form:
name=value
Because the equal sign is used as a separator, it must not be used in the name of an environment variable.
An environment block can contain either Unicode or ANSI characters. If the environment block pointed to by lpEnvironment contains Unicode characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
Note that an ANSI environment block is terminated by two zero bytes: one for the last string, one more to terminate the block. A Unicode environment block is terminated by four zero bytes: two for the last string, two more to terminate the block.
lpCurrentDirectory
[in] Pointer to a null-terminated string that specifies the full path to the current directory for the process. The string can also specify a UNC path. If this parameter is NULL, the new process will have the same current drive and directory as the calling process. (This feature is provided primarily for shells that need to start an application and specify its initial drive and working directory.)
lpStartupInfo
[in] Pointer to a STARTUPINFO structure that specifies the window station, desktop, standard handles, and appearance of the main window for the new process.
lpProcessInformation
[out] Pointer to a PROCESS_INFORMATION structure that receives identification information about the new process. Handles in PROCESS_INFORMATION must be closed with <mshelp:link tabindex="0" errorurl="../common/badjump.htm" keywords="base.closehandle">CloseHandle</mshelp:link> when they are no longer needed.

Return Values

If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call <mshelp:link tabindex="0" errorurl="../common/badjump.htm" keywords="base.getlasterror">GetLastError</mshelp:link>.

Remarks

The process is assigned a process identifier. The identifier is valid until the process terminates. It can be used to identify the process, or specified in the OpenProcess function to open a handle to the process. The initial thread in the process is also assigned a thread identifier. It can be specified in the OpenThread function to open a handle to the thread. The identifier is valid until the thread terminates and can be used to uniquely identify the thread within the system. These identifiers are returned in the PROCESS_INFORMATION structure.
The name of the executable in the command line that the operating system provides to a process is not necessarily identical to that in the command line that the calling process gives to the CreateProcess function. The operating system may prepend a fully qualified path to an executable name that is provided without a fully qualified path.
The calling thread can use the WaitForInputIdle function to wait until the new process has finished its initialization and is waiting for user input with no input pending. This can be useful for synchronization between parent and child processes, because CreateProcess returns without waiting for the new process to finish its initialization. For example, the creating process would use WaitForInputIdle before trying to find a window associated with the new process.
The preferred way to shut down a process is by using the ExitProcess function, because this function sends notification of approaching termination to all DLLs attached to the process. Other means of shutting down a process do not notify the attached DLLs. Note that when a thread calls ExitProcess, other threads of the process are terminated without an opportunity to execute any additional code (including the thread termination code of attached DLLs). For more information, see Terminating a Process.
A parent process can directly alter the environment variables of a child process during process creation. This is the only situation when a process can directly change the environment settings of another process. For more information, see Changing Environment Variables.
If an application provides an environment block, the current directory information of the system drives is not automatically propagated to the new process. For example, there is an environment variable named =C: whose value is the current directory on drive C. An application must manually pass the current directory information to the new process. To do so, the application must explicitly create these environment variable strings, sort them alphabetically (because the system uses a sorted environment), and put them into the environment block. Typically, they will go at the front of the environment block, due to the environment block sort order.
One way to obtain the current directory information for a drive X is to call <mshelp:link tabindex="0" errorurl="../common/badjump.htm" keywords="_win32_getfullpathname">GetFullPathName</mshelp:link>("X:",. .). That avoids an application having to scan the environment block. If the full path returned is X:\, there is no need to pass that value on as environment data, since the root directory is the default current directory for drive X of a new process.
When a process is created with CREATE_NEW_PROCESS_GROUP specified, an implicit call to SetConsoleCtrlHandler(NULL,TRUE) is made on behalf of the new process; this means that the new process has CTRL+C disabled. This lets shells handle CTRL+C themselves, and selectively pass that signal on to sub-processes. CTRL+BREAK is not disabled, and may be used to interrupt the process/process group.

Security Remarks


The first parameter, lpApplicationName, can be NULL, in which case the executable name must be in the white space-delimited string pointed to by lpCommandLine. If the executable or path name has a space in it, there is a risk that a different executable could be run because of the way the function parses spaces. The following example is dangerous because the function will attempt to run "Program.exe", if it exists, instead of "MyApp.exe".

CreateProcess(NULL, "C:\\Program Files\\MyApp", ...)
If a malicious user were to create an application called "Program.exe" on a system, any program that incorrectly calls CreateProcess using the Program Files directory will run this application instead of the intended application.
To avoid this problem, do not pass NULL for lpApplicationName. If you do pass NULL for lpApplicationName, use quotation marks around the executable path in lpCommandLine, as shown in the example below.

CreateProcess(NULL, "\"C:\\Program Files\\MyApp.exe\" -L -S", ...)
/////////////////////////////////////////////////////////////////////////////////////////////

위의 내용만 보고 함수를 수행시킬 수 있다면......... 아래 글은 안봐도 된다.
(당신은 고수 ㅡㅡ)

일단 함수에 대한 인자부터 파악을 잘 해야한다. 인자에 대한 설명은 생략
(영어 번역 하여 적기 귀찮음 ㅡㅡ)

MSDN에 나와있는 예제 소스도 살펴보자.

/////////////////////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include <stdio.h>

void main( VOID )
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    // Start the child process.
    if( !CreateProcess( NULL,   // No module name (use command line).
        TEXT("MyChildProcess"), // Command line.
        NULL,             // Process handle not inheritable.
        NULL,             // Thread handle not inheritable.
        FALSE,            // Set handle inheritance to FALSE.
        0,                // No creation flags.
        NULL,             // Use parent's environment block.
        NULL,             // Use parent's starting directory.
        &si,              // Pointer to STARTUPINFO structure.
        &pi )             // Pointer to PROCESS_INFORMATION structure.
    )
    {
        printf( "CreateProcess failed (%d).\n", GetLastError() );
        return;
    }

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
}

/////////////////////////////////////////////////////////////////////////////////////////////

이 코드를 수행시켜서 제대로 동작한다면... 삽질은 안했겠지 ㄷㄷ

위의 코드에서 참고할 만한 내용은 Process 생성 후 WaitForSingleObject를 이용하여 대기하고

그 이후 종료한 뒤의 핸들 처리 흐름뿐이다.

실제로 CreateProcess의 함수 사용에 대한 내용은 알수가 없었다.

"MyChildProcess" <- 이게 도대체 어디서 튀어나온건지 ㄷㄷ

그래서 나만의 방식으로 간단히 정리를 하고자 한다.

일단 함수에 대한 설명부터 -0-

(꼭 필요한 인자에 대한 설명만~~)

BOOL CreateProcess(
// 실행파일에 대한 절대 경로를 포함한 파일 이름
  LPCTSTR lpApplicationName,
// 실행파일의 이름 및 인자들
  LPTSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,  LPSECURITY_ATTRIBUTES lpThreadAttributes,  BOOL bInheritHandles,  DWORD dwCreationFlags,  LPVOID lpEnvironment,
// 실행파일이 있는 절대 경로

  LPCTSTR lpCurrentDirectory,
// STARTUPINFO 구조체 변수
  LPSTARTUPINFO lpStartupInfo,
// PROCESS_INFORMATION 구조체 변수
  LPPROCESS_INFORMATION lpProcessInformation 
);

감이 오는가..... 위의 예제나 웹에서 찾은 자료들로는 첫번째와 두번째 인자에 대한 내용이 부실하다.

하나가 NULL로 값을 주거나, 같은 값으로 인자를 주거나 그런 경우가 있었다.

그리 해서는 실행이 잘 안됐다.

그래서 바로 위에서 주석을 달은 인자에 대해서 정확히 작성을 하였다.

자 이제 실제로 예를 들은 코드다.

(역시 예제로 하는 설명이 짱!!)

/////////////////////////////////////////////////////////////////////////////////////////////

// 실행파일이 있는 절대경로를 포함한 실행파일 이름
WCHAR * pPath = _T("C:\\Program Files\\VideoLAN\\VLC\\vlc.exe");
// 실행파일 및 필요한 인자들
// 여기는 argv[0], argv[1], .... 이런식으로 들어간다고 생각하면 편하다.
// argv[0]이 실행파일이고 argv[1]부터는 인자가 할당된다.
// 띄어쓰기에 주의!! (밑의 문장은 두 군대의 띄어쓰기가 있다. 3개의 단어이고 2개가 vlc.exe 실행파일의 옵션이다.
// 문장 - 실행파일이름  플레이할영상  플레이옵션(udp전송) <- 이렇게 두 군데 띄어쓰기다
WCHAR * pCommand = _T("vlc.exe E:\\eMule\\Download\\movie\\cd1.avi :sout=#transcode{acodec=mpga,ab=192,channels=2}:duplicate{dst=display,dst=std{access=udp,mux=ts,dst=203.253.131.240:1234}}");
// 실행파일이 존재하는 절대경로
 WCHAR * pDir = _T("C:\\Program Files\\VideoLan\\VLC\\");

 STARTUPINFO si;
 PROCESS_INFORMATION pi;

 ZeroMemory( &si, sizeof(si) );
 ZeroMemory( &pi, sizeof(pi) );
 si.cb = sizeof(si);

 if(!CreateProcess(pPath, pCommand, NULL, NULL, FALSE, 0, NULL, pDir, &si, &pi))
 {
      AfxMessageBox(_T("createprocess error"));
      return;
 }

/////////////////////////////////////////////////////////////////////////////////////////////

위와 같이 해서 수행에 성공하였다 ^0^

4시간 삽질의 결정체 ㄷㄷ

여기서 잠시 태클을 걸만한 내용도 있다.

꼭 위와 같이 해야만 실행이 되는가? 라는 질문이다.

답은 물론 아니다.

notepad나 calc, editplus 등등 다른 프로그램에 대해서는 위와 같이 꼭 안해줘도

수행이 되었었다.....

그래서 더 vlc의 수행이 힘들었었다.

위의 코드는 vlc 수행에 대한 내용을 기본으로 하고 있다.

----------

VLC 실행 Command-line 에 대한 설명은 아래글을 참조하면 된다.




https://wiki.videolan.org/VLC_command-line_help

댓글 없음:

댓글 쓰기