深入解析ATL第二版(ATL8.0)笔记--(2.3节)

深入解析ATL(第二版) 深入解析ATL(第二版),需要的朋友拿去使用。 立即下载
 
深入解析 ATL 第二版 (ATL8.0) 笔记 (2.3 节 )
―― CComBSTR 类
整理:赖仪灵
声明:版权归原作者所有,转载时必须保留此部分文字。
 
    CComBSTR 是非常有用的 ATL 工具类,它封装了 BSTR 数据类型。 CComBSTR 类唯一的数据成员是变量 m_str 。
class CComBSTR {
public:
    BSTR    m_str;
};
 
2.3.1   构造函数和析构函数
    CComBSTR 的构造函数有八个。默认构造函数把 m_str 初始化为 NULL , NULL 也是有效的空 BSTR 串。析构函数用 SysFreeString 释放 m_str 。 SysFreeString 传入参数如果是 NULL ,函数直接返回,这样能确保空对象的析构不会有问题。
CComBSTR() { m_str = NULL; }
~CComBSTR() { ::SysFreeString(m_str); }
    在后面部分,我们将学习到 CComBSTR 的许多方法。其中的优点就是析构函数会在适当的时候释放 BSTR ,因此我们不必释放 BSTR 。
    最常用的 CComBSTR 构造函数之一可能就是用指向 NULL 终止的 OLECHAR 字符数组,也就是 LPOLECHAR 。
CComBSTR(LPOLECHAR pSrc) {
    if (pSrc == NULL) m_str = NULL;
    else {
        m_str = ::SysAllocString(pSrc);
        if (m_str == NULL)
            Atlthrow(E_OUTOFMEMORY);
    }
}
下面的类似代码就将调用上面的构造函数:
CComBSTR str1(OLECHAR(“This is a string of OLECHARs”));
    前面的构造函数会拷贝字符串的内容直到找到字符串的终止字符 NULL 。如果希望拷贝更少的字符串,比如字符串前缀,或者你希望拷贝嵌有 NULL 字符的字符串,就必须显示的指定拷贝的字符个数。此时,调用下面的构造函数:
CComBSTR(int nSize, LPOLESTR sz);
    此构造函数创建 nSize 大小的 BSTR 。从 sz 拷贝指定个数的字符,包括嵌入的 NULL ,最后再追加一个 NULL 。当 sz 是 NULL 时, SysAllocStringLen 忽略拷贝步骤,创建指定大小未初始化的 BSTR 字符串。我们可以用如下形式的代码调用前面的构造函数:
// str2 contains "This is a string"
CComBSTR str2 (16, OLESTR ("This is a string of OLECHARs"));
// Allocates an uninitialized BSTR with room for 64 characters
CComBSTR str3 (64, (LPCOLESTR) NULL);
// Allocates an uninitialized BSTR with room for 64 characters
CComBSTR str4 (64);
    CComBSTR 类为上面的 str4 例子提供了特殊的构造函数,调用它不需要提供 NULL 参数, str4 例子说明了它的用法。下面是构造函数实现:
CComBSTR(int nSize) {
    m_str = ::SysAllocStringLen(NULL, nSize);
}
    BSTR 有一个比较特殊的语义: NULL 指针可以表示合法的空 BSTR 字符串。比如, Visual Basic 认为一个 NULL BSTR 等于一个指向空字符串的指针,字符串的长度是 0 ,它的第一个字符就是 NUL 字符。为了象征性运用它, Visual Basic 认为 IF P=”” 正确, P 是等于 NULL 的 BSTR 。 SysStringLen 函数适当的实现了检测, CComBSTR 提供了 Length 方法的包装:
unsigned int Length() const { return ::SysStringLen(m_str); }
    我们也可以使用下面的拷贝构造函数从一个相等的已经存在并初始化的 CComBSTR 对象来创建、初始化另一个 CComBSTR 对象。
