Yet Another Code Site |
These are "snippets" or "snips" (bits and pieces of code) taken from working applications that do something useful (I hope). They worked in the original applications, but I have slightly modified them for posting here without any specific testing. Consider them "beta code" and kindling for your imagination and study. Be aware that, in almost all cases, you will need to modify the code before trying to wedge it into your application. Of course, "you use it at your own risk."
I confess that I have some trepidation about sharing bits and pieces of code in this way. I am concerned that presenting the code out of context and without a sample application will generate more questions than I can answer -- so, please, please study the code and make sure that you understand it before asking me questions. On the other hand, you may find bugs or truly incomplete or incomprehensible code. If so, please tell me so that I can correct or clarify the code where needed for those that come here later. However, I do not have the time to teach everyone the Windows API (and this code contains lots of Windows API calls), so please try to diagnose any problems before asking for help. Once you have done that, please feel free to ask me questions.
Generally, each snippet of code is presented as:
A global function,
A method of the main form, or
A class derived directly from a VCL class (such as TRichEdit).
I have tried to limit the potential confusion by adopting the following conventions:
For a class derived from the VCL, the derived class will be named similarly to IDE-generated names. For example, for the default initial IDE-generated main form, the class name is TForm1.
For an object of a class derived from the VCL, the derived class object will be named similarly to IDE-generated objects. For example, Form1 is the TForm1* generated for the main form by the IDE. Similarly, RichEdit1 is the first TRichEdit dropped on the form.
Windows API calls are prefixed with "::" (unless I forgot).
Normally, you will need to declare the functions in a header file, possibly as part of a class. If a "#include" is included in the snippet, you will probably need to put it in the header file as well.
Again, keep in mind that you will almost certainly need modify the code to work in your application. Please take the time to study it. In many cases, you can probably improve it -- for example, by adding const to invariant parameters.
Send the text from a TRichEdit via e-mail using MAPI. (Easily modified to send arbitrary text, not just from TRichEdit controls.) (10/28/98) Go to the code.
Get the icon associated with a file type. (10/28/98) Go to the code.
Get the title associated with a file. (10/28/98) Go to the code.
Get the long file name of a file. (02/01/99) Go to the code.
Get the name of the user currently logged on. (10/28/98) Go to the code.
Build a string describing a font. (10/28/98) Go to the code.
Format a long integer for displaying to a user. (10/28/98) Go to the code.
Convert an 8-bit byte to hexadecimal. (10/28/98) Go to the code.
Determine the current position (line and character offset or column) of the cursor in a TRichEdit control. Move the cursor to a specific line or column. (10/29/98) Go to the code.
Determine if a document file is plain text, Rich Text Format (RTF), or something else before loading the document. (10/29/98) Go to the code.
Create an instance of a TMetaClass. (08/01/99, updated 12/25/2000) Go to the code.
Determine which version of a DLL is loaded at runtime. (03-03-2001) Go to the code.
Determine if a function is available at runtime and, if so, execute it. (03-03-2001) Go to the code.
//--------------------------------------------------------------------------- // send the contents of a TRichEdit via e-mail using MAPI. note that the // MAPI mail service must be configured by the user and may be an Internet // mail server (SMTP) or an intranet server such as, presumably, GroupWise // 5+, Lotus Notes, or some other MAPI-compliant service. frankly, it does // not matter to us what service they use as long as MAPI works, right? // // calling parameters: // re - pointer to the TRichEdit from which to get the text for the message body. // subject - text to put in the message subject line. // // return value: // zero on success. // non-zero (MAPI error code) on failure. // #include "mapi.hpp" unsigned int __fastcall RichEditMapiSend(TRichEdit* re, AnsiString subject) { TMapiMessage MapiMessage; MapiMessage.ulReserved = 0; MapiMessage.lpszSubject = subject; MapiMessage.lpszNoteText = re->Lines->Text.c_str(); MapiMessage.lpszMessageType = NULL; MapiMessage.lpszDateReceived = NULL; MapiMessage.lpszConversationID = NULL; MapiMessage.flFlags = 0; MapiMessage.lpOriginator = NULL; MapiMessage.nRecipCount = 0; MapiMessage.lpRecips = NULL; MapiMessage.nFileCount = 0; MapiMessage.lpFiles = NULL; unsigned int error = MapiSendMail(0, 0, MapiMessage, MAPI_DIALOG | MAPI_LOGON_UI | MAPI_NEW_SESSION, 0); // if the user cancelled, clear error if (error == MAPI_E_USER_ABORT) error = 0; return error; } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // get the icon associated with a file type (that is, the small icon // displayed in Explorer). note: caller must call ::DestroyIcon() // // calling parameters: // filepath - path, name, and extension of file for which to retrieve the icon. // // return value: // a handle to a Windows icon (an HICON) - caller is responsible for // deleting this handle with the Windows API DestroyIcon() call. failure // to delete the icon will result in memory leakage. // // usage example: // HICON hIcon = GetAssociatedIcon("c:\\autoexec.bat"); // [ ...use the icon... ] // ::DestroyIcon(hIcon); // HICON __fastcall GetAssociatedIcon(AnsiString filepath) { // if the filepath is empty, we'll assume that we're looking for the // icon associated with the "txt" extension. unfortunately, // SHGetFileInfo() apparently fails if the target file doesn't actually // exist, so we'll rely on an arcane feature present since the early // days of DOS... a filename for which the name portion matches a DOS // device actually points to the device... so "nul.txt" actually // references the NUL: device. yeah, well, anyway, it works. well, // sort of... apparently the extension is actually ignored and we // get back a default icon. good enough for me -- nobody's paying // for this anyway. AnsiString s = filepath; s.Unique(); if (s == "") s = "nul.txt"; // changed to use ExtractAssociatedIcon() WORD iconIndex = 0; HICON hIcon = 0; try { char buf[MAXPATH]; // executable path written back here strncpy(buf, s.c_str(), sizeof(buf)); hIcon = ::ExtractAssociatedIcon(HInstance, buf, &iconIndex); } catch (...) { hIcon = 0; } return hIcon; } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // get the title associated with a file (for example a file named // somefile.doc might return "Microsoft Word Document") // // calling parameters: // filename - the name of the file for which to retrieve the title. // I do not recall if calling this with a non-extant file causes // a failure; I *think* that you will get the title that would be // associated with the file as if it did exist. if so, the parameter // does not necessarily have to contain the full path. // // return value: // the title as an AnsiString. // AnsiString GetAssociatedTitle(AnsiString filename) { // get shell title from filename int buflen = ::GetFileTitle(filename.c_str(), 0, 0); char *buf = new char[buflen]; ::GetFileTitle(filename.c_str(), buf, (WORD) buflen); AnsiString title(buf); delete[] buf; return title; } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // GetLongFileName() - intended to take a filename (which may or may not // include a path) and convert it to a file/path in which each element of // the path is in long name format. this turned out to be a bit harder than // I expected.... // // after many travails and searches, the following code was adapted from an // article on the MSDN library: // // http://premium.microsoft.com/msdn/library/periodic/period97/f1/d3/s245b8.htm // Win32 Q & A // Jeffrey Richter // // thanks, Jeffrey; I was already headed toward this solution, but you saved me // a few hours of coding and more than a few hairs! // // calling parameters: // filename - the file path and name of the file for which to retrieve the // long filename. // // return value: // the long filename as an AnsiString. on error, returns original filename. // AnsiString GetLongFileName(AnsiString filename) { LPSHELLFOLDER psfDesktop = 0; ULONG chEaten = 0; LPITEMIDLIST pidlShellItem = 0; WCHAR szLongPath[_MAX_PATH] = { 0 }; WCHAR szShortPath[_MAX_PATH]; filename.WideChar(szShortPath, sizeof(szShortPath)); // get the desktop folder shell interface HRESULT hr = ::SHGetDesktopFolder(&psfDesktop); if (hr != NOERROR) return filename; // get pidl for the short path hr = psfDesktop->ParseDisplayName(0, 0, szShortPath, &chEaten, &pidlShellItem, 0); // release the desktop IShellFolder interface psfDesktop->Release(); // if we failed to parse the filename, return original value if (hr != NOERROR) return filename; // so far, so good; get the long filename bool retval = ::SHGetPathFromIDListW(pidlShellItem, szLongPath); // free the pidl allocated by ParseDisplayName LPMALLOC pMalloc = 0; if (::SHGetMalloc(&pMalloc) == NOERROR) { pMalloc->Free(pidlShellItem); pMalloc->Release(); } // if failed, return original string if (!retval) return filename; // everything worked, so return new path return AnsiString(szLongPath); } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // get the user's Windows login name // // calling parameters: // none. // // return value: // the currently logged on user's name (as supplied to Windows or a network). // AnsiString GetNetUser(void) { AnsiString retVal("Anonymous"); char *user = 0; DWORD len = 0; ::WNetGetUser(NULL, user, &len); user = new char[(int) (len + 1)]; if (::WNetGetUser(NULL, user, &len) == NO_ERROR) retVal = user; delete[] user; return retVal; } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // build a string representation of a font // // calling parameters: // a reference to a TFont for which a string representation of the font is to be built. // // return value: // the string representation of the font (e.g., "Courier New, 9pt, bold") // AnsiString FontString(TFont& font) { AnsiString s; s = font.Name; s += AnsiString(", ") + AnsiString(abs(font.Size)) + AnsiString(" pt"); if (font.Style.Contains(fsBold)) s += ", bold"; if (font.Style.Contains(fsItalic)) s += ", italic"; if (font.Style.Contains(fsStrikeOut)) s += ", strikeout"; if (font.Style.Contains(fsUnderline)) s += ", underline"; return s; } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // format a long integer with thousands separators // // calling parameters: // a long integer to be formatted as a string. // // return value: // the formatted string representation of the long integer value. // for example, 1000000 would return "1,000,000" if the thousands separator // is set to "," in the Regional Settings in Control Panel. // AnsiString FormatLongInt(long int val) { // format and return it return AnsiString::FloatToStrF((long double) val, AnsiString::sffNumber, 18, 0); } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // convert a byte to two hexadecimal characters // // calling parameters: // byte - a byte to convert to hexadecimal. // dest - a pointer to a buffer *at least* two bytes long. // // return value: // none. // // usage example: // char buf[3]; // allocate a buffer for the call // buf[2] = '\0'; // terminate the buffer // ByteToHex('a'); // convert the character 'a' to hex // // buffer now contains "61\0" // AnsiString s("Hex value is: ") + AnsiString(buf); // // notes: // this function requires that you pass a buffer in order to make it // reasonably efficient. it could be easily modified to return an // AnsiString representation of the value, but I used it to convert // entire files into hex dumps so performance was tantamount. // // byte could be declared as inline to further improve performance. // // the "& 0x0f" can be dropped from both lines as long as byte // remains declared as unsigned char. // void ByteToHex(const unsigned char byte, char *dest) { static char ascii[] = "0123456789abcdef"; *dest++ = ascii[(byte >> 4) & 0x0f]; *dest = ascii[byte & 0x0f]; } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // get or set the line and column offset of the cursor in a TRichEdit control. // (there are several interrelated functions in this snippet.) note that this // example presumes that you are creating a TRichEdit-derived class called // TMyRichEdit and that the following are member functions. //--------------------------------------------------------------------------- // get the length of the text in the control (used by other functions in this snippet). // // calling parameters: // none. // // return value: // the length of the text in the RE control. // unsigned long int TMyRichEdit::GetCurrentLength(void) { // get the length of the text in the control // if using Rich Edit 1.0 (BCB 1.0 & 3.0 use RE 1.0) return ::SendMessage(Handle, WM_GETTEXTLENGTH, 0, 0); // if using Rich Edit 2.0, use this instead: // GETTEXTLENGTHEX gtlx = { GTL_PRECISE, CP_ACP }; // return ::SendMessage(Handle, EM_GETTEXTLENGTHEX, (WPARAM) >lx, 0); } //--------------------------------------------------------------------------- // get the line number of the current cursor position. // // calling parameters: // none. // // return value: // the line number of the current cursor position in the RE control. // unsigned int TMyRichEdit::GetLine(void) { CHARRANGE chrg; ::SendMessage(Handle, EM_EXGETSEL, 0, (LPARAM) &chrg); return ::SendMessage(Handle, EM_EXLINEFROMCHAR, 0, (LPARAM) chrg.cpMin); } //--------------------------------------------------------------------------- // get the column (character offset) of the cursor position in the control. // // calling parameters: // none. // // return value: // the column (character offset) of the cursor position in the RE control. // unsigned int TMyRichEdit::GetColumn(void) { CHARRANGE chrg; ::SendMessage(Handle, EM_EXGETSEL, 0, (LPARAM) &chrg); return chrg.cpMin - ::SendMessage(Handle, EM_LINEINDEX, (WPARAM) GetLine(), 0); } //--------------------------------------------------------------------------- // set the cursor position to a specific line in the control. // // calling parameters: // line - the line number (zero-based) to which the cursor will be moved. // // return value: // none. // void TMyRichEdit::SetLine(unsigned int line) { CHARRANGE chrg; chrg.cpMin = chrg.cpMax = ::SendMessage(Handle, EM_LINEINDEX, (WPARAM) line, 0); if (chrg.cpMin == -1) // past end of text so find last line chrg.cpMin = chrg.cpMax = GetCurrentLength(); ::SendMessage(Handle, EM_EXSETSEL, 0, (LPARAM) &chrg); } //--------------------------------------------------------------------------- // set the cursor position to a particular column (in the current line). // // calling parameters: // column - the column to which to move the cursor (zero-based). // // return value: // none. // void TMyRichEdit::SetColumn(unsigned int column) { CHARRANGE chrg; unsigned int currLine = GetLine(); unsigned int ndx = ::SendMessage(Handle, EM_LINEINDEX, (WPARAM) currLine, 0); unsigned int cols = ::SendMessage(Handle, EM_LINEINDEX, (WPARAM) currLine + 1, 0) - ndx - 1; if (column > cols) column = cols; chrg.cpMin = chrg.cpMax = ndx + column; ::SendMessage(Handle, EM_EXSETSEL, 0, (LPARAM) &chrg); } //---------------------------------------------------------------------------
//--------------------------------------------------------------------------- // determine if a file is an RTF document without regard to the filename extension. // // calling parameters: // filepath - the path and filename to test. // // return value: // true if RTF. // false if not RTF. // bool IsRTF(AnsiString filepath) { static char idBytes[5] = "{\\rtf"; char buf[sizeof(idBytes)]; int fileHandle; int bytesRead; bool result = false; try { fileHandle = ::FileOpen(filepath, fmOpenRead | fmShareDenyWrite); bytesRead = ::FileRead(fileHandle, buf, sizeof(buf)); ::FileClose(fileHandle); if (bytesRead == sizeof(buf)) result = !::memcmp(idBytes, buf, sizeof(buf)); } catch (...) { if (fileHandle) ::FileClose(fileHandle); } return result; } //--------------------------------------------------------------------------- // determine if a file is likely to be a binary file (arbitrarily defined as // a any file containing a null byte in the first 8192 bytes of the file). // // calling parameters: // filepath - the path and filename to test. // // return value: // true if the file is probably binary. // false if the file is probably not binary (is probably plain text). // bool IsLikelyBinaryFile(AnsiString filepath) { char buf[8192]; ::memset(buf, 'z', sizeof(buf)); int fileHandle; bool result = false; try { fileHandle = ::FileOpen(filepath, fmOpenRead | fmShareDenyWrite); ::FileRead(fileHandle, buf, sizeof(buf)); ::FileClose(fileHandle); result = ::memchr(buf, '\0', sizeof(buf)); } catch (...) { if (fileHandle) ::FileClose(fileHandle); } return result; } //---------------------------------------------------------------------------
Here are two answers: A straight BCB/C++ solution that has worked in the past and another answer that is guaranteed to work....
The following has worked for me in the past. However, since installing BCB5 with CodeGuard (CG), I find that CG complains about this code. Of course, CG may simply be whining so, if you want a BCB/C++ solution and do not care about CG "errors," then give this a try:
//--------------------------------------------------------------------------- // helper function to create and initialize a TMetaClass instance. // // calling parameters: // a pointer to a TMetaClass object to be intantiated. // // return value: // a pointer to the TObject that was created. the caller is // responsible for for any additional initialization required // and for freeing the object. the caller will need to cast // this value to the proper type, of course. // TObject* CreateMetaClassObject(TMetaClass* metaClass) { int objSize = TObject::InstanceSize(metaClass); TObject* obj = (TObject*) new char[objSize]; TObject::InitInstance(metaClass, obj); return obj; } //---------------------------------------------------------------------------
If you need a "perfect" solution, then here is the Delphi/Pascal code required. Save the following as CreateDynInst.pas and add it to your project file:
{ This code is modified from a sample provided by Jeff Overcash (TeamB). Thanks, Jeff, for sharing. } unit CreateDynInst; {$R-,T-,H+,X+} interface uses Classes; //type TClass = class of TObject; function CreateDynamicInstance(ClassRef : TClass) : TObject; implementation function CreateDynamicInstance(ClassRef : TClass) : TObject; {var FC : TComponentClass; } begin try Result := ClassRef.Create; except Result := nil; end; end; initialization finalization
After creating and adding this *.pas unit, the BCB compiler will generate a *.hpp header.
This code demonstrates the technique using the RichEdit DLLs as an example. It makes the following assumptions:
If riched32.dll is loaded, then we are using RichEdit 1.0.
If riched20.dll is loaded, then we are using RichEdit 2.0 or 3.0.
If the riched20.dll minor version is 0, then we are using RichEdit 2.0. Otherwise, we assume RichEdit 3.0.
Note that it may be possible to load both RichEdit DLLs simultaneously. This code does not test for that possibility.
//--------------------------------------------------------------------------- // function to determine which version of RichEdit is loaded. // // calling parameters: // none. // // return value: // -1 internal failure. // 0 no RichEdit DLL is loaded. // 1 RichEdit 1.0 is loaded. // 2 RichEdit 2.0 is loaded. // 3 RichEdit 3.0 is loaded. // int RichEditVersion(void) { // try RE 1.0 DLL HMODULE hDll = ::GetModuleHandle("riched32.dll"); if (hDll) return 1; // try RE 2.0/3.0 DLL hDll = ::GetModuleHandle("riched20.dll"); if (!hDll) return 0; // neither version is loaded // get the path to the DLL TCHAR modulePath[MAX_PATH]; DWORD result = ::GetModuleFileName(hModule, modulePath, sizeof(modulePath) / sizeof(TCHAR)); // if could not get module filename, fail return -1; // unexpected failure // get size of version information DWORD dummy; DWORD size = ::GetFileVersionInfoSize(modulePath, &dummy); if (!size) return -1; // unexpected failure // allocate a buffer for version information BYTE* buffer = (BYTE*) new BYTE[size]; // get the version information if (!::GetFileVersionInfo(modulePath, 0, size, buffer)) { delete[] buffer; return -1; // unexpected faiure } // get the static version information VS_FIXEDFILEINFO* vsInfo; UINT vsInfoSize; if (!::VerQueryValue(buffer, "\\", (LPVOID*) &vsInfo, &vsInfoSize)) { delete[] buffer; return -1; // unexpected failure } // int FileVersionMajor = HIWORD(vsInfo->dwFileVersionMS); int FileVersionMinor = LOWORD(vsInfo->dwFileVersionMS); // int FileVersionRelease = HIWORD(vsInfo->dwFileVersionLS); // int FileVersionBuild = LOWORD(vsInfo->dwFileVersionLS); // free the buffer delete[] buffer; return FileVersionMinor ? 3 : 2; } //---------------------------------------------------------------------------
I found that programs that called SHGetPathFromIDListW() (the Unicode version of SHGetPathFromIDList()) would not load on Win95B (at least, I think that it was Win95B). I eventually found an acknowledgement on Microsoft's site that the function was not properly exported from SHELL32.DLL in that version of Windows. This was a problem because I wanted to use the function if it was available and I did not want to ship a separate version of the program for Win95B.
A similar situation exists when you want to use WindowsNT functions when running on NT and do something different when running on Win9x, but do not want to ship two versions of your program.
In both cases, the program cannot contain a direct call to the Windows function because Windows will fail to resolve the function call's address at load time and, therefore, refuse to load the program. The only way to work around this is to determine the function's address dynamically and execute it through a pointer. Here is an example using SHGetPathFromIDListW().
Note that, unlike most of the code on this page, the following is not presented as a callable function, method, or class copy the code into your function or method and modify it as needed.
//--------------------------------------------------------------------------- // for convenience, typedef the function signature typedef WINSHELLAPI BOOL WINAPI TShGetPathFromIDListW(LPCITEMIDLIST, LPWSTR); // allocate a pointer to the function and initialize to null static TShGetPathFromIDListW* ShGetPath = 0; // we need to do this only once per run static bool firsttime = true; if (firsttime) { // get a module handle to shell32.dll HMODULE hShell32Dll = ::GetModuleHandle("shell32.dll"); // if we got a handle, try to get a pointer // to ShGetPathFromIDListW() if (hShell32Dll) ShGetPath = (TShGetPathFromIDListW*) ::GetProcAddress(hShell32Dll, "SHGetPathFromIDListW"); // clear the sentinel firsttime = false; } // if ShGetPath is null, fail if (!ShGetPath) /* failure */; // otherwise, call it else { [ ... ] // use the indirect call to SHGetPathFromIDListW() // note that the following is equivalent to // (*ShGetPath)(/* parameters */); ShGetPath(/* parameters omitted here */); [ ... ] } //---------------------------------------------------------------------------
Home | Top Of Page | Code | Papers | FAQs | Links | Search | Feedback |
Page updated |
Copyright © 1998, 1999 Thin Air Enterprises and Robert Dunn. All rights reserved.