xref: /reactos/base/system/rundll32/rundll32.c (revision 84344399)
1 /*
2  * ReactOS rundll32
3  * Copyright (C) 2003-2004 ReactOS Team
4  *
5  * COPYRIGHT:       See COPYING in the top level directory
6  * PROJECT:         ReactOS rundll32.exe
7  * FILE:            base/system/rundll32/rundll32.c
8  * PURPOSE:         Run a DLL as a program
9  * PROGRAMMER:      ShadowFlare (blakflare@hotmail.com)
10  */
11 
12 // Both UNICODE and _UNICODE must be either defined or undefined
13 // because some headers use UNICODE and others use _UNICODE
14 #ifdef UNICODE
15 #ifndef _UNICODE
16 #define _UNICODE
17 #endif
18 #else
19 #ifdef _UNICODE
20 #define UNICODE
21 #endif
22 #endif
23 
24 #define WIN32_NO_STATUS
25 #include <stdarg.h>
26 #include <stdlib.h>
27 #include <windef.h>
28 #include <winbase.h>
29 #include <winnls.h>
30 #include <winuser.h>
31 #include <tchar.h>
32 #include <undocuser.h> // For WM_POPUPSYSTEMMENU
33 
34 #include "resource.h"
35 
36 typedef int (WINAPI *DllWinMainW)(
37   HWND hWnd,
38   HINSTANCE hInstance,
39   LPWSTR lpwCmdLine,
40   int nCmdShow
41 );
42 typedef int (WINAPI *DllWinMainA)(
43   HWND hWnd,
44   HINSTANCE hInstance,
45   LPSTR lpCmdLine,
46   int nCmdShow
47 );
48 
49 /*
50 LPCTSTR DllNotLoaded = _T("LoadLibrary failed to load \"%s\"");
51 LPCTSTR MissingEntry = _T("Missing entry point:%s\nIn %s");
52 */
53 LPCTSTR rundll32_wtitle = _T("rundll32");
54 LPCTSTR rundll32_wclass = _T("RunDLL");
55 
56 TCHAR ModuleFileName[MAX_PATH+1];
57 LPTSTR ModuleTitle;
58 
59 
60 // CommandLineToArgv converts a command-line string to argc and
61 // argv similar to the ones in the standard main function.
62 // This is a specialized version coded specifically for rundll32
63 // and is not intended to be used in any other program.
64 LPTSTR *WINAPI CommandLineToArgv(LPCTSTR lpCmdLine, int *lpArgc)
65 {
66     LPTSTR *argv, lpSrc, lpDest, lpArg;
67     int argc, nBSlash, nNames;
68     BOOL bInQuotes, bFirstChar;
69 
70     // If null was passed in for lpCmdLine, there are no arguments
71     if (!lpCmdLine) {
72         if (lpArgc)
73             *lpArgc = 0;
74         return 0;
75     }
76 
77     lpSrc = (LPTSTR)lpCmdLine;
78     // Skip spaces at beginning
79     while (*lpSrc == _T(' ') || *lpSrc == _T('\t'))
80         lpSrc++;
81 
82     // If command-line starts with null, there are no arguments
83     if (*lpSrc == 0) {
84         if (lpArgc)
85             *lpArgc = 0;
86         return 0;
87     }
88 
89     lpArg = lpSrc;
90     argc = 0;
91     nBSlash = 0;
92     bInQuotes = FALSE;
93     bFirstChar = TRUE;
94     nNames = 0;
95 
96     // Count the number of arguments
97     while (nNames < 4) {
98         if (*lpSrc == 0 || (*lpSrc == _T(',') && nNames == 2) || ((*lpSrc == _T(' ') || *lpSrc == _T('\t')) && !bInQuotes)) {
99             // Whitespace not enclosed in quotes signals the start of another argument
100             argc++;
101 
102             // Skip whitespace between arguments
103             while (*lpSrc == _T(' ') || *lpSrc == _T('\t') || (*lpSrc == _T(',') && nNames == 2))
104                 lpSrc++;
105             if (*lpSrc == 0)
106                 break;
107             if (nNames >= 3) {
108                 // Increment the count for the last argument
109                 argc++;
110                 break;
111             }
112             nBSlash = 0;
113             bFirstChar = TRUE;
114             continue;
115         }
116         else if (*lpSrc == _T('\\')) {
117             // Count consecutive backslashes
118             nBSlash++;
119             bFirstChar = FALSE;
120         }
121         else if (*lpSrc == _T('\"') && !(nBSlash & 1)) {
122             // Open or close quotes
123             bInQuotes = !bInQuotes;
124             nBSlash = 0;
125         }
126         else {
127             // Some other character
128             nBSlash = 0;
129             if (bFirstChar && ((*lpSrc != _T('/') && nNames <= 1) || nNames > 1))
130                 nNames++;
131             bFirstChar = FALSE;
132         }
133         lpSrc++;
134     }
135 
136     // Allocate space for the pointers in argv and the strings in one block
137     argv = (LPTSTR *)malloc(argc * sizeof(LPTSTR) + (_tcslen(lpArg) + 1) * sizeof(TCHAR));
138 
139     if (!argv) {
140         // Memory allocation failed
141         if (lpArgc)
142             *lpArgc = 0;
143         return 0;
144     }
145 
146     lpSrc = lpArg;
147     lpDest = lpArg = (LPTSTR)(argv + argc);
148     argc = 0;
149     nBSlash = 0;
150     bInQuotes = FALSE;
151     bFirstChar = TRUE;
152     nNames = 0;
153 
154     // Fill the argument array
155     while (nNames < 4) {
156         if (*lpSrc == 0 || (*lpSrc == _T(',') && nNames == 2) || ((*lpSrc == _T(' ') || *lpSrc == _T('\t')) && !bInQuotes)) {
157             // Whitespace not enclosed in quotes signals the start of another argument
158             // Null-terminate argument
159             *lpDest++ = 0;
160             argv[argc++] = lpArg;
161 
162             // Skip whitespace between arguments
163             while (*lpSrc == _T(' ') || *lpSrc == _T('\t') || (*lpSrc == _T(',') && nNames == 2))
164                 lpSrc++;
165             if (*lpSrc == 0)
166                 break;
167             lpArg = lpDest;
168             if (nNames >= 3) {
169                 // Copy the rest of the command-line to the last argument
170                 argv[argc++] = lpArg;
171                 _tcscpy(lpArg,lpSrc);
172                 break;
173             }
174             nBSlash = 0;
175             bFirstChar = TRUE;
176             continue;
177         }
178         else if (*lpSrc == _T('\\')) {
179             *lpDest++ = _T('\\');
180             lpSrc++;
181 
182             // Count consecutive backslashes
183             nBSlash++;
184             bFirstChar = FALSE;
185         }
186         else if (*lpSrc == _T('\"')) {
187             if (!(nBSlash & 1)) {
188                 // If an even number of backslashes are before the quotes,
189                 // the quotes don't go in the output
190                 lpDest -= nBSlash / 2;
191                 bInQuotes = !bInQuotes;
192             }
193             else {
194                 // If an odd number of backslashes are before the quotes,
195                 // output a quote
196                 lpDest -= (nBSlash + 1) / 2;
197                 *lpDest++ = _T('\"');
198                 bFirstChar = FALSE;
199             }
200             lpSrc++;
201             nBSlash = 0;
202         }
203         else {
204             // Copy other characters
205             if (bFirstChar && ((*lpSrc != _T('/') && nNames <= 1) || nNames > 1))
206                 nNames++;
207             *lpDest++ = *lpSrc++;
208             nBSlash = 0;
209             bFirstChar = FALSE;
210         }
211     }
212 
213     if (lpArgc)
214         *lpArgc = argc;
215     return argv;
216 }
217 
218 void GetModuleTitle(void)
219 {
220     LPTSTR lpStr;
221 
222     GetModuleFileName(0,ModuleFileName,MAX_PATH);
223     ModuleTitle = ModuleFileName;
224 
225     for (lpStr = ModuleFileName;*lpStr;lpStr++) {
226         if (*lpStr == _T('\\'))
227             ModuleTitle = lpStr+1;
228     }
229 
230     for (lpStr = ModuleTitle;*lpStr;lpStr++) {
231         if (_tcsicmp(lpStr,_T(".exe"))==0)
232             break;
233     }
234 
235     *lpStr = 0;
236 }
237 
238 // The macro ConvertToWideChar takes a tstring parameter and returns
239 // a pointer to a unicode string.  A conversion is performed if
240 // necessary.  FreeConvertedWideChar string should be used on the
241 // return value of ConvertToWideChar when the string is no longer
242 // needed.  The original string or the string that is returned
243 // should not be modified until FreeConvertedWideChar has been called.
244 #ifdef UNICODE
245 #define ConvertToWideChar(lptString) (lptString)
246 #define FreeConvertedWideChar(lpwString)
247 #else
248 
249 LPWSTR ConvertToWideChar(LPCSTR lpString)
250 {
251     LPWSTR lpwString;
252     size_t nStrLen;
253 
254     nStrLen = strlen(lpString) + 1;
255 
256     lpwString = (LPWSTR)malloc(nStrLen * sizeof(WCHAR));
257     MultiByteToWideChar(0,0,lpString,nStrLen,lpwString,nStrLen);
258 
259     return lpwString;
260 }
261 
262 #define FreeConvertedWideChar(lpwString) free(lpwString)
263 #endif
264 
265 // The macro ConvertToMultiByte takes a tstring parameter and returns
266 // a pointer to an ansi string.  A conversion is performed if
267 // necessary.  FreeConvertedMultiByte string should be used on the
268 // return value of ConvertToMultiByte when the string is no longer
269 // needed.  The original string or the string that is returned
270 // should not be modified until FreeConvertedMultiByte has been called.
271 #ifdef UNICODE
272 #define ConvertToMultiByte(lptString) DuplicateToMultiByte(lptString,0)
273 #define FreeConvertedMultiByte(lpaString) free(lpaString)
274 #else
275 #define ConvertToMultiByte(lptString) (lptString)
276 #define FreeConvertedMultiByte(lpaString)
277 #endif
278 
279 // DuplicateToMultiByte takes a tstring parameter and always returns
280 // a pointer to a duplicate ansi string.  If nBufferSize is zero,
281 // the buffer length is the exact size of the string plus the
282 // terminating null.  If nBufferSize is nonzero, the buffer length
283 // is equal to nBufferSize.  As with strdup, free should be called
284 // for the returned string when it is no longer needed.
285 LPSTR DuplicateToMultiByte(LPCTSTR lptString, size_t nBufferSize)
286 {
287     LPSTR lpString;
288     size_t nStrLen;
289 
290     nStrLen = _tcslen(lptString) + 1;
291     if (nBufferSize == 0) nBufferSize = nStrLen;
292 
293     lpString = (LPSTR)malloc(nBufferSize);
294 #ifdef UNICODE
295     WideCharToMultiByte(0,0,lptString,nStrLen,lpString,nBufferSize,0,0);
296 #else
297     strncpy(lpString,lptString,nBufferSize);
298 #endif
299 
300     return lpString;
301 }
302 
303 typedef struct
304 {
305     HWND hwndOwner;
306     HWND hwndTarget;
307 } FIND_OWNED, *PFIND_OWNED;
308 
309 static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
310 {
311     PFIND_OWNED pFindOwned = (PFIND_OWNED)lParam;
312     if (pFindOwned->hwndOwner == GetWindow(hwnd, GW_OWNER))
313     {
314         pFindOwned->hwndTarget = hwnd;
315         return FALSE;
316     }
317     return TRUE;
318 }
319 
320 LRESULT CALLBACK EmptyWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
321 {
322     switch (uMsg)
323     {
324         case WM_POPUPSYSTEMMENU:
325         case WM_SYSCOMMAND:
326         {
327             /* Find the owned window */
328             FIND_OWNED FindOwned = { hWnd, NULL };
329             EnumWindows(EnumWindowsProc, (LPARAM)&FindOwned);
330             /* Forward message */
331             if (FindOwned.hwndTarget)
332                 PostMessageW(FindOwned.hwndTarget, uMsg, wParam, lParam);
333             break;
334         }
335         case WM_ACTIVATE:
336         {
337             /* Find the owned window */
338             FIND_OWNED FindOwned = { hWnd, NULL };
339             EnumWindows(EnumWindowsProc, (LPARAM)&FindOwned);
340             if (FindOwned.hwndTarget)
341             {
342                 if (LOWORD(wParam) != WA_INACTIVE) /* To be activated */
343                 {
344                     SetActiveWindow(FindOwned.hwndTarget);
345                     return 0;
346                 }
347             }
348             /* Fall through */
349         }
350         default:
351             return DefWindowProc(hWnd, uMsg, wParam, lParam);
352     }
353     return 0;
354 }
355 
356 // Registers a minimal window class for passing to the dll function
357 BOOL RegisterBlankClass(HINSTANCE hInstance, HINSTANCE hPrevInstance)
358 {
359     WNDCLASSEX wcex;
360 
361     wcex.cbSize = sizeof(WNDCLASSEX);
362     wcex.style         = 0;
363     wcex.lpfnWndProc   = EmptyWindowProc;
364     wcex.cbClsExtra    = 0;
365     wcex.cbWndExtra    = 0;
366     wcex.hInstance     = hInstance;
367     wcex.hIcon         = 0;
368     wcex.hCursor       = 0;
369     wcex.hbrBackground = 0;
370     wcex.lpszMenuName  = 0;
371     wcex.lpszClassName = rundll32_wclass;
372     wcex.hIconSm       = 0;
373 
374     return (RegisterClassEx(&wcex) != (ATOM)0);
375 }
376 
377 int WINAPI _tWinMain(
378   HINSTANCE hInstance,
379   HINSTANCE hPrevInstance,
380   LPTSTR lpCmdLine,
381   int nCmdShow
382 )
383 {
384     int argc;
385     TCHAR szMsg[RC_STRING_MAX_SIZE];
386 
387     LPTSTR *argv;
388     LPTSTR lptCmdLine,lptDllName,lptFuncName,lptMsgBuffer;
389     WCHAR ResolvedFile[MAX_PATH + 1] = {0}, *lpManifestName;
390     LPSTR lpFuncName,lpaCmdLine;
391     LPWSTR lpwCmdLine;
392     HMODULE hDll;
393     DllWinMainW fnDllWinMainW;
394     DllWinMainA fnDllWinMainA;
395     HWND hWindow;
396     int i;
397     size_t nStrLen;
398 
399     ACTCTXW ActCtx = {sizeof(ACTCTX), ACTCTX_FLAG_RESOURCE_NAME_VALID};
400     HANDLE hActCtx;
401     ULONG_PTR cookie;
402     BOOL bActivated;
403 
404     // Get command-line in argc-argv format
405     argv = CommandLineToArgv(GetCommandLine(),&argc);
406 
407     // Skip all beginning arguments starting with a slash (/)
408     for (i = 1; i < argc; i++)
409         if (*argv[i] != _T('/')) break;
410 
411     // If no dll was specified, there is nothing to do
412     if (i >= argc) {
413         if (argv) free(argv);
414         return 0;
415     }
416 
417     lptDllName = argv[i++];
418 
419     // The next argument, which specifies the name of the dll function,
420     // can either have a comma between it and the dll filename or a space.
421     // Using a comma here is the preferred method
422     if (i < argc)
423         lptFuncName = argv[i++];
424     else
425         lptFuncName = _T("");
426 
427     // If no function name was specified, nothing needs to be done
428     if (!*lptFuncName) {
429         if (argv) free(argv);
430         return 0;
431     }
432 
433     // The rest of the arguments will be passed to dll function
434     if (i < argc)
435         lptCmdLine = argv[i];
436     else
437         lptCmdLine = _T("");
438 
439     lpManifestName = lptDllName;
440     if (GetFileAttributesW(lptDllName) == INVALID_FILE_ATTRIBUTES)
441     {
442         LPWSTR FilePart = NULL;
443         if (SearchPathW(NULL, lptDllName, NULL, _countof(ResolvedFile) - 1, ResolvedFile, &FilePart))
444         {
445             lpManifestName = ResolvedFile;
446         }
447     }
448 
449     // FIXME: If there is a .manifest file next to the input file, we should use that instead!
450     ActCtx.lpSource = lpManifestName;
451     ActCtx.lpResourceName = (LPCWSTR)123;
452     hActCtx = CreateActCtx(&ActCtx);
453     bActivated = (hActCtx != INVALID_HANDLE_VALUE ? ActivateActCtx(hActCtx, &cookie) : FALSE);
454 
455     // Everything is all setup, so load the dll now
456     hDll = LoadLibrary(lptDllName);
457     if (hDll) {
458         nStrLen = _tcslen(lptFuncName);
459         // Make a non-unicode version of the function name,
460         // since that is all GetProcAddress accepts
461         lpFuncName = DuplicateToMultiByte(lptFuncName,nStrLen + 2);
462 
463 #ifdef UNICODE
464         lpFuncName[nStrLen] = 'W';
465         lpFuncName[nStrLen+1] = 0;
466         // Get address of unicode version of the dll function if it exists
467         fnDllWinMainW = (DllWinMainW)GetProcAddress(hDll,lpFuncName);
468         fnDllWinMainA = 0;
469         if (!fnDllWinMainW) {
470             // If no unicode function was found, get the address of the non-unicode function
471             lpFuncName[nStrLen] = 'A';
472             fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
473             if (!fnDllWinMainA) {
474                 // If first non-unicode function was not found, get the address
475                 // of the other non-unicode function
476                 lpFuncName[nStrLen] = 0;
477                 fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
478             }
479         }
480 #else
481         // Get address of non-unicode version of the dll function if it exists
482         fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
483         fnDllWinMainW = 0;
484         if (!fnDllWinMainA) {
485             // If first non-unicode function was not found, get the address
486             // of the other non-unicode function
487             lpFuncName[nStrLen] = 'A';
488             lpFuncName[nStrLen+1] = 0;
489             fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
490             if (!fnDllWinMainA) {
491                 // If non-unicode function was not found, get the address of the unicode function
492                 lpFuncName[nStrLen] = 'W';
493                 fnDllWinMainW = (DllWinMainW)GetProcAddress(hDll,lpFuncName);
494             }
495         }
496 #endif
497 
498         free(lpFuncName);
499 
500         if (!RegisterBlankClass(hInstance, hPrevInstance))
501         {
502             FreeLibrary(hDll);
503             if (bActivated)
504                 DeactivateActCtx(0, cookie);
505             return 0;
506         }
507         // Create a window so we can pass a window handle to
508         // the dll function; this is required
509         hWindow = CreateWindowEx(0,rundll32_wclass,rundll32_wtitle,0,CW_USEDEFAULT,0,CW_USEDEFAULT,0,0,0,hInstance,0);
510 
511         if (fnDllWinMainW) {
512             // Convert the command-line string to unicode and call the dll function
513             lpwCmdLine = ConvertToWideChar(lptCmdLine);
514             fnDllWinMainW(hWindow,hInstance,lpwCmdLine,nCmdShow);
515             FreeConvertedWideChar(lpwCmdLine);
516         }
517         else if (fnDllWinMainA) {
518             // Convert the command-line string to ansi and call the dll function
519             lpaCmdLine = ConvertToMultiByte(lptCmdLine);
520             fnDllWinMainA(hWindow,hInstance,lpaCmdLine,nCmdShow);
521             FreeConvertedMultiByte(lpaCmdLine);
522         }
523         else {
524             // The specified dll function was not found; display an error message
525             GetModuleTitle();
526             LoadString( GetModuleHandle(NULL), IDS_MissingEntry, (LPTSTR) szMsg,RC_STRING_MAX_SIZE);
527 
528             lptMsgBuffer = (LPTSTR)malloc((_tcslen(szMsg) - 4 + _tcslen(lptFuncName) + _tcslen(lptDllName) + 1) * sizeof(TCHAR));
529             _stprintf(lptMsgBuffer,szMsg,lptFuncName,lptDllName);
530             MessageBox(0,lptMsgBuffer,ModuleTitle,MB_ICONERROR);
531             free(lptMsgBuffer);
532         }
533 
534         DestroyWindow(hWindow);
535         UnregisterClass(rundll32_wclass,hInstance);
536 
537         // The dll function has finished executing, so unload it
538         FreeLibrary(hDll);
539     }
540     else {
541         // The dll could not be loaded; display an error message
542         GetModuleTitle();
543         LoadString( GetModuleHandle(NULL), IDS_DllNotLoaded, (LPTSTR) szMsg,RC_STRING_MAX_SIZE);
544 
545         lptMsgBuffer = (LPTSTR)malloc((_tcslen(szMsg) - 2 + _tcslen(lptDllName) + 1) * sizeof(TCHAR));
546         _stprintf(lptMsgBuffer,szMsg,lptDllName);
547 
548         MessageBox(0,lptMsgBuffer,ModuleTitle,MB_ICONERROR);
549         free(lptMsgBuffer);
550     }
551 
552     if (bActivated)
553         DeactivateActCtx(0, cookie);
554 
555     if (argv) free(argv);
556     return 0; /* rundll32 always returns 0! */
557 }
558 
559