7

I'm late to the Win32 party and there's a sea of functions such as _tprintf, TEXT(), there are also libraries like strsafe.h which have such functions like StringCchCopy(), StringCchLength(), etc.. Basically, Win32 API introduces a bunch of extra functions and types on top of C which can be confusing to a C programmer who hasn't worked with Win32 much. I do not have a problem finding the definitions of these types and functions on MSDN. However, I do have a problem finding guidelines on when and why they should be used.

I have 2 questions:

  1. How important is it to use all of these types and special functions which Microsoft has provided on top of standard C when programming with Win32? Is it considered good practice to do away with all standard C functions and types and use entirely Microsoft wrappers?

  2. Is it okay to mix standard C functions in with these Microsoft types and functions? For example, to use malloc() instead of HeapAlloc(), or to use printf() instead of _tprintf() and etc...?

I have a copy of Charles Petzold's Programming Windows Fifth Edition book but it mostly covers GUI stuff and not a lot of the remainder of the API.

the_endian
  • 2,259
  • 1
  • 24
  • 49
  • The `winapi` to an extend guarantees that you could get away with a single piece of code for a wide range of windows OSs. Your question title is vague. Why would you even want to compare. The idea is pretty much clear. Isn't it? – sjsam Jan 21 '18 at 12:47
  • @sjsam Apparently not because that's why I'm confused. Uhh I just changed the question phrasing hopefully to make it more clear. – the_endian Jan 21 '18 at 13:01
  • 2
    Do you want to program some stuff for yourself, or do you have to share your code with others, who will compile it with a variety of Windows versions and compilers, over a period longer than a few years? – Jongware Jan 21 '18 at 13:02
  • 1
    @usr2564301 ah thanks for asking me that. Well, sometimes I don't know and I work on small projects with colleagues which turn big so I suppose it's better to go the extra mile upfront and make it more versatile – the_endian Jan 21 '18 at 13:09

3 Answers3

8

There are actually 3 questions here, the ones you explicitly asked, and the one you didn't. Let's get that last one out of the way first, as it tends to cause the most confusion:

What are those _t-extensions offered by the Microsoft-provided CRT?

They are generic-text mappings, introduced to make it possible to write code that targets both ANSI-based systems (Win9x) as well as Unicode-based systems (Windows NT). They are macros that expand to the actual function calls, based on the _UNICODE and _MBCS preprocessor symbols. For example, the symbol _tprintf expands to either printf or wprintf.

Likewise, the Windows API provides both ANSI and Unicode versions of the API calls. They, too, are preprocessor macros that expand to the actual API call, depending on the preprocessor symbol UNICODE. For example, the CreateFile symbol expands to CreateFileA or CreateFileW.

Generic-text mappings haven't been useful in the past two decades. Today, simply use the Unicode versions of the CRT and API calls (e.g. wprintf and CreateFileW). You can define _UNICODE and UNICODE for good measure, too, so that you don't accidentally call an ANSI version.

there are also libraries like strsafe.h which have such functions like StringCchCopy(), StringCchLength()

Those are safe variants of the CRT string manipulation calls. They are safer than e.g. strcpy by providing the buffer size of the destination, similar to strncpy. The latter, however, suffers from an awkward design decision, that causes the destination buffer to not get zero-terminated, in case the source won't fit. StringCchCopy will always zero-terminate the destination buffer, and thus provides additional safety over the CRT implementations. (Note: C11 introduces safe variants, e.g. strncpy_s, that will always zero-terminate the destination array, in case the input is valid. They also validate the input, calling the currently installed constraint handler when validation fails, thus providing even stronger safety than the strsafe.h implementations. The bounds-checked implementations are a conditional feature of C11.)

How important is it to use all of these types and special functions which Microsoft has provided on top of standard C when programming with Win32? Is it considered good practice to do away with all standard C functions and types and use entirely Microsoft wrappers?

It is not important at all. You can use whichever is more suitable in your scenario. If in doubt, writing portable (i.e. Standard C) code is generally preferable. You only ever want to call the Windows API calls, if you need the additional control they offer (e.g. HeapAlloc allows more control over the allocation than malloc does; likewise CreateFile provides more options than fopen).