CComBSTR(const CComBSTR& src) {
    m_str = src.Copy();
}
在下面的代码中,创建了 str5 变量,它调用前面的拷贝构造函数以初始化它的每个对象。
CComBSTR str1(OLESTR(“This is a string of OLECHARs”));
CComBSTR str5 = str1;
    注意前面的拷贝构造函数调用了源 CComBSTR 对象的 Copy 方法。 Copy 方法做了一个字符串的拷贝并返回一个新的 BSTR 。因为 Copy 方法根据已经存在的 BSTR 字符串的长度分配内存,然后再拷贝指定长度的字符串内容。所以 Copy 方法可以适当的拷贝嵌有 NUL 字符的 BSTR 字符串。
BSTR Copy() const {
    if ( !*this) { return NULL: }
    return ::SysStringByteLen((char*)m_str, ::SysStringByteLen(m_str));
}
    另两个构造函数从 LPCSTR 字符串初始化 CComBSTR 对象。只有单个参数的构造函数期待一个 NUL 终止的 LPCSTR 字符串。带两个参数的构造函数允许指定 LPCSTR 字符串的长度。下面的两个构造函数期待 ANSI 字符,创建一个包含相等的 OLECHAR 字符的 BSTR 字符串。
CComBSTR (LPCSTR pSrc) {
    m_str = A2WBSTR(pSrc);
}
CComBSTR(int nSize, LPCSTR sz) {
    m_str = A2WBSTR(sz, nSize);
}
    最后一个构造函数也比较奇怪。它期待一个 GUID 参数,然后产生一个包含描述 GUID 的字符串。
CComBSTR(REFGUID src);
    在组件注册建立字符串时此构造函数非常有用。很多时候,我们需要把一个描述 GUID 的字符串内容写入注册表。下面是使用此构造函数的示例代码:
// Define a GUID as a binary constant
static const GUID GUID_Sample = { 0x8a44e110, 0xf134, 0x11d1,
    { 0x96, 0xb1, 0xBA, 0xDB, 0xAD, 0xBA, 0xDB, 0xAD } };
// Convert the binary GUID to its string representation
CComBSTR str6 (GUID_Sample) ;
// str6 contains "{8A44E110-F134-11d1-96B1-BADBADBADBAD}"
 
2.3.2   赋值运算符
    CComBSTR 类定义了三个赋值运算符。第一个用另一个不同的 CComBSTR 对象来初始化一个 CComBSTR 对象。第二个用 LPCOLESTR 指针初始化 CComBSTR 对象。第三个用 LPCSTR 指针初始化 CComBSTR 对象。下面的 operator = () 方法用另一个 CComBSTR 对象来初始化 CComBSTR 对象。
CComBSTR& operator = (const CComBSTR& src) {
    if (m_str != str.m_str) (
        ::SysFreeString(m_str);
        m_str = src.Copy();
        if (!! src && !*this) { AtlThrow(E_OUTOFMEMORY); }
    }
    return *this;
}
    注意上面的赋值运算符使用 Copy 方法,对指定的 CComBSTR 实例做原样拷贝,本节稍后讨论。如下的代码就会调用此运算符:
CComBSTR str1(OLESTR(“This is a string of OLECHARs”));
CComBSTR str7;
str7 = str1;    // str7 contains “This is a string of OLECHARs”
str7 = str1;    // This is a NOP. Assignment operator detects this case
 
第二个 operator = () 方法用指向以 NUL 字符结束的字符串的 LPCOLESTR 指针初始化 CComBSTR 对象。
CComBSTR& operator = (LPCOLESTR pSrc) {
    if (pSrc != m_str) {
        ::SysFreeString(m_str);
        if (pSrc != NULL) {
            m_str = ::SysAllocString(pSrc);
            if (!*this) { AtlThrow(E_OUTOFMEMORY); }
        }
        else { m_str = NULL; }
    }
    return *this;
}
    注意此赋值运算符使用 SysAllocString 函数来分配指定 LPCOLESTR 参数的 BSTR 拷贝。下面的示例代码可以调用此函数:
CComBSTR str8;
str8 = OLESTR(“This is a string of OLECHARs”);
    我们很容易滥用这个赋值操作符。下面就是一种错误的用法:
// BSTR bstrInput contains "This is part one/0and here's part two"
CComBSTR str10 ;
str10 = bstrInput; // str10 now contains "This is part one"
    上面这种情况我们应该用函数 AssignBSTR 。它的实现与 operator=(LPCOLESTR) 很相似。除了内部是用 SysAllocStringByteLen 。
HRESULT AssignBSTR(const BSTR bstrSrc){
    HRESULT hr = S_OK;
    if (m_str != bstrSrc){
        ::SysFreeString(m_str);
        if (bstrSrc != NULL){
            m_str = ::SysAllocStringByteLen((char*)bstrSrc,
                ::SysStringByteLen(bstrSrc));
            if (!*this) { hr = E_OUTOFMEMORY; }
        } else {
            m_str = NULL;
        }
    }
    return hr;
}
所以我们的代码就可以修改为
CComBSTR str10;
str10.AssignBSTR(bstrInput);
 
第三个 operator= 操作符以 LPCSTR 作参数。指向 NUL 结束的字符串。函数把 ANSI 字符转为 Unicode 。然后再创建一个 BSTR 包含 UNICODE 字符串。
CComBSTR& operator=(LPCSTR pSrc) {
    ::SysFreeString(m_str);
    m_str = A2WBSTR(pSrc);
    if (!*this && pSrc != NULL) { AtlThrow(E_OUTOFMEMORY); }
    return *this;
}
    最后两个赋值方法是重载的 LoadString.
bool LoadString(HINSTANCE hInst, UINT nID);
bool LoadString(UINT nID);
    前一个从指定的模块加载字符串资源。后者是从当前的全局变量模块 _AtlBaseModule 加载。
 
2.3.3   CComBSTR 操作符
 
    访问封装在 CComBSTR 类的 BSTR 字符串有四种方法。操作符 BSTR() 允许把 CComBSTR 当原始指针使用。需要把 CComBSTR 对象显式、隐式转化为 BSTR 都可调用此方法。
operator BSTR() const { return m_str; }
    通常,我们可以把一个 CComBSTR 对象传递给一个需要 BSTR 参数的函数。
    当我们取 CComBSTR 对象的地址时, operator&() 方法返回 m_str 变量的地址。使用 CComBSTR 对象地址时要小心。因为返回的是内部 BSTR 变量的地址。我们可以覆盖原来的值而不需要释放。这样会内存泄漏。如果我们在工程中定义宏 ATL_CCOMBSTR_ADDRESS_OF_ASSERT ,这种错误就会抛出异常。利用这个操作符,在需要 BSTR 指针的地方我们可以传递 CComBSTR 对象。
    CopyTo 函数拷贝内部的 BSTR 到指定位置。我们必须显式的调用 SysStringFree 来释放字符串。我们需要返回字符串拷贝时可使用此方法:
STDMETHODIMP SomeClass::get_Name (/* [out] */ BSTR* pName) {
    // Name is maintained in variable m_strName of type CComBSTR
    return m_strName.CopyTo (pName);
}
    Detach 函数返回 CComBSTR 内部的 BSTR 内容。并把对象清空。这样可防止析构函数释放字符串。我们也需要在外部调用 SysStringFree 释放字符串。
BSTR Detach() { BSTR s = m_str; m_str = NULL; return s; }
    Attach 方法执行与 Detach 相反的工作。它把 BSTR 依附在空 CComBSTR 对象上,如果对象不空,先进行释放 :
void Attach(BSTR src) {
    if (m_str != src) {
        ::SysFreeString(m_str);
        m_str = src;
    }
}
    调用 Attach 函数要小心,我们必须拥有 BSTR 的所有权,因为 CComBSTR 最后会释放这个 BSTR 。比如下面的代码就是错误的:
STDMETHODIMP SomeClass::put_Name (/* [in] */ BSTR bstrName) {
    // Name is maintained in variable m_strName of type CComBSTR
    m_strName.Attach (bstrName); // Wrong! We don't own bstrName
    return E_BONEHEAD;
}
    Attach 函数通常在我们被给予 BSTR 所有权,并且我们希望用 CComBSTR 来管理 BSTR 时使用:
