[55400] | 1 | /* $Id: VBoxCAPIGlue.c 98694 2023-02-22 23:08:43Z vboxsync $ */
|
---|
[16832] | 2 | /** @file
|
---|
[50183] | 3 | * Glue code for dynamically linking to VBoxCAPI.
|
---|
[16832] | 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2008-2023 Oracle and/or its affiliates.
|
---|
[16832] | 8 | *
|
---|
[69377] | 9 | * Permission is hereby granted, free of charge, to any person
|
---|
| 10 | * obtaining a copy of this software and associated documentation
|
---|
| 11 | * files (the "Software"), to deal in the Software without
|
---|
| 12 | * restriction, including without limitation the rights to use,
|
---|
| 13 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
|
---|
| 14 | * copies of the Software, and to permit persons to whom the
|
---|
| 15 | * Software is furnished to do so, subject to the following
|
---|
| 16 | * conditions:
|
---|
[16832] | 17 | *
|
---|
[69377] | 18 | * The above copyright notice and this permission notice shall be
|
---|
| 19 | * included in all copies or substantial portions of the Software.
|
---|
[16837] | 20 | *
|
---|
[69377] | 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
---|
| 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
---|
| 23 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
---|
| 24 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
---|
| 25 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
---|
| 26 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
---|
| 27 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
---|
[18241] | 28 | * OTHER DEALINGS IN THE SOFTWARE.
|
---|
[16832] | 29 | */
|
---|
| 30 |
|
---|
[57358] | 31 |
|
---|
| 32 | /*********************************************************************************************************************************
|
---|
| 33 | * Header Files *
|
---|
| 34 | *********************************************************************************************************************************/
|
---|
[64448] | 35 | /* NOTE: do NOT use any include files here which are only available in the
|
---|
| 36 | * VirtualBox tree, e.g. iprt. They are not available in the SDK, which is
|
---|
| 37 | * where this file will provided as source code and has to be compilable. */
|
---|
[50183] | 38 | #include "VBoxCAPIGlue.h"
|
---|
| 39 |
|
---|
[16832] | 40 | #include <stdio.h>
|
---|
| 41 | #include <string.h>
|
---|
| 42 | #include <stdlib.h>
|
---|
[19028] | 43 | #include <stdarg.h>
|
---|
[64449] | 44 | #ifdef WIN32
|
---|
| 45 | # define _INTPTR 2 /* on Windows stdint.h compares this in #if, causing warnings if not defined */
|
---|
| 46 | #endif /* WIN32 */
|
---|
[64448] | 47 | #include <stdint.h>
|
---|
[50183] | 48 | #ifndef WIN32
|
---|
| 49 | # include <dlfcn.h>
|
---|
| 50 | # include <pthread.h>
|
---|
[98694] | 51 | #else /* WIN32 */
|
---|
[64448] | 52 | # include <Windows.h>
|
---|
[50183] | 53 | #endif /* WIN32 */
|
---|
[16832] | 54 |
|
---|
| 55 |
|
---|
[57358] | 56 | /*********************************************************************************************************************************
|
---|
| 57 | * Defined Constants And Macros *
|
---|
| 58 | *********************************************************************************************************************************/
|
---|
[98694] | 59 | #if defined(__i386__) || defined(_M_IX86) || defined(__X86__)
|
---|
| 60 | /** This is a 32-bit process running against a 64-bit VBox installation.
|
---|
| 61 | * Since there are only 64-bit VBox now, we don't need any runtime checks. */
|
---|
| 62 | # define IS_32_ON_64
|
---|
| 63 | #endif
|
---|
| 64 |
|
---|
[18923] | 65 | #if defined(__linux__) || defined(__linux_gnu__) || defined(__sun__) || defined(__FreeBSD__)
|
---|
[98694] | 66 | # ifndef IS_32_ON_64
|
---|
| 67 | # define DYNLIB_NAME "VBoxXPCOMC.so"
|
---|
| 68 | # else
|
---|
| 69 | # define DYNLIB_NAME "VBoxCAPI-x86.so"
|
---|
| 70 | # endif
|
---|
[16832] | 71 | #elif defined(__APPLE__)
|
---|
[98694] | 72 | # ifndef IS_32_ON_64
|
---|
| 73 | # define DYNLIB_NAME "VBoxXPCOMC.dylib"
|
---|
| 74 | # else
|
---|
| 75 | # define DYNLIB_NAME "VBoxCAPI-x86.dylib"
|
---|
| 76 | # endif
|
---|
[50183] | 77 | #elif defined(WIN32)
|
---|
[98694] | 78 | # ifndef IS_32_ON_64
|
---|
| 79 | # define DYNLIB_NAME "VBoxCAPI.dll"
|
---|
| 80 | # else
|
---|
| 81 | # define DYNLIB_NAME "VBoxCAPI-x86.dll"
|
---|
| 82 | # define DYNLIB_SUBDIR "x86"
|
---|
| 83 | # define SLASH_DYNLIB_SUBDIR "\\" DYNLIB_SUBDIR
|
---|
| 84 | # endif
|
---|
[16832] | 85 | #else
|
---|
| 86 | # error "Port me"
|
---|
| 87 | #endif
|
---|
[98694] | 88 | #ifndef DYNLIB_SUBDIR
|
---|
| 89 | # define DYNLIB_SUBDIR ""
|
---|
| 90 | # define SLASH_DYNLIB_SUBDIR ""
|
---|
| 91 | #endif
|
---|
[16832] | 92 |
|
---|
[98694] | 93 | #ifdef WIN32
|
---|
| 94 | # define DIR_SLASH_CH '\\'
|
---|
| 95 | #else
|
---|
| 96 | # define DIR_SLASH_CH '/'
|
---|
| 97 | #endif
|
---|
[18155] | 98 |
|
---|
[98694] | 99 |
|
---|
[57358] | 100 | /*********************************************************************************************************************************
|
---|
| 101 | * Global Variables *
|
---|
| 102 | *********************************************************************************************************************************/
|
---|
[50183] | 103 | /** The so/dynsym/dll handle for VBoxCAPI. */
|
---|
| 104 | #ifndef WIN32
|
---|
| 105 | void *g_hVBoxCAPI = NULL;
|
---|
| 106 | #else /* WIN32 */
|
---|
| 107 | HMODULE g_hVBoxCAPI = NULL;
|
---|
| 108 | #endif /* WIN32 */
|
---|
[16832] | 109 | /** The last load error. */
|
---|
[50183] | 110 | char g_szVBoxErrMsg[256] = "";
|
---|
| 111 | /** Pointer to the VBOXCAPI function table. */
|
---|
| 112 | PCVBOXCAPI g_pVBoxFuncs = NULL;
|
---|
| 113 | /** Pointer to VBoxGetCAPIFunctions for the loaded VBoxCAPI so/dylib/dll. */
|
---|
| 114 | PFNVBOXGETCAPIFUNCTIONS g_pfnGetFunctions = NULL;
|
---|
[16832] | 115 |
|
---|
[49539] | 116 | typedef void FNDUMMY(void);
|
---|
| 117 | typedef FNDUMMY *PFNDUMMY;
|
---|
| 118 | /** Just a dummy global structure containing a bunch of
|
---|
[50183] | 119 | * function pointers to code which is wanted in the link. */
|
---|
| 120 | PFNDUMMY g_apfnVBoxCAPIGlue[] =
|
---|
[49539] | 121 | {
|
---|
[50183] | 122 | #ifndef WIN32
|
---|
| 123 | /* The following link dependency is for helping gdb as it gets hideously
|
---|
| 124 | * confused if the application doesn't drag in pthreads, but uses it. */
|
---|
| 125 | (PFNDUMMY)pthread_create,
|
---|
| 126 | #endif /* !WIN32 */
|
---|
| 127 | NULL
|
---|
[49539] | 128 | };
|
---|
[16832] | 129 |
|
---|
[49539] | 130 |
|
---|
[16832] | 131 | /**
|
---|
[19028] | 132 | * Wrapper for setting g_szVBoxErrMsg. Can be an empty stub.
|
---|
| 133 | *
|
---|
[19052] | 134 | * @param fAlways When 0 the g_szVBoxErrMsg is only set if empty.
|
---|
[19028] | 135 | * @param pszFormat The format string.
|
---|
| 136 | * @param ... The arguments.
|
---|
| 137 | */
|
---|
[19052] | 138 | static void setErrMsg(int fAlways, const char *pszFormat, ...)
|
---|
[19028] | 139 | {
|
---|
[19052] | 140 | if ( fAlways
|
---|
| 141 | || !g_szVBoxErrMsg[0])
|
---|
| 142 | {
|
---|
| 143 | va_list va;
|
---|
| 144 | va_start(va, pszFormat);
|
---|
| 145 | vsnprintf(g_szVBoxErrMsg, sizeof(g_szVBoxErrMsg), pszFormat, va);
|
---|
| 146 | va_end(va);
|
---|
| 147 | }
|
---|
[19028] | 148 | }
|
---|
| 149 |
|
---|
| 150 |
|
---|
| 151 | /**
|
---|
[50183] | 152 | * Try load C API .so/dylib/dll from the specified location and resolve all
|
---|
| 153 | * the symbols we need. Tries both the new style and legacy name.
|
---|
[16832] | 154 | *
|
---|
| 155 | * @returns 0 on success, -1 on failure.
|
---|
[50183] | 156 | * @param pszHome The directory where to try load VBoxCAPI/VBoxXPCOMC
|
---|
| 157 | * from. Can be NULL.
|
---|
[19028] | 158 | * @param fSetAppHome Whether to set the VBOX_APP_HOME env.var. or not
|
---|
| 159 | * (boolean).
|
---|
[16832] | 160 | */
|
---|
[98694] | 161 | static int tryLoadLibrary(const char *pszHome, const char *pszSub, int fSetAppHome)
|
---|
[16832] | 162 | {
|
---|
[98694] | 163 | size_t const cchHome = pszHome ? strlen(pszHome) : 0;
|
---|
| 164 | size_t const cchSub = pszSub ? strlen(pszSub) : 0;
|
---|
| 165 | size_t cbBufNeeded;
|
---|
| 166 | size_t offName;
|
---|
| 167 | char szName[4096];
|
---|
[16832] | 168 |
|
---|
| 169 | /*
|
---|
| 170 | * Construct the full name.
|
---|
| 171 | */
|
---|
[98694] | 172 | cbBufNeeded = cchHome + !!cchHome + cchSub + !!cchSub + sizeof(DYNLIB_NAME);
|
---|
[19028] | 173 | if (cbBufNeeded > sizeof(szName))
|
---|
[16832] | 174 | {
|
---|
[19052] | 175 | setErrMsg(1, "path buffer too small: %u bytes needed",
|
---|
[19028] | 176 | (unsigned)cbBufNeeded);
|
---|
[16832] | 177 | return -1;
|
---|
| 178 | }
|
---|
[98694] | 179 | offName = 0;
|
---|
[18871] | 180 | if (cchHome)
|
---|
| 181 | {
|
---|
[98694] | 182 | memcpy(&szName[offName], pszHome, cchHome);
|
---|
| 183 | offName += cchHome;
|
---|
| 184 | szName[offName++] = DIR_SLASH_CH;
|
---|
[18871] | 185 | }
|
---|
[98694] | 186 | if (cchSub)
|
---|
| 187 | {
|
---|
| 188 | memcpy(&szName[offName], pszSub, cchSub);
|
---|
| 189 | offName += cchSub;
|
---|
| 190 | szName[offName++] = DIR_SLASH_CH;
|
---|
| 191 | }
|
---|
| 192 | memcpy(&szName[offName], DYNLIB_NAME, sizeof(DYNLIB_NAME));
|
---|
[16832] | 193 |
|
---|
| 194 | /*
|
---|
| 195 | * Try load it by that name, setting the VBOX_APP_HOME first (for now).
|
---|
| 196 | * Then resolve and call the function table getter.
|
---|
| 197 | */
|
---|
[18871] | 198 | if (fSetAppHome)
|
---|
[18853] | 199 | {
|
---|
[50183] | 200 | #ifndef WIN32
|
---|
[18871] | 201 | if (pszHome)
|
---|
| 202 | setenv("VBOX_APP_HOME", pszHome, 1 /* always override */);
|
---|
| 203 | else
|
---|
| 204 | unsetenv("VBOX_APP_HOME");
|
---|
[50183] | 205 | #endif /* !WIN32 */
|
---|
[18853] | 206 | }
|
---|
[50183] | 207 |
|
---|
| 208 | #ifndef WIN32
|
---|
| 209 | g_hVBoxCAPI = dlopen(szName, RTLD_NOW | RTLD_LOCAL);
|
---|
[54027] | 210 | #else /* WIN32 */
|
---|
[98694] | 211 | g_hVBoxCAPI = LoadLibraryExA(szName, NULL /* hFile */,
|
---|
| 212 | cchHome ? LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32 : 0 /* dwFlags */);
|
---|
| 213 | if (!g_hVBoxCAPI && GetLastError() == ERROR_INVALID_PARAMETER)
|
---|
| 214 | g_hVBoxCAPI = LoadLibraryExA(szName, NULL /* hFile */, 0 /* dwFlags */);
|
---|
[54027] | 215 | #endif /* WIN32 */
|
---|
[50183] | 216 | if (g_hVBoxCAPI)
|
---|
[16832] | 217 | {
|
---|
[50183] | 218 | PFNVBOXGETCAPIFUNCTIONS pfnGetFunctions;
|
---|
[54027] | 219 | #ifndef WIN32
|
---|
[50183] | 220 | pfnGetFunctions = (PFNVBOXGETCAPIFUNCTIONS)(uintptr_t)
|
---|
| 221 | dlsym(g_hVBoxCAPI, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME);
|
---|
[54027] | 222 | # ifdef VBOX_GET_XPCOM_FUNCTIONS_SYMBOL_NAME
|
---|
[50183] | 223 | if (!pfnGetFunctions)
|
---|
| 224 | pfnGetFunctions = (PFNVBOXGETCAPIFUNCTIONS)(uintptr_t)
|
---|
| 225 | dlsym(g_hVBoxCAPI, VBOX_GET_XPCOM_FUNCTIONS_SYMBOL_NAME);
|
---|
[54027] | 226 | # endif /* VBOX_GET_XPCOM_FUNCTIONS_SYMBOL_NAME */
|
---|
| 227 | #else /* WIN32 */
|
---|
| 228 | pfnGetFunctions = (PFNVBOXGETCAPIFUNCTIONS)
|
---|
| 229 | GetProcAddress(g_hVBoxCAPI, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME);
|
---|
| 230 | #endif /* WIN32 */
|
---|
[16832] | 231 | if (pfnGetFunctions)
|
---|
| 232 | {
|
---|
[50183] | 233 | g_pVBoxFuncs = pfnGetFunctions(VBOX_CAPI_VERSION);
|
---|
[16832] | 234 | if (g_pVBoxFuncs)
|
---|
[17677] | 235 | {
|
---|
[98694] | 236 | if ( VBOX_CAPI_MAJOR(g_pVBoxFuncs->uVersion)
|
---|
| 237 | == VBOX_CAPI_MAJOR(VBOX_CAPI_VERSION)
|
---|
| 238 | && VBOX_CAPI_MINOR(g_pVBoxFuncs->uVersion)
|
---|
| 239 | >= VBOX_CAPI_MINOR(VBOX_CAPI_VERSION))
|
---|
[54027] | 240 | {
|
---|
| 241 | g_pfnGetFunctions = pfnGetFunctions;
|
---|
| 242 | return 0;
|
---|
| 243 | }
|
---|
| 244 | setErrMsg(1, "%.80s: pfnGetFunctions(%#x) returned incompatible version %#x",
|
---|
| 245 | szName, VBOX_CAPI_VERSION, g_pVBoxFuncs->uVersion);
|
---|
| 246 | g_pVBoxFuncs = NULL;
|
---|
[17677] | 247 | }
|
---|
[54027] | 248 | else
|
---|
| 249 | {
|
---|
| 250 | /* bail out */
|
---|
| 251 | setErrMsg(1, "%.80s: pfnGetFunctions(%#x) failed",
|
---|
| 252 | szName, VBOX_CAPI_VERSION);
|
---|
| 253 | }
|
---|
[16832] | 254 | }
|
---|
[19028] | 255 | else
|
---|
[54027] | 256 | {
|
---|
| 257 | #ifndef WIN32
|
---|
[19052] | 258 | setErrMsg(1, "dlsym(%.80s/%.32s): %.128s",
|
---|
[50183] | 259 | szName, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME, dlerror());
|
---|
[54027] | 260 | #else /* WIN32 */
|
---|
[50183] | 261 | setErrMsg(1, "GetProcAddress(%.80s/%.32s): %d",
|
---|
| 262 | szName, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME, GetLastError());
|
---|
[54027] | 263 | #endif /* WIN32 */
|
---|
| 264 | }
|
---|
| 265 |
|
---|
| 266 | #ifndef WIN32
|
---|
| 267 | dlclose(g_hVBoxCAPI);
|
---|
| 268 | #else /* WIN32 */
|
---|
[50183] | 269 | FreeLibrary(g_hVBoxCAPI);
|
---|
[54027] | 270 | #endif /* WIN32 */
|
---|
[50183] | 271 | g_hVBoxCAPI = NULL;
|
---|
| 272 | }
|
---|
| 273 | else
|
---|
[54027] | 274 | {
|
---|
| 275 | #ifndef WIN32
|
---|
| 276 | setErrMsg(0, "dlopen(%.80s): %.160s", szName, dlerror());
|
---|
| 277 | #else /* WIN32 */
|
---|
[50183] | 278 | setErrMsg(0, "LoadLibraryEx(%.80s): %d", szName, GetLastError());
|
---|
[54027] | 279 | #endif /* WIN32 */
|
---|
| 280 | }
|
---|
[50183] | 281 |
|
---|
| 282 | return -1;
|
---|
[16832] | 283 | }
|
---|
| 284 |
|
---|
| 285 |
|
---|
| 286 | /**
|
---|
[50183] | 287 | * Tries to locate and load VBoxCAPI.so/dylib/dll, resolving all the related
|
---|
[16832] | 288 | * function pointers.
|
---|
| 289 | *
|
---|
| 290 | * @returns 0 on success, -1 on failure.
|
---|
| 291 | *
|
---|
| 292 | * @remark This should be considered moved into a separate glue library since
|
---|
[50183] | 293 | * its its going to be pretty much the same for any user of VBoxCAPI
|
---|
[16832] | 294 | * and it will just cause trouble to have duplicate versions of this
|
---|
| 295 | * source code all around the place.
|
---|
| 296 | */
|
---|
[17813] | 297 | int VBoxCGlueInit(void)
|
---|
[16832] | 298 | {
|
---|
[50183] | 299 | const char *pszHome;
|
---|
| 300 |
|
---|
| 301 | memset(g_szVBoxErrMsg, 0, sizeof(g_szVBoxErrMsg));
|
---|
| 302 |
|
---|
[16832] | 303 | /*
|
---|
| 304 | * If the user specifies the location, try only that.
|
---|
| 305 | */
|
---|
[50183] | 306 | pszHome = getenv("VBOX_APP_HOME");
|
---|
[16832] | 307 | if (pszHome)
|
---|
[98694] | 308 | return tryLoadLibrary(pszHome, DYNLIB_SUBDIR, 0);
|
---|
[16832] | 309 |
|
---|
| 310 | /*
|
---|
| 311 | * Try the known standard locations.
|
---|
| 312 | */
|
---|
| 313 | #if defined(__gnu__linux__) || defined(__linux__)
|
---|
[98694] | 314 | if (tryLoadLibrary("/opt/VirtualBox", DYNLIB_SUBDIR, 1) == 0)
|
---|
[16832] | 315 | return 0;
|
---|
[98694] | 316 | if (tryLoadLibrary("/usr/lib/virtualbox", DYNLIB_SUBDIR, 1) == 0)
|
---|
[16832] | 317 | return 0;
|
---|
| 318 | #elif defined(__sun__)
|
---|
[98694] | 319 | if (tryLoadLibrary("/opt/VirtualBox/amd64", NULL, 1) == 0)
|
---|
[16832] | 320 | return 0;
|
---|
[98694] | 321 | if (tryLoadLibrary("/opt/VirtualBox/i386", NULL, 1) == 0)
|
---|
[16832] | 322 | return 0;
|
---|
| 323 | #elif defined(__APPLE__)
|
---|
[98694] | 324 | if (tryLoadLibrary("/Applications/VirtualBox.app/Contents/MacOS", DYNLIB_SUBDIR, 1) == 0)
|
---|
[16832] | 325 | return 0;
|
---|
[18923] | 326 | #elif defined(__FreeBSD__)
|
---|
[98694] | 327 | if (tryLoadLibrary("/usr/local/lib/virtualbox", DYNLIB_SUBDIR, 1) == 0)
|
---|
[18923] | 328 | return 0;
|
---|
[98694] | 329 | #elif defined(WIN32)
|
---|
| 330 | # ifdef IS_32_ON_64
|
---|
| 331 | pszHome = getenv("ProgramW6432");
|
---|
| 332 | if (pszHome && tryLoadLibrary(pszHome, "\\Oracle\\VirtualBox" SLASH_DYNLIB_SUBDIR, 1) == 0)
|
---|
[33396] | 333 | return 0;
|
---|
[98694] | 334 | # endif
|
---|
[50183] | 335 | pszHome = getenv("ProgramFiles");
|
---|
[98694] | 336 | if (pszHome && tryLoadLibrary(pszHome, "\\Oracle\\VirtualBox" SLASH_DYNLIB_SUBDIR, 1) == 0)
|
---|
[50183] | 337 | return 0;
|
---|
[98694] | 338 | if (tryLoadLibrary("C:\\Program Files\\Oracle\\VirtualBox", DYNLIB_SUBDIR, 1) == 0)
|
---|
| 339 | return 0;
|
---|
[16832] | 340 | #else
|
---|
| 341 | # error "port me"
|
---|
| 342 | #endif
|
---|
| 343 |
|
---|
| 344 | /*
|
---|
| 345 | * Finally try the dynamic linker search path.
|
---|
| 346 | */
|
---|
[98694] | 347 | if (tryLoadLibrary(NULL, NULL, 1) == 0)
|
---|
[16832] | 348 | return 0;
|
---|
| 349 |
|
---|
| 350 | /* No luck, return failure. */
|
---|
| 351 | return -1;
|
---|
| 352 | }
|
---|
| 353 |
|
---|
[18155] | 354 |
|
---|
[16832] | 355 | /**
|
---|
| 356 | * Terminate the C glue library.
|
---|
| 357 | */
|
---|
| 358 | void VBoxCGlueTerm(void)
|
---|
| 359 | {
|
---|
[50183] | 360 | if (g_hVBoxCAPI)
|
---|
[17570] | 361 | {
|
---|
[18156] | 362 | #if 0 /* VBoxRT.so doesn't like being reloaded. See @bugref{3725}. */
|
---|
[50183] | 363 | #ifndef WIN32
|
---|
| 364 | dlclose(g_hVBoxCAPI);
|
---|
| 365 | #else
|
---|
| 366 | FreeLibrary(g_hVBoxCAPI);
|
---|
[18156] | 367 | #endif
|
---|
[50183] | 368 | #endif
|
---|
| 369 | g_hVBoxCAPI = NULL;
|
---|
[17570] | 370 | }
|
---|
| 371 | g_pVBoxFuncs = NULL;
|
---|
[17677] | 372 | g_pfnGetFunctions = NULL;
|
---|
[19028] | 373 | memset(g_szVBoxErrMsg, 0, sizeof(g_szVBoxErrMsg));
|
---|
[16832] | 374 | }
|
---|
[18155] | 375 |
|
---|