Is it okay to mix standard C functions in with these Microsoft types and functions? For example, to use malloc() instead of HeapAlloc(), or to use printf() instead of _tprintf() and etc...?

In general, yes, as long as you match those calls: HeapFree what you HeapAlloc, free what you malloc. You must not mix HeapAlloc and free, for example. In case a Windows API call requires special memory management functions to be used, it is explicitly pointed out in the documentation. For example, if FormatMessage is requested to allocate the buffer to return data, it must be freed using LocalFree. If you do not request the API to allocate a buffer, you can pass in a buffer allocated any way you like (malloc, HeapAlloc, IMalloc::Alloc, etc.).

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 1
    `strncpy()` - _"If count is reached before the entire array src was copied, the resulting character array is not null-terminated."_ Compare to `StringCchCopy()` which always null-terminates the destination buffer. – zett42 Jan 21 '18 at 13:59
  • @zett42: True, I forgot about that. So `StringCchCopy` does in fact address an entire class of bugs, that `strncpy` introduced. – IInspectable Jan 21 '18 at 14:01
  • MSVC also offers the _s set of functions which perform parameter checking (although in the case of strcpy_s will return an error and set *dest to 0 if the destination is too small rather than perform a truncated copy), MSVC also uses safe overloads for many standard C library functions if compiled as C++ and if the parameter is actually a static array. – SoronelHaetir Jan 21 '18 at 22:36
  • @SoronelHaetir: The security enhanced versions in the CRT are actually an optional part of C11, but they are not widely implemented. The C++ overloads don't add much safety, though. `template char* strncpy()`, for example, would still not zero-terminate the destination, in case the source wouldn't fit. They merely allow passing an array without repeating its size for the destination. – IInspectable Jan 22 '18 at 08:17
3

Note that different versions of the OS use different definitions of base types and use different alignments/padding. Remember 8086, 386 and now Core i7(16, 32, 64 bits).

When structs need to be compatible with earlier versions, they typically use pre-defined integer widths and pad for legacy alignment.

For that reason, the types from the API must be used in API calls.

Also in memory there used and maybe are different memory models of process memory and shared memory. For example the clipboard uses a form of shared memory. It is important to use the memory allocation mechanisms Microsoft advices here for API calls.

For everything non-API I use the standard C functions and types.

machine_1
  • 4,266
  • 2
  • 21
  • 42
Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • 1
    Not sure why a downvote on this but `For everything non-API I use the standard C functions and types.` This is pretty much clear from the name itself. Isn't it? – sjsam Jan 21 '18 at 12:49
  • 1
    @sjsam so if you have routines in your code which don't happen to use any API functions, you will use completely standard C types in the same codebase as other functions which have to interface with APIs and thus use the MS types there? Do you end up using a lot of casts? – the_endian Jan 21 '18 at 13:11
  • I down-voted this contribution, because it is flat-out wrong. There is absolutely no requirement at all to use the types defined in the SDK headers with API calls. You can pass in a `malloc`ed buffer, whenever an API call requires a caller-provided buffer, for example. `malloc` is implemented to return a buffer that is suitably aligned for *any* type. – IInspectable Jan 21 '18 at 13:22
3

It is possible to create programs on Windows without using any standard C library functions but most programs do and then you might as well use malloc over HeapAlloc. malloc will use HeapAlloc or VirtualAlloc internally but it is probably tuned for better performance/less fragmentation compared to the raw API. It also makes it easier to port to POSIX in the future. You will still be forced to use LocalFree/GlobalFree/HeapFree in some places where the API allocates memory for you.

Handling text needs special consideration and you need to decide if you need Unicode support or not. A stroll down memory lane might shed some light on why things are the way they are.

Back when Windows 95/98 was king you could use the char/CHAR narrow string types with both the C standard functions and the Windows API. There was virtually no Unicode support except for a handful of functions.

On Windows NT4/2000 however the native string type is WCHAR (UTF-16 LE but Microsoft just calls it Unicode). If you are using Microsoft Visual C++ then you have access to wide string versions of the C standard libray beyond what the C standard actually requires to ease coding for this platform. When coding for Windows using the Microsoft toolchain you can assume that the Windows SDK WCHAR type is the same as the wchar_t type defined by C.