BSTR bstrName;
pObj->get_Name (&bstrName); // We own and must free the raw BSTR
CComBSTR strName;
strName.Attach(bstrName); // Attach raw BSTR to the object
    我们可调用 Empty 函数显式释放 CComBSTR 对象的字符串。因为内部是调用 SysStringFree 函数,所以我们可以对空对象调用 Empty 函数:
void Empty() { ::SysStringFree(m_str); m_str = NULL; }
    CComBSTR 还提供了另外两个方法实现 BSTR 与 SAFEARRAY 的转化。
HRESULT BSTRToArray(LPSAFEARRAY * ppArray) {
    Return VectorFromBstr(m_str, ppArray);
}
HRESULT ArrayToBSTR(const SAFEARRAY * pSrc) {
    ::SysStringFree(m_str);
    return BstrFromVector((LPSAFEARRAY)pSrc, &m_str);
}
    其实这两个方法只是 WIN32 函数 BstrFromVector 和 VectorFromBstr 的封装。 BSTRToArray 把字符串的每个字符赋值为调用者提供的一维 SAFEARRAY 的元素。注意释放 SAFEARRAY 是调用者的责任。 ArrayToBSTR 相反:接受一个一维 SAFEARRAY 指针建立一个 BSTR 。注意 SAFEARRAY 里只能是 char 类型元素。否则函数返回类型匹配错误。
 
2.3.4   CComBSTR 的字符串连接
 
    连接 CComBSTR 对象和指定字符串的方法有八个。六个重载的 Append ,一个 AppendBSTR 和一个 operator+=() 方法。
HRESULT Append(LPCOLESTR lpsz, int nLen);
HRESULT Append(LPCOLESTR lpsz);
HRESULT Append(LPCSTR);
HRESULT Append(char ch);
HRESULT Append(wchar_t ch);
HRESULT Append(const CComBSTR& bstrSrc);
CComBSTR& operator+=(const CComBSTR& bstrSrc);
HRESULT AppendBSTR(BSTR p);
    Append(LPCOLESTR lpsz, int nLen); 方法计算原来的字符串加上 nLen 的长度,并分配空间。把原来的字符串内容拷贝到新空间,然后从 lpsz 指定的字符串拷贝 nLen 个字符到分配的空间里。并释放原来的字符串。用新 BSTR 对象赋值。
    其他的重载 Append 函数在内部都是调用这个函数的。区别只是在于获取字符串和长度值的方法不同。
    注意,当我们用 BSTR 作参数调用 Append 函数时,实际上调用的是 Append(LPCOLESTR). 因为编译器把 BSTR 当作 OLECHAR * 参数。因为函数从 BSTR 拷贝字符串直到第一个 NUL 字符。当我们希望拷贝整个 BSTR 时,应该使用 AppendBSTR 方法。
    另一个附加方法可以追加包含二进制数据的数组:
HRESULT AppendBytes(const char * lpsz, int nLen);
    AppendBytes 并不执行从 ANSI 到 UNICODE 的转换。而是用 SysAllocStringByteLen 分配 nLen 字节大小的 BSTR 。然后把结果追加到 CComBSTR 对象。
 
2.3.5   字符大小写转换
    大小写的转换可由方法 ToLower 和 ToUpper 完成。在 Unicode 编译时,内部用 CharLowerBuff API 函数。 ANSI 编译时,字符串先转为 MBCS 然后调用 CharLowerBuff. 结果被转回 UNICODE 存储在新分配的 BSTR 。 m_str 的任何内容在覆盖前都被 SysStringFree 释放。
