1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include "jlong.h"
  27 #include "awtmsg.h"
  28 #include "awt_AWTEvent.h"
  29 #include "awt_Component.h"
  30 #include "awt_Toolkit.h"
  31 #include "locale_str.h"
  32 #include <sun_awt_windows_WInputMethod.h>
  33 #include <sun_awt_windows_WInputMethodDescriptor.h>
  34 #include <java_awt_event_InputMethodEvent.h>
  35 
  36 const UINT SYSCOMMAND_IMM = 0xF000 - 100;
  37 
  38 /************************************************************************
  39  * WInputMethod native methods
  40  */
  41 
  42 extern "C" {
  43 
  44 jobject CreateLocaleObject(JNIEnv *env, const char * name);
  45 HKL getDefaultKeyboardLayout();
  46 
  47 extern BOOL g_bUserHasChangedInputLang;
  48 
  49 /*
  50  * Class:     sun_awt_windows_WInputMethod
  51  * Method:    createNativeContext
  52  * Signature: ()I
  53  */
  54 JNIEXPORT jint JNICALL
  55 Java_sun_awt_windows_WInputMethod_createNativeContext(JNIEnv *env, jobject self)
  56 {
  57     TRY;
  58 
  59     // use special message to call ImmCreateContext() in main thread.
  60     return (jint)AwtToolkit::GetInstance().SendMessage(WM_AWT_CREATECONTEXT);
  61 
  62     CATCH_BAD_ALLOC_RET(0);
  63 }
  64 
  65 
  66 /*
  67  * Class:     sun_awt_windows_WInputMethod
  68  * Method:    destroyNativeContext
  69  * Signature: (I)V
  70  */
  71 JNIEXPORT void JNICALL
  72 Java_sun_awt_windows_WInputMethod_destroyNativeContext(JNIEnv *env, jobject self, jint context)
  73 {
  74     TRY_NO_VERIFY;
  75 
  76     // use special message to call ImmDestroyContext() in main thread.
  77     AwtToolkit::GetInstance().SendMessage(WM_AWT_DESTROYCONTEXT, context, 0);
  78 
  79     CATCH_BAD_ALLOC;
  80 }
  81 
  82 
  83 /*
  84  * Class:     sun_awt_windows_WInputMethod
  85  * Method:    enableNativeIME
  86  * Signature: (Lsun/awt/windows/WComponentPeer;I)V
  87  */
  88 JNIEXPORT void JNICALL
  89 Java_sun_awt_windows_WInputMethod_enableNativeIME(JNIEnv *env, jobject self, jobject peer,
  90                                                   jint context, jboolean useNativeCompWindow)
  91 {
  92     TRY;
  93 
  94     jobject selfGlobalRef = env->NewGlobalRef(self);
  95     jobject peerGlobalRef = env->NewGlobalRef(peer);
  96 
  97     EnableNativeIMEStruct *enis = new EnableNativeIMEStruct;
  98 
  99     enis->self = selfGlobalRef;
 100     enis->peer = peerGlobalRef;
 101     enis->context = context;
 102     enis->useNativeCompWindow = useNativeCompWindow;
 103 
 104     AwtToolkit::GetInstance().SendMessage(WM_AWT_ASSOCIATECONTEXT,
 105                                           reinterpret_cast<WPARAM>(enis), (LPARAM)0);
 106     // global refs are deleted in message handler
 107 
 108     CATCH_BAD_ALLOC;
 109 }
 110 
 111 
 112 /*
 113  * Class:     sun_awt_windows_WInputMethod
 114  * Method:    disableNativeIME
 115  * Signature: (Lsun/awt/windows/WComponentPeer;)V
 116  */
 117 JNIEXPORT void JNICALL
 118 Java_sun_awt_windows_WInputMethod_disableNativeIME(JNIEnv *env, jobject self, jobject peer)
 119 {
 120     TRY_NO_VERIFY;
 121 
 122     jobject peerGlobalRef = env->NewGlobalRef(peer);
 123     // self reference is not used
 124 
 125     EnableNativeIMEStruct *enis = new EnableNativeIMEStruct;
 126     enis->self = NULL;
 127     enis->peer = peerGlobalRef;
 128     enis->context = NULL;
 129     enis->useNativeCompWindow = JNI_TRUE;
 130 
 131     AwtToolkit::GetInstance().SendMessage(WM_AWT_ASSOCIATECONTEXT,
 132                                           reinterpret_cast<WPARAM>(enis), (LPARAM)0);
 133     // global refs are deleted in message handler
 134 
 135     CATCH_BAD_ALLOC;
 136 }
 137 
 138 
 139 /*
 140  * Class:     sun_awt_windows_WComponentPeer
 141  * Method:    handleEvent
 142  * Signature: (Lsun/awt/windows/WComponentPeer;Ljava/awt/AWTEvent;)V
 143  */
 144 JNIEXPORT void JNICALL
 145 Java_sun_awt_windows_WInputMethod_handleNativeIMEEvent(JNIEnv *env, jobject self,
 146                                                        jobject peer, jobject event)
 147 {
 148     TRY;
 149 
 150     PDATA pData;
 151     JNI_CHECK_PEER_RETURN(peer);
 152     AwtComponent* p = (AwtComponent *)pData;
 153 
 154     JNI_CHECK_NULL_RETURN(event, "null AWTEvent");
 155     if (env->EnsureLocalCapacity(1) < 0) {
 156         return;
 157     }
 158     jbyteArray bdata = (jbyteArray)(env)->GetObjectField(event, AwtAWTEvent::bdataID);
 159     if (bdata == 0) {
 160         return;
 161     }
 162     MSG msg;
 163     (env)->GetByteArrayRegion(bdata, 0, sizeof(MSG), (jbyte *)&msg);
 164     (env)->DeleteLocalRef(bdata);
 165     BOOL isConsumed =
 166       (BOOL)(env)->GetBooleanField(event, AwtAWTEvent::consumedID);
 167     int id = (env)->GetIntField(event, AwtAWTEvent::idID);
 168     DASSERT(!safe_ExceptionOccurred(env));
 169 
 170     if (isConsumed || p==NULL)  return;
 171 
 172     if (id >= java_awt_event_InputMethodEvent_INPUT_METHOD_FIRST &&
 173         id <= java_awt_event_InputMethodEvent_INPUT_METHOD_LAST)
 174     {
 175         jobject peerGlobalRef = env->NewGlobalRef(peer);
 176 
 177         // use special message to access pData on the toolkit thread
 178         AwtToolkit::GetInstance().SendMessage(WM_AWT_HANDLE_NATIVE_IME_EVENT,
 179                                               reinterpret_cast<WPARAM>(peerGlobalRef),
 180                                               reinterpret_cast<LPARAM>(&msg));
 181         // global ref is deleted in message handler
 182 
 183         (env)->SetBooleanField(event, AwtAWTEvent::consumedID, JNI_TRUE);
 184     }
 185 
 186     CATCH_BAD_ALLOC;
 187 }
 188 
 189 /*
 190  * Class:     sun_awt_windows_WInputMethod
 191  * Method:    notifyNativeIME
 192  * Signature: (Lsun/awt/windows/WComponentPeer;I)V
 193  */
 194 JNIEXPORT void JNICALL
 195 Java_sun_awt_windows_WInputMethod_endCompositionNative(JNIEnv *env, jobject self,
 196                                                        jint context, jboolean flag)
 197 {
 198     TRY;
 199 
 200     // TODO: currently the flag parameter is ignored and the outstanding input is
 201     //       always discarded.
 202     //       If the flag value is Java_sun_awt_windows_WInputMethod_COMMIT_INPUT,
 203     //       then input text should be committed. Otherwise, should be discarded.
 204     //
 205     // 10/29/98 - Changed to commit it according to the flag.
 206 
 207     // use special message to call ImmNotifyIME() in main thread.
 208     AwtToolkit::GetInstance().SendMessage(WM_AWT_ENDCOMPOSITION, context,
 209         (LPARAM)(flag != sun_awt_windows_WInputMethod_DISCARD_INPUT));
 210 
 211     CATCH_BAD_ALLOC;
 212 }
 213 
 214 /*
 215  * Class:     sun_awt_windows_WInputMethod
 216  * Method:    setConversionStatus
 217  * Signature: (II)V
 218  */
 219 JNIEXPORT void JNICALL
 220 Java_sun_awt_windows_WInputMethod_setConversionStatus(JNIEnv *env, jobject self, jint context, jint request)
 221 {
 222     TRY;
 223 
 224     // use special message to call ImmSetConversionStatus() in main thread.
 225     AwtToolkit::GetInstance().SendMessage(WM_AWT_SETCONVERSIONSTATUS,
 226                                           context,
 227                                           MAKELPARAM((WORD)request, (WORD)0));
 228 
 229     CATCH_BAD_ALLOC;
 230 }
 231 
 232 /*
 233  * Class:     sun_awt_windows_WInputMethod
 234  * Method:    getConversionStatus
 235  * Signature: (I)I
 236  */
 237 JNIEXPORT jint JNICALL
 238 Java_sun_awt_windows_WInputMethod_getConversionStatus(JNIEnv *env, jobject self, jint context)
 239 {
 240     TRY;
 241 
 242     // use special message to call ImmSetConversionStatus() in main thread.
 243     return (jint) AwtToolkit::GetInstance().SendMessage(
 244         WM_AWT_GETCONVERSIONSTATUS, context, 0);
 245 
 246     CATCH_BAD_ALLOC_RET(0);
 247 }
 248 
 249 /*
 250  * Class:     sun_awt_windows_WInputMethod
 251  * Method:    setOpenStatus
 252  * Signature: (IZ)V
 253  */
 254 JNIEXPORT void JNICALL
 255 Java_sun_awt_windows_WInputMethod_setOpenStatus(JNIEnv *env, jobject self, jint context, jboolean flag)
 256 {
 257     TRY;
 258 
 259     // use special message to call ImmSetConversionStatus() in main thread.
 260     AwtToolkit::GetInstance().SendMessage(WM_AWT_SETOPENSTATUS,
 261                                           context, flag);
 262 
 263     CATCH_BAD_ALLOC;
 264 }
 265 
 266 /*
 267  * Class:     sun_awt_windows_WInputMethod
 268  * Method:    getConversionStatus
 269  * Signature: (I)Z
 270  */
 271 JNIEXPORT jboolean JNICALL
 272 Java_sun_awt_windows_WInputMethod_getOpenStatus(JNIEnv *env, jobject self, jint context)
 273 {
 274     TRY;
 275 
 276     // use special message to call ImmSetConversionStatus() in main thread.
 277     return (jboolean)(AwtToolkit::GetInstance().SendMessage(
 278                                                        WM_AWT_GETOPENSTATUS,
 279                                                        context, 0));
 280     CATCH_BAD_ALLOC_RET(0);
 281 }
 282 
 283 /*
 284  * Class:     sun_awt_windows_WInputMethod
 285  * Method:    getNativeLocale
 286  * Signature: ()Ljava/util/Locale;
 287  */
 288 JNIEXPORT jobject JNICALL Java_sun_awt_windows_WInputMethod_getNativeLocale
 289   (JNIEnv *env, jclass cls)
 290 {
 291     TRY;
 292 
 293     const char * javaLocaleName = getJavaIDFromLangID(AwtComponent::GetInputLanguage());
 294     if (javaLocaleName != NULL) {
 295         // Now WInputMethod.currentLocale and AwtComponent::m_idLang are get sync'ed,
 296         // so we can reset this flag.
 297         g_bUserHasChangedInputLang = FALSE;
 298 
 299         jobject ret = CreateLocaleObject(env, javaLocaleName);
 300         free((void *)javaLocaleName);
 301         return ret;
 302     } else {
 303         return NULL;
 304     }
 305 
 306     CATCH_BAD_ALLOC_RET(NULL);
 307 }
 308 
 309 /*
 310  * Class:     sun_awt_windows_WInputMethod
 311  * Method:    setNativeLocale
 312  * Signature: (Ljava/lang/String;Z)Z
 313  */
 314 JNIEXPORT jboolean JNICALL Java_sun_awt_windows_WInputMethod_setNativeLocale
 315   (JNIEnv *env, jclass cls, jstring localeString, jboolean onActivate)
 316 {
 317     TRY;
 318 
 319     // check if current language ID is the requested one.  Note that the
 320     // current language ID (returned from 'getJavaIDFromLangID') is in
 321     // ASCII encoding, so we use 'GetStringUTFChars' to retrieve requested
 322     // language ID from the 'localeString' object.
 323     const char * current = getJavaIDFromLangID(AwtComponent::GetInputLanguage());
 324     jboolean isCopy;
 325     const char * requested = env->GetStringUTFChars(localeString, &isCopy);
 326     if ((current != NULL) && (strcmp(current, requested) == 0)) {
 327         env->ReleaseStringUTFChars(localeString, requested);
 328         free((void *)current);
 329         return JNI_TRUE;
 330     }
 331 
 332     // get list of available HKLs.  Adding the user's preferred layout on top of the layout
 333     // list which is returned by GetKeyboardLayoutList ensures to match first when
 334     // looking up suitable layout.
 335     int layoutCount = ::GetKeyboardLayoutList(0, NULL) + 1;  // +1 for user's preferred HKL
 336     HKL FAR * hKLList = (HKL FAR *)safe_Malloc(sizeof(HKL)*layoutCount);
 337     DASSERT(!safe_ExceptionOccurred(env));
 338     ::GetKeyboardLayoutList(layoutCount - 1, &(hKLList[1]));
 339     hKLList[0] = getDefaultKeyboardLayout(); // put user's preferred layout on top of the list
 340 
 341     // lookup matching LangID
 342     jboolean retValue = JNI_FALSE;
 343     for (int i = 0; i < layoutCount; i++) {
 344         const char * supported = getJavaIDFromLangID(LOWORD(hKLList[i]));
 345         if ((supported != NULL) && (strcmp(supported, requested) == 0)) {
 346             // use special message to call ActivateKeyboardLayout() in main thread.
 347             if (AwtToolkit::GetInstance().SendMessage(WM_AWT_ACTIVATEKEYBOARDLAYOUT, (WPARAM)onActivate, (LPARAM)hKLList[i])) {
 348                 //also need to change the same keyboard layout for the Java AWT-EventQueue thread
 349                 AwtToolkit::activateKeyboardLayout(hKLList[i]);
 350                 retValue = JNI_TRUE;
 351             }
 352             break;
 353         }
 354     }
 355 
 356     env->ReleaseStringUTFChars(localeString, requested);
 357     free(hKLList);
 358     free((void *)current);
 359     return retValue;
 360 
 361     CATCH_BAD_ALLOC_RET(JNI_FALSE);
 362 }
 363 
 364 /*
 365  * Class:     sun_awt_windows_WInputMethod
 366  * Method:    hideWindowsNative
 367  * Signature: (Lsun/awt/windows/WComponentPeer;Z)V
 368  */
 369 JNIEXPORT void JNICALL Java_sun_awt_windows_WInputMethod_setStatusWindowVisible
 370   (JNIEnv *env, jobject self, jobject peer, jboolean visible)
 371 {
 372     /* Retrieve the default input method Window handler from AwtToolkit.
 373        Windows system creates a default input method window for the
 374        toolkit thread.
 375     */
 376 
 377     HWND defaultIMEHandler = AwtToolkit::GetInstance().GetInputMethodWindow();
 378 
 379     if (defaultIMEHandler == NULL)
 380     {
 381         jobject peerGlobalRef = env->NewGlobalRef(peer);
 382 
 383         // use special message to access pData on the toolkit thread
 384         LRESULT res = AwtToolkit::GetInstance().SendMessage(WM_AWT_GET_DEFAULT_IME_HANDLER,
 385                                           reinterpret_cast<WPARAM>(peerGlobalRef), 0);
 386         // global ref is deleted in message handler
 387 
 388         if (res == TRUE) {
 389             defaultIMEHandler = AwtToolkit::GetInstance().GetInputMethodWindow();
 390         }
 391     }
 392 
 393     if (defaultIMEHandler != NULL) {
 394         ::SendMessage(defaultIMEHandler, WM_IME_CONTROL,
 395                       visible ? IMC_OPENSTATUSWINDOW : IMC_CLOSESTATUSWINDOW, 0);
 396     }
 397 }
 398 
 399 /*
 400  * Class:     sun_awt_windows_WInputMethod
 401  * Method:    openCandidateWindow
 402  * Signature: (Lsun/awt/windows/WComponentPeer;II)V
 403  */
 404 JNIEXPORT void JNICALL Java_sun_awt_windows_WInputMethod_openCandidateWindow
 405   (JNIEnv *env, jobject self, jobject peer, jint x, jint y)
 406 {
 407     TRY;
 408 
 409     PDATA pData;
 410     JNI_CHECK_PEER_RETURN(peer);
 411 
 412     jobject peerGlobalRef = env->NewGlobalRef(peer);
 413 
 414     // WARNING! MAKELONG macro treats the given values as unsigned.
 415     //   This may lead to some bugs in multiscreen configurations, as
 416     //   coordinates can be negative numbers. So, while handling
 417     //   WM_AWT_OPENCANDIDATEWINDOW message in AwtToolkit, we should
 418     //   carefully extract right x and y values using GET_X_LPARAM and
 419     //   GET_Y_LPARAM, not LOWORD and HIWORD
 420     // See CR 4805862, AwtToolkit::WndProc
 421 
 422     // use special message to open candidate window in main thread.
 423     AwtToolkit::GetInstance().SendMessage(WM_AWT_OPENCANDIDATEWINDOW,
 424                                           (WPARAM)peerGlobalRef, MAKELONG(x, y));
 425     // global ref is deleted in message handler
 426 
 427     CATCH_BAD_ALLOC;
 428 }
 429 
 430 
 431 /************************************************************************
 432  * WInputMethodDescriptor native methods
 433  */
 434 
 435 /*
 436  * Class:     sun_awt_windows_WInputMethodDescriptor
 437  * Method:    getNativeAvailableLocales
 438  * Signature: ()[Ljava/util/Locale;
 439  */
 440 JNIEXPORT jobjectArray JNICALL Java_sun_awt_windows_WInputMethodDescriptor_getNativeAvailableLocales
 441   (JNIEnv *env, jclass self)
 442 {
 443     TRY;
 444 
 445     // get list of available HKLs
 446     int layoutCount = ::GetKeyboardLayoutList(0, NULL);
 447     HKL FAR * hKLList = (HKL FAR *)safe_Malloc(sizeof(HKL)*layoutCount);
 448     DASSERT(!safe_ExceptionOccurred(env));
 449     ::GetKeyboardLayoutList(layoutCount, hKLList);
 450 
 451     // get list of Java locale names while getting rid of duplicates
 452     int srcIndex = 0;
 453     int destIndex = 0;
 454     int javaLocaleNameCount = 0;
 455     int current = 0;
 456     const char ** javaLocaleNames = (const char **)safe_Malloc(sizeof(char *)*layoutCount);
 457     DASSERT(!safe_ExceptionOccurred(env));
 458     for (; srcIndex < layoutCount; srcIndex++) {
 459         const char * srcLocaleName = getJavaIDFromLangID(LOWORD(hKLList[srcIndex]));
 460 
 461         if (srcLocaleName == NULL) {
 462             // could not find corresponding Java locale name for this HKL.
 463             continue;
 464         }
 465 
 466         for (current = 0; current < destIndex; current++) {
 467             if (strcmp(javaLocaleNames[current], srcLocaleName) == 0) {
 468                 // duplicated. ignore this HKL
 469                 break;
 470             }
 471         }
 472 
 473         if (current == destIndex) {
 474             javaLocaleNameCount++;
 475             destIndex++;
 476             javaLocaleNames[current] = srcLocaleName;
 477         }
 478     }
 479 
 480     // convert it to an array of Java locale objects
 481     jclass localeClass = env->FindClass("java/util/Locale");
 482     jobjectArray locales = env->NewObjectArray(javaLocaleNameCount, localeClass, NULL);
 483     for (current = 0; current < javaLocaleNameCount; current++) {
 484         env->SetObjectArrayElement(locales,
 485                                    current,
 486                                    CreateLocaleObject(env, javaLocaleNames[current]));
 487         free((void *)javaLocaleNames[current]);
 488     }
 489     DASSERT(!safe_ExceptionOccurred(env));
 490 
 491     env->DeleteLocalRef(localeClass);
 492     free(hKLList);
 493     free(javaLocaleNames);
 494     return locales;
 495 
 496     CATCH_BAD_ALLOC_RET(NULL);
 497 }
 498 
 499 /**
 500  * Class:     sun_awt_windows_WInputMethod
 501  * Method:    getNativeIMMDescription
 502  * Signature: ()Ljava/lang/String;
 503  *
 504  * This method tries to get the information about the input method associated with
 505  * the current active thread.
 506  *
 507  */
 508 JNIEXPORT jstring JNICALL Java_sun_awt_windows_WInputMethod_getNativeIMMDescription
 509   (JNIEnv *env, jobject self) {
 510 
 511     TRY;
 512 
 513     // Get the keyboard layout of the active thread.
 514     HKL hkl = AwtComponent::GetKeyboardLayout();
 515     LPTSTR szImmDescription = NULL;
 516     UINT buffSize = 0;
 517     jstring infojStr = NULL;
 518 
 519     if ((buffSize = ::ImmGetDescription(hkl, szImmDescription, 0)) > 0) {
 520         szImmDescription = (LPTSTR) safe_Malloc((buffSize+1) * sizeof(TCHAR));
 521 
 522         if (szImmDescription != NULL) {
 523             ImmGetDescription(hkl, szImmDescription, (buffSize+1));
 524 
 525             infojStr = JNU_NewStringPlatform(env, szImmDescription);
 526 
 527             free(szImmDescription);
 528         }
 529     }
 530 
 531     return infojStr;
 532 
 533     CATCH_BAD_ALLOC_RET(NULL);
 534 }
 535 
 536 /*
 537  * Create a Java locale object from its name string
 538  */
 539 jobject CreateLocaleObject(JNIEnv *env, const char * name)
 540 {
 541     TRY;
 542 
 543     // create Locale object
 544     jobject langtagObj = env->NewStringUTF(name);
 545     jobject localeObj = JNU_CallStaticMethodByName(env,
 546                                                    NULL,
 547                                                    "java/util/Locale",
 548                                                    "forLanguageTag",
 549                                                    "(Ljava/lang/String;)Ljava/util/Locale;",
 550                                                    langtagObj).l;
 551     env->DeleteLocalRef(langtagObj);
 552 
 553     return localeObj;
 554 
 555     CATCH_BAD_ALLOC_RET(NULL);
 556 }
 557 
 558 
 559 /*
 560  * Gets user's preferred keyboard layout
 561  * Warning: This is version dependent code
 562  */
 563 HKL getDefaultKeyboardLayout() {
 564     LONG ret;
 565     HKL hkl = 0;
 566     HKEY hKey;
 567     BYTE szHKL[16];
 568     DWORD cbHKL = 16;
 569     LPTSTR end;
 570 
 571     ret = ::RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Keyboard Layout\\Preload"), NULL, KEY_READ, &hKey);
 572 
 573     if (ret == ERROR_SUCCESS) {
 574         ret = ::RegQueryValueEx(hKey, TEXT("1"), 0, 0, szHKL, &cbHKL);
 575 
 576         if (ret == ERROR_SUCCESS) {
 577             hkl = reinterpret_cast<HKL>(static_cast<INT_PTR>(
 578                 _tcstoul((LPCTSTR)szHKL, &end, 16)));
 579         }
 580 
 581         ::RegCloseKey(hKey);
 582     }
 583 
 584     return hkl;
 585 }
 586 } /* extern "C" */