Because the development of 95 and NT4 overlapped they share the same API and every function that receives/returns a string has two versions, one with a A suffix ("ANSI") and one with a W suffix. On Windows 95 the W functions are just stubs that return failure.

When you include Windows.h it will create defines like #define CreateProcess CreateProcessW if UNICODE is defined or #define CreateProcess CreateProcessA if not.

Visual C++ does the same thing with the tchar.h header. It uses the _UNICODE define to decide if the TCHAR type and the _t* functions use the char or wchar_t type. This meant that you could create two releases from the same source code, one for Windows 95/98/ME and one with full Unicode support.

This is not that relevant anymore but you still need to make a choice because things will be defined for one or the other.

It is still perfectly valid to do

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>

void foo()
{
  TCHAR buf[100];
  SomeWindowsFunction(buf, 100);
  _tprintf(_T("foo: %s\n"), buf);
}

although you will see many people go straight for WCHAR and wprintf these days.

The StrSafe functions were added to make it easier to write bug free code, they still have the same A/W duplication.

You cannot mix and match WCHAR with printf, even if you use %ls in the format string the string will be converted internally and not all Unicode strings will convert correctly.

If POSIX portability is not a requirement then I suggest that you use the wide function extensions provided by Microsoft when you need a C library function.

Anders
  • 97,548
  • 12
  • 110
  • 164
  • Thank you! Is `_T()` as used in your example the same as `TEXT()`? – the_endian Jan 21 '18 at 13:45
  • 1
    No-ish. TEXT is the Windows SDK (CHAR or WCHAR), _TEXT (char or wchar_t) is the CRT and _T is shorthand for _TEXT. They could be different if you only set one of the controlling defines but I have never seen a real project pull that off in practice. See the "same thing" link for more info (https://blogs.msdn.microsoft.com/oldnewthing/20040212-00/?p=40643#). – Anders Jan 21 '18 at 13:51
  • Windows NT has been Unicode from the beginning, starting with the initial release of Windows NT 3.1. Granted, that was UCS-2, because UTF-16 hadn't been invented until 1996, and implemented in Windows 2000. – IInspectable Jan 21 '18 at 13:57
  • Yes, my history lesson skipped some parts. 2000 is not fully UTF-16 compatible everywhere either, it lacks surrogate support some places in GDI (IIRC). – Anders Jan 21 '18 at 14:03
  • there is no reason in this day in age to use the `T` defines. Any use of the WIN32 API should be done via the UCS-2/UTF-16 Wide character apis. I would highly recommend against using the ANSI functions as they have odd behaviors across locales and do not behave the way you always expect. Instead if you need POSIX convert from UTF-8 right before the API call. – Mgetz Jan 21 '18 at 15:32
  • If you want to port to POSIX in the future then using _T and TCHAR is better because TCHAR can be plain char on those platforms. If you use wchar_t directly then you will run into problems because POSIX does not support wchar_t strings for things like fopen etc. Using UTF-8 internally on Windows is annoying and using wchar_t internally on POSIX is annoying. – Anders Jan 21 '18 at 15:38
  • @Anders or you could just convert from UTF-8 before the call. It's really not worth the pain of dealing with the ANSI apis. Not to mention that entire APIs don't have ANSI versions. If you're calling `fopen` on windows it won't work with POSIX anyway because it doesn't support UTF-8 and MS has no intention of doing so. – Mgetz Jan 21 '18 at 15:41
  • @Mgetz I'm not suggesting that you use the ANSI API on Windows. Set the UNICODE defines and be fully Unicode on Windows. On other platforms however it is better to use char. If you are writing GUI independent code then TCHAR can be handy if you support anything other than Windows, you just have to fill in the tchar.h file on your own for those platforms. – Anders Jan 21 '18 at 15:43
  • @Anders ah, that's not how your comments read. In that case I would suggest you still not use the `T` defines as they aren't portable and create unnecessary encoding confusion in the actual code. I'd still say to be explicit as per [UTF8Everywhere](http://utf8everywhere.org/). – Mgetz Jan 21 '18 at 15:46
  • @Mgetz UTF8Everywhere can be a good idea but it really depends on what your application does. If you are not really dealing with UTF-8 in your file formats and things like that then it is just an extra conversion layer. If possible, just use the native format of your platform. – Anders Jan 21 '18 at 15:51