HRESULT ToLower(){
    if (m_str != NULL) {
#ifdef _UNICODE
        // Convert in place
            CharLowerBuff(m_str, Length());
#else
        UINT _acp = _AtlGetConversionACP();
         int nRet = WideCharToMultiByte(_acp, 0, m_str, Length(),
             pszA, _convert, NULL, NULL);
         CharLowerBuff(pszA, nRet);
         nRet = MultiByteToWideChar(_acp, 0, pszA, nRet, pszW, _convert);
         BSTR b = ::SysAllocStringByteLen((LPCSTR)(LPWSTR)pszW,
            nRet * sizeof(OLECHAR));
            if (b == NULL)
                    return E_OUTOFMEMORY;
        SysFreeString(m_str);
        m_str = b;
#endif
    }
    return S_OK;
}
    注意这些方法可以完全实现大小写转换,即使原始字符串有内嵌 NUL 字符。但是同样也要注意转换也可能有损,如果本地代码页不包括与原始 UNICODE 字符相等的字符。它就不能被转换。
 
2.3.6   CComBSTR 比较操作
    当 CComBSTR 对象是空时 operator!() 返回 true ,否则返回 false.
bool operator !() const { return (m_str == NULL); }
    有四个 operator<() 方法,四个 operator>() ,五个 operator==() 和 operator!=() 方法。另外还有一个重载 operator==() 方法处理与 NULL 比较的特殊情形。在这些方法中的代码大同小异,因为我们现在只讨论 operator<() 方法,这些说明也同样适用于 operator>() 和 operator==() 方法。
    这些操作内部调用了 VarBstrCmp 函数,这与前一版本的 ATL 不同,旧版本不能正确比较包含有内嵌 NUL 字符的 CComBSTR 对象,这些新的操作法大部分时候能正确处理这种比较。因此,下面的代码可以按期望的正常工作。本节稍后我们会讨论用内嵌 NULs 初始化 CComBSTR 对象。
BSTR bstrIn1 = SysAllocStringLen(OLESTR("Here's part 1/0and here's part 2"), 35);
BSTR bstrIn2 = SysAllocStringLen(OLESTR("Here's part 1/0and here’s part 2"), 35);
CComBSTR bstr1(::SysStringLen(bstrIn1), bstrIn1);
CComBSTR bstr2(::SysStringLen(bstrIn2), bstrIn2);
bool b = bstr1 == bstr2; // correctly returns false
    在 operator<() 方法的第一个重载版本里,比较与一个 CComBSTR 参数进行。
bool operator<(const CComBSTR& bstrSrc) const {
    return VarBstrCmp(m_str, bstrSrc.m_str, LOCALE_USER_DEFAULT,0) == VARCMP_LT;
}
    在 operator<() 方法的第二个重载版本里,比较与一个 LPCSTR 参数进行。 LPCSTR 与内部的宽字符 BSTR 类型不同。因此,实现时通过构造一个临时 CComBSTR 对象后再调用第一个版本的 operator<() 。
bool operator>(LPCSTR pszSrc) const {
    CComBSTR bstr2(pszSrc);
    return operator>(bstr2);
}
    第三个版本的参数是 LPCOLESTR ,实现与前一版本类似:
bool operator>(LPCOLESTR pszSrc) const {
    CComBSTR bstr2(pszSrc);
    return operator>(bstr2);
}
    第四个版本的参数是 LPOLESTR ,它也是简单调用前一种实现:
bool operator>(LPOLESTR pszSrc) const {
    return operator>((LPCOLESTR)(pszSrc));
}
 
2.3.6   CComBSTR 的持续化支持
    CComBSTR 类的最后两个方法实现 BSTR 字符串与流之间的读写。 WriteToStream 方法首先写入一个 ULONG 类型的值表示 BSTR 字符串的个数。然后接着就写入 BSTR 字符串。注意方法没没有写入标签值说明字节顺序。因此,与流数据的大多数情况一样, CComBSTR 通常以特定硬件结构格式把字符串写入到流。
HRESULT WriteToStream(IStream* pStream) {
    ATLASSERT(pStream != NULL);
    if(pStream == NULL)
        return E_INVALIDARG;
    ULONG cb;
    ULONG cbStrLen = ULONG(m_str ? SysStringByteLen(m_str)+sizeof(OLECHAR) : 0);
    HRESULT hr = pStream->Write((void*) &cbStrLen, sizeof(cbStrLen), &cb);
    if (FAILED(hr))
        return hr;
    return cbStrLen ? pStream->Write((void*) m_str, cbStrLen, &cb) : S_OK;                                        
}
    ReadFromStream 方法从指定流读取一个 ULONG 值。然后分配适当的空间,再读取 BSTR 字符串。调用 ReadFromStream 方法 CComBSTR 对象必须为空。否则, DEBUG 时会有 ASSERT 错误, Release 时发生内存泄漏。
