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