HRESULT ReadFromStream(IStream* pStream) {
    ATLASSERT(pStream != NULL);
    ATLASSERT(!*this); // should be empty
    ULONG cbStrLen = 0;
    HRESULT hr = pStream->Read((void*) &cbStrLen, sizeof(cbStrLen), NULL);                               
    if ((hr == S_OK) && (cbStrLen != 0)) {
        //subtract size for terminating NULL which we wrote out
        //since SysAllocStringByteLen overallocates for the NULL
        m_str = SysAllocStringByteLen(NULL, cbStrLen-sizeof(OLECHAR));
        if (!*this) hr = E_OUTOFMEMORY;
        else hr = pStream->Read((void*) m_str, cbStrLen, NULL);
        ...
    }
    if (hr == S_FALSE) hr = E_FAIL;
    return hr;
}
 
2.3.7   BSTR 的注意点、嵌入 NUL 字符的字符串、
    编译器认为 BSTR 和 OLECHAR* 同义。事实上, BSTR 是 OLECHAR* 的一种 typedef 定义。看 wtypes.h 的定义:
typedef /* [wire_marshal] */ OLECHAR __RPC_FAR * BSTR;
    这是非常头痛的。并不是所有的 BSTR 都是 OLECHAR* ,不是所有的 OLECHAR* 是 BSTR 。正因为大多数情况下 BSTR 能和 OLECHAR* 一样使用,这方面非常容易误导我们。
STDMETHODIMP SomeClass::put_Name(LPCOLESTR pName);
 
BSTR bstrInput = ...
pObj->put_Name(bstrInput); // This works just fine... usually
SysFreeString(bstrInput);
    在上面的例子中,因为 bstrInput 参数是 BSTR 类型,在字符串内可能包含 NUL 字符。 put_Name 方法的期望参数是 LPCOLESTR(NUL 字符结束的字符串 ) ,可能会只保存第一个 NUL 字符前面部分的字符。也就是可能切断字符串。
    当要求 [out] OLECHAR* 参数时也不能使用 BSTR 。比如:
STDMETHODIMP SomeClass::get_Name(OLECHAR** ppName) {
    BSTR bstrOutput =...    // Produce BSTR string to return
    *ppName = bstrOutput;   // This compiles just fine
    return S_OK;            // but leaks memory as caller 
                            // doesn't release BSTR
}
    相反地,需要 BSTR 时也不能使用 OLECHAR* 。即使碰巧能运作,也是一个潜在的 BUG 。比如,下面的代码就是不正确的:
STDMETHODIMP SomeClass::put_Name(BSTR bstrName);
// Wrong! Wrong! Wrong!
pObj->put_Name(OLECHAR("This is not a BSTR!"))
    如果 put_Name 方法用 SysStringLen 函数获取 BSTR 的长度,它会尝试从整型前缀读取长度,但是字符串并没有这样的整数。即使 put_Name 方法间接那样做也会有问题,存在进程外访问。在此情景,列集代码调用 SysStringLen 获取字符串长度放入请求包。这个长度值通常非常大(例子中是字符串开始四个字节的字面数值),当尝试拷贝字符串时常常引起崩溃。
    因为编译器不知道 BSTR 和 OLECHAR* 的区别。我们很容易因无意中用一个内嵌 NUL 字符的 BSTR 字符串代替 CComBSTR 而引起非法运行。下面讨论在这些情况下应该使用哪种方法。
    构造一个 CComBSTR 对象,必须指定字符串的长度:
BSTR bstrInput=SysAllocStringLen(OLESTR("Part one/0and Part two"), 36);
CComBSTR str8 (bstrInput);      // Wrong! Unexpected behavior here
                                // Note: str8 contains only “Part one"
CComBSTR str9 (::SysStringLen (bstrInput), bstrInput); // Correct!
// str9 contains "This is part one/0and here's part two"
    把一个内嵌 NUL 字符的 BSTR 赋值给 CComBSTR 对象将肯定错误。比如:
// BSTR bstrInput contains "This is part one/0and here's part two"
CComBSTR str10;
str10 = bstrInput; // Wrong! Unexpected behavior here
                    // str10 now contains "This is part one"
    执行 BSTR 的赋值操作最简单的方法就是用 Empty 和 AppendBSTR 方法:
str10.Empty();                // Insure object is initially empty
str10.AppendBSTR(bstrInput);    // This works!
    在实际中,虽然 BSTR 可以包含内嵌的 NUL 字符,但是多数时候都不包含。当然,这也表示多数时候我们不能轻易发现由错误使用 BSTR 引起的潜在 BUG 。



深入解析ATL(第2版) 深入解析ATL(第2版) ,原书清晰扫描,需要的朋友可以下载 立即下载

相关推荐

深入解析ATL

深入解析ATL Four leading Windows programming experts systematically reveal ATL&#39;s inner workings, explaining not just how ATL works, but why it works the way it does. Client-side developers will master ATL&#39;s resources for windowing, COM control, MFC integration, web service proxy generation, and more. Server-side...

ATL介绍

ATL 介 绍( 一) 吕 思 伟 ---- 潘 爱 民 一、 什 么 是ATL ---- 1 .COM 的 发 展 及 其 局 限 性 ---- 自 从1993 年Microsoft 首 次 公 布 了COM 技 术 以 后,Windows 平 台 上 的 开 发 模 式 发 生 了 巨 大 的 变 化, 以COM 为 基 础 的 一 系 列 软 件 组 件 化 技 术 将Windows 编 程

放飞梦想 4862

HR离职预测模型实战:可解释、可干预、可落地的三层架构

员工离职预测本质上是组织健康度的早期诊断技术,其核心原理在于从行为、心理与结构数据中识别稳定性衰减信号。该技术的价值不仅在于提升预测准确率,更在于将模糊的‘人才风险’转化为可归因、可干预的具体因子,如工龄阶段、满意度落差、薪酬分位数等。典型应用场景覆盖HRBP主动留任、业务部门梯队预警及HRIS系统智能升级。本文聚焦于构建具备业务可解释性与操作闭环能力的离职预警模型,深度融合HR工作流与机器学习工程实践,尤其强调逻辑回归基线、特征业务化重构及三层递进式架构设计。

赵士杰的博客 388

深入解析ATL(第二版ATL8.0)(2.1-2.2节)

第二章 字符串和文本翻译:赖仪灵出处:http://blog.csdn.net/laiyiling, http://www.cppblog.com/techlab 声明:版权归原作者拥有,请勿随意转载此翻译文档,保留一切权利。字符串引入了多种不同的字符集。COM组件通常需要使用多个不同的字符集,并在不同的字符集之间相互转换。ATL提供了一些字符串转换类,需要时这些类会自动把字符集进行转换,如果不需

C++、.NET、WPF、Android 4448

Opencv计算相机响应函数(Camera Response Function)与Radiance

本文主要用于记录自己在学习CRF过程中遇到的一些问题。 CRF模型 I=f(V(t⋅L))I=f(V( t\cdot L))I=f(V(t⋅L)) 其中III为相机灰度值Intensity,LLL为环境中irradiance,ttt为曝光时间,VVV为相机镜头参数,fff为相机响应函数CRF。 有些论文中认为相机镜头参数是线性的不考虑,但有些考虑了衰减即靠近图像边缘亮度会暗一些,但没有看到二者同时计算的,如果同时计算两个非线性的函数,我觉得可能会有不确定性,在这里还请教一下了解的朋友。 控制

larrydong的博客 6002

python k线顶分型_【缠论】分型、笔的定义及其程序化

K线包含关系处理和分型的定义如果一个K线的高低点全在另一K线的范围里,称之为包含关系。针对这种K线,需要进行一些处理,去除包含关系,处理方法:向上时,把两K线的高点当高点,而两K线低点中的较高者当成低点;反之,当向下时,把两K线的低点当低点,而两K线高点中的较低者当成高点,这样就把两K线合并成一新的K线。经过这样的处理,所有K线图都可以处理成没有包含关系的图形。两K线的包含关系,可以分成四种:1)...

weixin_39717152的博客 2825

深入解析ATL第二版(ATL8.0)笔记(2.3节)

整理:赖仪灵出处:http://blog.csdn.net/laiyiling, http://www.cppblog.com/techlab 声明:版权归原作者所有,转载时必须保留此部分文字。     CComBSTR是非常有用的ATL工具类,它封装了BSTR数据类型。CComBSTR类唯一的数据成员是变量m_str。class CComBSTR {public:  

天涯旧客 965

金蝶云星空初级实施认证考题

Q4.[单选题] (1.5分) 总账中的多账簿账表主要是基于什么进行查看的Q47.[多选题] (2.5分) 销售订单的收款计划页签勾选预收之后,预收控制环节可以控制哪些节点?

m0_51994557的博客 3125

深入解析ATL(第二版ATL8.0)(1.11-1.13节)

深入解析ATL(第二版ATL8.0)(1.11-1.13节), 文章在这里:http://www.cppblog.com/TechLab/articles/12671.html 

C++、.NET、WPF、Android 1776

高项49个过程,输入、工具技术、输出的思维导图

为了制定项目章程的管理方法主要是采用了数据收集技术,例如通过头脑风暴,通过召集主题专家和团队成员收集信息、解决方案,并在短时间内获得大量创意,并对大量创意进行分析和排序,选出最优化最适合本项目方案,并对项目成员的不同差异观点,进行了引导和对冲突的管理,最终有助于项目干系人的目标、成功标准的内容达成一致意见,形成完善的项目章程。在本过程中得到项目章程,在制定项目章程中需要包括信息有:项目名称、项目目标、项目总里程碑进度表、项目经理、主要干系人及财务预算信息。本过程在仅开展一次,或仅在项目的预定义点开展。

Legend_Jiang的博客 6185

飞牛fnNAS搭建个人小账本

本文介绍了如何在飞牛NAS上部署ezBookkeeping个人记账应用。该开源工具支持多平台使用,具备双因素认证、多级分类等功能。文章详细讲解了Docker激活、镜像下载、容器部署等步骤,并展示了账户管理、交易记录等基础操作。同时指导用户通过ZeroTier实现远程访问,方便随时随地使用个人记账系统。该方案既保障了数据隐私,又提供了专业记账功能,适合对财务隐私敏感的用户。

dgnankai的专栏 783

深入解析ATL(第二版ATL8.0)(1.1-1.3节)

深入解析ATL(第二版ATL8.0),文章在这里:http://www.cppblog.com/TechLab/articles/11561.html

C++、.NET、WPF、Android 2475

orin+96712接GMSL2相机调试经验

orin gmsl camera调试

乐于分享 4457

深入解析ATL(第二版ATL8.0)(1.8-1.10节)

深入解析ATL(第二版ATL8.0),文章在这里:http://www.cppblog.com/TechLab/articles/12172.html  

C++、.NET、WPF、Android 2022

基础入门篇 | YOLOv8 项目【训练】【验证】【推理】最简单教程 | YOLOv8必看 | 最新更新,直接打印 FPS,mAP50,75,95

三个文件搞定YOLOv8【训练】【验证】【推理】🚀🚀🚀

Coding... 2万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

深入解析ATL(第二版ATL8.0)(1.4-1.7节)

深入解析ATL(第二版ATL8.0),文章在这里:http://www.cppblog.com/TechLab/articles/11761.html

C++、.NET、WPF、Android 1802

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

上一篇: Bertrand Meyer谈契约式设计
下一篇: Anders Hejlsberg谈C#设计过程
techweb
博客等级 码龄19年 226粉丝 3842原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值