在计算亲和数的时候,由于涉及到密集运算,有必要把计算工作转移到背景线程,以避免界面会失去响应。在.Net 1.0中,可以用
ManualResetEvent、
线程和
Delegate的异步调用来实现,但是在.Net 2.0中,可以使用
BackgroundWorker对象
来简化这个工作。这个对象自动化了进度报告和终止线程的功能。
要使用这个对象来创建工作线程,首先需要加入一个BackgroundWorker对象到表单(Form)或者用户控件(UserControl),然后调用其RunWorkerAsync方法:
private: System::Void AmicableNumberView_Load(System::Object^ sender, System::EventArgs^ e)

...{
propertyGrid->SelectedObject = Range;
listViewPairs->VirtualListSize=0;
timer->Start();
m_rAmicableNumberPairs->Clear();
backgroundWorker->RunWorkerAsync (Range);
}
在线程创建之后会自动触发DoWork事件。这个事件中的处理类似于1.1中的线程函数体,可以通过访问DoWorkEventArgs参数的argument属性来访问在用RunWorkerAsync启动线程时传递的参数,以及调用ReportProgress定时报告进度。
private: System::Void backgroundWorker_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e)

...{
CAmicableNumberRange range=(CAmicableNumberRange)e->Argument;
int nNumbers=range.Max-range.Min;
int nPercentComplete=0;
m_rGenerator->Range=range;
m_rGenerator->StartWork();

while(!backgroundWorker->CancellationPending && m_rGenerator->DoWork())...{
nPercentComplete = (int)(
(float)(m_rGenerator->GetNumberInQuestion()-range.Min)
/(float)nNumbers
* 100);
backgroundWorker->ReportProgress(nPercentComplete,m_rGenerator->GetProgress());
}
m_rGenerator->EndWork();
if(backgroundWorker->CancellationPending)
e->Cancel=true;
}

在主线程中可以处理ProgressChanged事件来更新界面。
private: System::Void backgroundWorker_ProgressChanged(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ e)

...{
progressBar->Value=e->ProgressPercentage;
if(e->UserState!=nullptr)//a new pair is found

...{
CAmicableNumberPair^ anp=(CAmicableNumberPair^)e->UserState;
m_rAmicableNumberPairs->Add(anp);
}
}
在计算完成之后,可以处理RunWorkCompleted事件来更新界面
private: System::Void backgroundWorker_RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e)

...{
timer->Stop();
if(e->Error!=nullptr)
MessageBox::Show(e->Error->Message);
progressBar->Value=100;
}
当然,为了避免过于频繁地更新界面,仍然可以使用传统的定时更新方法
private: System::Void timer_Tick(System::Object^ sender, System::EventArgs^ e)

...{
if(IsHandleCreated)

...{
int nOldListSize=listViewPairs->VirtualListSize;
int nNewListSize=m_rAmicableNumberPairs->Count;
if(nNewListSize>nOldListSize)

...{
listViewPairs->VirtualListSize=nNewListSize;
listViewPairs->RedrawItems(nOldListSize, nNewListSize - 1, true);
}
}
}
用户有时会在计算中途取消计算,这时候可以调用CancelAsync方法,然后等待至IsBusy属性成为false为止。在DoWork中检查到CancellationPending属性为true时,应该终止并从DoWork中返回。
void Stop()

...{
backgroundWorker->CancelAsync();
while(backgroundWorker->IsBusy)

...{
Application::DoEvents();
}
}
对于Windows Forms,应该在表单的FormClosing事件处理中自动停止计算,但是对于UserControl,由于没有办法判断其容器的类型,所以只能在容器中手动调用一个方法来终止计算。下面是在MFC8.0的容器类CWinFormsView中调用的方法:
void CAmicableNumbersView::OnDestroy()

...{
m_ViewControl->Stop();
CWinFormsView::OnDestroy();
}
在使用MFC的.Net容器类和用户控件来计算亲和数时,需要在.Net控件的Load事件之前给控件传递一个计算范围参数。在MFC中,.Net控件是以ActiveX控件的形式被创建的,但是在传统的窗口初始化函数CWinFormsView::OnInitialUpdate被执行之前,控件的Load事件已经被调用了,这使得参数的传递更加困难。.Net控件是在窗口的Create方法中被创建的:
BOOL CWinFormsView::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext)

...{
m_nFlags |= WF_ISWINFORMSVIEWWND;
BOOL ok=__super::Create(lpszClassName, lpszWindowName, dwStyle, rect,pParentWnd, nID, pContext);
ASSERT(ok);
ok=ok && m_control.CreateManagedControl(m_pManagedViewType,WS_VISIBLE, rect, this,nID);
ASSERT(ok);
.....
return ok;
}

但是在CWinFormsControl::CreateManagedControl返回之前,Load事件就已经被触发了。在Load事件中设置一个断点,在执行时查看调用堆栈发现,这个事件是COleControlSite::CreateControlCommon中的下面语句调用的
// control is visible: just activate it
hr = DoVerb(OLEIVERB_INPLACEACTIVATE);
也就是说,为了在这之前调用自定义代码,需要自定义一个COleControlSite类。在MFC6.0中,COleControlSite是一个未文档化的类,但是在微软知识库文章Q236312和Q329802中描述了自定义全局ActiveX控件站点的方法。从MFC7.0开始,COleControlSite被文档化了,而且可以通过重载CWnd::CreateControlSite来使用自定义控件站点类。这个方法在MFC7.0中被用来扩展CHTMLView和CDHTMLDialog,而且在MFC8.0中被用来将默认的COleControlSite替换成CWinFormsControlSite以提供托管控件支持。同样的方法可以用来将CWinFormsControlSite替换成其派生类来自定义控件创建过程。
virtual BOOL CreateControlSite(COleControlContainer* pContainer, COleControlSite** ppSite, UINT nID, REFCLSID clsid)

...{
COleControlSite* pSite=NULL;
if (InlineIsEqualGUID(clsid , CLSID_WinFormsControl))

...{
pSite=new CCAmicablesControlSite(this, pContainer);
if(pSite)

...{
*ppSite=pSite;return TRUE;
}
}
return FALSE;
}
自定义控件创建过程中需要重载的函数是CWinFormsControlSite::CreateOrLoad。CWinFormsControlSite的成员函数并不多,只有这个函数是在控件的创建之前被调用,并且在控件的Load事件被触发之前结束执行。重载的函数只是简单的调用视图的一个成员函数,而由视图来负责进行具体的处理
class CCAmicablesControlSite:public CWinFormsControlSite

...{
public:
CCAmicablesControlSite(CAmicableNumbersView* pView, COleControlContainer* pCtrlCont)

: CWinFormsControlSite(pCtrlCont) ,m_pView(pView) ...{}

virtual HRESULT CreateOrLoad(const CControlCreationInfo& creationInfo)...{
HRESULT hr =CWinFormsControlSite::CreateOrLoad(creationInfo);

if (SUCCEEDED(hr)) ...{
m_pView->OnControlCreated();
}
return hr;
}
protected:
CAmicableNumbersView* m_pView;
};
void CAmicableNumbersView::OnControlCreated()

...{
m_ViewControl = safe_cast<Amicable::AmicableNumberView ^>(this->GetControl());
m_ViewControl->Range = m_range;
}
和传统的视图不同,使用CWinFormsView时,不能在重载的PreCreateWindow函数中返回FALSE来终止视图的创建。这是因为CWinFormsView::Create没有对视图创建失败的情况进行正确处理,而是在创建.Net控件失败之后仍试图访问这个对象而造成的。如果确实要终止视图的创建,那么可以重载CWinFormsView::Create,在这之前终止视图的创建。
BOOL CAmicableNumbersView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)

...{
// TODO: Add your specialized code here and/or call the base class
//prompt for search range
FormRange^ fr= gcnew FormRange();
fr->Range=m_range;
if (fr->ShowDialog() == DialogResult::OK)

...{
Amicable::CAmicableNumberRange range=fr->Range;
m_range=range;
}
else
return FALSE;
return CWinFormsView::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
}

尽管可以使用MFC来给应用程序加上托管扩展,但是托管代码和非托管代码互操作造成的性能影响还是很明显。没有绝对必要的话,还是建议像上面调用FormRange表单那样直接调用托管的表单而不是使用MFC的托管支持。
一个算法的优化的相关代码
// Amicable.h
#pragma once
#include "WinFormThread.h"
using namespace System;
using namespace System::Collections::Generic;

/**//// <summary>
/// Amicable Number Generator
/// Search a range for amicable pairs.
/// </summary>

namespace Amicable ...{

/**//// <summary>
/// An amicable pair consists of two distinct integers
/// for which the sum of proper divisors (the divisors excluding the number itself)
/// of one number equals the other.
/// for example, (220, 284) is amicable because
/// the restricted divisor function of 220
/// 1 + 2 + 4 + 5 + 10 + 11 + 20 + 22 + 44 + 55 + 110
/// equals to 284
/// and the restricted divisor function of 284
/// 1 + 2 + 4 + 71 + 142
/// equals to 220
/// </summary>
public ref class CAmicableNumberPair:public ICloneable

...{
public:

/**//// <summary>The smaller number in the pair</summary>
property Int32 m ;

/**//// <summary>The larger number in the pair</summary>
property Int32 n ;

/**//// <summary>
/// If both numbers are happy numbers.
/// <remarks>
/// A number is happy when repeating the process of
/// taking the sum of the squares of the digits of a given number
/// eventually yields 1.
/// </remarks>
/// </summary>
property Boolean isHappy ;

/**//// <summary>
/// If both numbers are Harshad numbers.
/// <remarks>
/// A Harshad number, or Niven number, is an integer that
// is divisible by the sum of its digits.

/**//// </remarks>
/// </summary>
property Boolean isHarshad;
virtual Object^ Clone();
};

/**//// <summary>
///
/// </summary>
public ref class CAmicableNumberPairGeneratorProgressEventArgs : public EventArgs

...{
public:
property int nNumberInQuestion;

/**//// <summary>the generated amicable pair, nullptr if the number is not amicable </summary>
property CAmicableNumberPair^ Pair;
};

/**////<summary>The range to look for amicable pairs</summary>
public value class CAmicableNumberRange

...{
public:
property Int32 Min;
property Int32 Max;
};

/**////<summary>The amicable pair generator, look a range for amicable pairs</summary>
public ref class CAmicableNumberPairGenerator:public IWinFormThreadWork

...{
public:

CAmicableNumberPairGenerator()...{}

/**///////////////////////////////////////////
//Properties

/**//// <summary>The range of amicable pairs </summary>
property CAmicableNumberRange Range;

/**////////////////////////////////////////////////
///Methods
public:
virtual System::Boolean StartWork() ;
virtual System::Boolean DoWork();
virtual void EndWork();
virtual System::EventArgs^ GetProgressEventArgs();

/**////////////////////////////////////////////////
///Implementation
protected:
int m_nRangeMax,m_nRangeMin;//cached range for faster access
//loop vars
int m_nNumberInQuestion;
CAmicableNumberPair^ m_anp; //a cache of the amicable pair
//the generate amicable pairs
List<CAmicableNumberPair^>^m_rAmicableNumberPairs;
//generate all primes below range.Max+1 for number decomposition
List<int>^ m_rListPrimes;

/**//// <summary>
/// generate the list of primes below a cenain number
/// use the Sieve of Eratosthenes algorithm
/// </summary>
void GeneratePrimes(int nMax) ;

/**//// <summary>
/// Given a positive number n and m returns m to the power n
/// Formulae: exponentiation by squaring
/// pow(m, 0)=1;
/// pow(m, 2*k) = pow(square(m), k)
/// pow(m, 2*k+1)= m * pow(m,2*k)
/// Algorithm
/// when n is >=0
/// Repeatedly reduce the power until it becomes zero
/// keeping track of the effect in p and m.
/// </summary>
static Int64 pow(Int64 m, int n);

/**//// <summary>
/// The sum of the aliquot divisors of nNumber
/// can be calculated by minusing the nNumber form the divisor function
/// </summary>
Int64 RestrictedDivisorFunction(Int64 nNumber);

/**//// <summary>
/// detect If a numbers is a happy number.
/// <remarks>
/// A number is happy when repeating the process of
/// taking the sum of the squares of the digits of a given number
/// eventually yields 1.
/// </remarks>
/// </summary>
static bool IsHappy(int nNumber);

/**//// <summary>
/// detect If a numbers is a Harshad number.
/// <remarks>
/// A Harshad number, or Niven number, is an integer that
// is divisible by the sum of its digits.

/**//// </remarks>
static bool IsHarshad(int nNumber);
};
}
// This is the source file.
#include "stdafx.h"
#include <stack>
using namespace std;
#include "Amicable.h"

namespace Amicable ...{

/**////<summary>The element of the prime factorization </summary>
struct CFactor

...{
int prime;//the prime factor
int exponent;//the exponent of the factor
};

/**////<summary>The Primality of a natural number</summary>
public enum class Primality

...{
Unknown,Prime,Composite
};
Object^ CAmicableNumberPair::Clone()

...{
CAmicableNumberPair^ p=gcnew CAmicableNumberPair();
p->m=m;
p->n=n;
p->isHappy=isHappy;
p->isHarshad=isHarshad;
return p;
}
System::Boolean CAmicableNumberPairGenerator::StartWork()

...{
m_nRangeMax=Range.Max;
m_nNumberInQuestion=m_nRangeMin=Range.Min;
m_rAmicableNumberPairs=gcnew List<CAmicableNumberPair^>();
m_rListPrimes=gcnew List<int>();;
GeneratePrimes(m_nRangeMax+1);
return true;
}
System::Boolean CAmicableNumberPairGenerator::DoWork()

...{
if(m_nNumberInQuestion>m_nRangeMax) return false;//stop wording
int nNumberInQuestion=m_nNumberInQuestion;//cached version of the member var
m_nNumberInQuestion+=1;
//Primality check. Primes are not amicable
for each (int i in m_rListPrimes)

...{
if(i==nNumberInQuestion) return true;//next number
if(i>nNumberInQuestion) break;
}
//amicable pair check. skip if it is in an existing amicable pair
for each (CAmicableNumberPair^ anp in m_rAmicableNumberPairs)

...{
if(anp->n==nNumberInQuestion) return true;//next number
}
Int64 nRestrictedDivisorFunction =RestrictedDivisorFunction(nNumberInQuestion);
// if nNumberInQuestion > RestrictedDivisorFunction (nNumberInQuestion) and nNumberInQuestion is amicable
// then (nRestrictedDivisorFunction,nNumberInQuestion) can not pass the amicable pair check
if(nRestrictedDivisorFunction< nNumberInQuestion) return true;//next number
//the first amicable partner is 284, and perfect numbers are not our target
if( nRestrictedDivisorFunction < 284 || nRestrictedDivisorFunction ==nNumberInQuestion) return true;//next number
//if nNumberInQuestion mod 6==0 and nRestrictedDivisorFunction is even
//then they can not be amicable pairs
//Lee, E. J. "On Divisibility of the Sums of Even Amicable Pairs." Math. Comput. 23, 545-548, 1969.
if(nNumberInQuestion % 6 == 0 && nRestrictedDivisorFunction % 2 == 1) return true;//next number
//amicable
if((Int64)nNumberInQuestion==RestrictedDivisorFunction(nRestrictedDivisorFunction))

...{
int nAmicablePartner=(int)nRestrictedDivisorFunction;
m_anp = gcnew CAmicableNumberPair();
m_anp->m = nNumberInQuestion;
m_anp->n = nAmicablePartner;
m_anp->isHappy = IsHappy(nNumberInQuestion) && IsHappy(nAmicablePartner);
m_anp->isHarshad = IsHarshad(nNumberInQuestion) && IsHarshad(nAmicablePartner);
m_rAmicableNumberPairs->Add(m_anp);
}
return true;//next number
}
void CAmicableNumberPairGenerator::EndWork()

...{
if(m_anp!=nullptr)

...{
delete m_anp;
m_anp=nullptr;
}
if(m_rListPrimes!=nullptr)

...{
delete m_rListPrimes;
m_rListPrimes=nullptr;
}
if(m_rAmicableNumberPairs!=nullptr)

...{
delete m_rAmicableNumberPairs;
m_rAmicableNumberPairs=nullptr;
}
}
System::EventArgs^ CAmicableNumberPairGenerator::GetProgressEventArgs()

...{
CAmicableNumberPairGeneratorProgressEventArgs^ args=gcnew CAmicableNumberPairGeneratorProgressEventArgs();
if(m_anp)

...{
args->Pair=(CAmicableNumberPair^)m_anp->Clone();
m_anp=nullptr;
}
args->nNumberInQuestion=m_nNumberInQuestion;
return args;
}
void CAmicableNumberPairGenerator::GeneratePrimes(int nMax)

...{
//should be able to use vector<int>, but since this is not the bottleneck, use manageed array
array<Primality>^ arPrimality=gcnew array<Primality>(nMax);
for each (Primality p in arPrimality)

...{
p=Primality ::Unknown;
}
//not really, but it helps
arPrimality[0]=Primality::Prime;
arPrimality[1]=Primality::Prime;
int i,j;
//Loop through each unknown value
for (i= 2; i<nMax; i++)

...{
if(arPrimality[i]!=Primality::Unknown) continue;
//The next FirstUnknown value must be prime
arPrimality[i] = Primality::Prime;
m_rListPrimes->Add(i);
//mark out all multiples of this prime as being Composite
for (j = i * 2; j < nMax; j += i)

...{
arPrimality[j] = Primality::Composite;
}
}
}
Int64 CAmicableNumberPairGenerator::pow(Int64 m, int n)

...{
//uses
//Local Data
Int64 p = 1; //working storage for the power.

if( n<0 )...{
p=0;
}
else // n>=0

...{
while(n>0) // here pow(m0,n0) is p * pow(m, n)
// where m0 and n0 were the initial values of m,n.

...{ //n>0
if( n % 2 == 0 ) // so that n is 2*k or equivalently
// k is n/2.

...{
m*= m;
n = n/2;
}
else // n is odd and 2*k+1

...{
p = p * m;
n--;
}
} // n==0
// So pow(m0,n0) is p*pow(m,0) is p.
}
return p;
}
Int64 CAmicableNumberPairGenerator::RestrictedDivisorFunction(Int64 nNumber)

...{
if(nNumber<4) return 1; //4 is the first composite number
//decomposition results
stack<CFactor> stackFactors;
//temp vars
int nPrime;
CFactor f;
Int64 nNumberToFactor=nNumber;
for each (nPrime in m_rListPrimes)

...{
if(nNumberToFactor==1) break;//done
//if nPrime> sqrt(nNumberToFactor), then nNumberToFactor is a prime
if(nPrime > nNumberToFactor/nPrime)

...{
//nNumberToFactor is a prime
f.exponent=1;f.prime=(int)nNumberToFactor;
stackFactors.push(f);
break;//done
}
//composite
while(nNumberToFactor % nPrime==0)

...{
//increase the exponent if it is already a factor
if(stackFactors.size()>0 && nPrime==stackFactors.top().prime)

...{
f = stackFactors.top();
stackFactors.pop();
f.exponent = f.exponent + 1;
stackFactors.push(f);
}
else

...{
//a new factor
f.exponent=1;f.prime=nPrime;
stackFactors.push(f);
}
nNumberToFactor /= nPrime;
}
}
//calculate the sum of the aliquot divisors
Int64 nSumDivisor=1;
Int64 nPrime64;//cache for 64 bit operations
//for each factor f, calculate the sum of the divisors of pow( f.nPrime, f.exponent)
//and multiply with existing result, nSumDivisor
while(stackFactors.size()>0)

...{
f = stackFactors.top();
stackFactors.pop();
nPrime64=f.prime;
//the sum of the divisors of pow( nPrime, exponent)
//is 1 + nPrime + nPrime ^ 2 + nPrime ^ 3 +...+ nPrime ^ exponent
//and can be calculated by the fomula
// ( pow ( nPrime, exponent + 1) - 1 ) / ( nPrime - 1 )
nSumDivisor*= ( pow ( nPrime64, f.exponent + 1) - 1 );
nSumDivisor/=(nPrime64- 1);
}
//calculate the restricted divisor function
return nSumDivisor-nNumber;
}
bool CAmicableNumberPairGenerator::IsHappy(int nNumber)

...{
int sum = 0; //Declaring variable for sum of squre digits
int nDigits = 0;
int nDigit = 0;
do

...{
sum = 0;
nDigits = nNumber;
while (nDigits > 0)

...{
nDigit = nDigits % 10;
sum += nDigit * nDigit;
nDigits /= 10;
}
nNumber = sum;
}
while (nNumber > 9);
return nNumber == 1 || nNumber == 7;
}
bool CAmicableNumberPairGenerator::IsHarshad(int nNumber)

...{
int sum = 0; //Declaring variable for sum of digits
int nDigits = nNumber;
while (nDigits > 0)

...{
sum += nDigits % 10;
nDigits /= 10;
}
if (sum > 0)

...{
return nNumber % sum == 0;
}
return false;
}
}
点此查看本文代码
相亲数(Amicable Pair),又称亲和数、友爱数,指两个正整数中,彼此的全部约数之和(本身除外)与另一方相等。
例如220与284:
- 220的全部约数(除掉本身)相加是:1+2+4+5+10+11+20+22+44+55+110=284
- 284的全部约数(除掉284本身)相加的和是:1+2+4+71+142=220
寻找指定范围内的相亲数的一个方法如下:对于范围内的每个数,用试除法获得约数,之后求和:
int GetAmicableNumber(int nNumber)

...{
int nSumDivisors = SumDivisors(nNumber);
if (nSumDivisors == nNumber) return 0;//perfect number
if (nNumber == SumDivisors(nSumDivisors)) return nSumDivisors;
return 0;
}
int SumDivisors(int n)

...{
int sum = 0; //Declaring variable for sum of divisors
for (int i = 1; i <= n / 2; i++)
if (n % i == 0)
sum += i;
return sum; //Return value of sum of divisors
}

但是这个算法效率很低。一个优化的算法是在计算约数之前进行质因数分解,然后用质因数分解的结果来计算约数之和。
设
这里pi为M的质因子,而ki为质因子的幂,那么M的约数之和可以表示为
| |
|
n |
|
2 |
|
ki |
|
|
|
|
ki |
| N |
= |
∏ |
(1+pi+pi |
|
+……+pi |
) |
|
- |
|
pi |
|
| |
|
i=1 |
|
|
|
|
|
|
|
|
|
而上式中的求和可以用一个简单的除法来替代
| ki |
|
i |
|
|
ki |
+1 |
|
| ∑ |
pi |
|
= |
(pi |
|
|
-1) / (pi-1) |
| i=0 |
|
|
|
|
|
|
|
这样做的好处是
- 减少了运算的次数。
- 质数表可以在分解不同的数时重用。由于质数表只生成一次,这里没做什么优化,就用了最古老的筛法。
System::Boolean CAmicableNumberPairGenerator::StartWork()

...{
m_nRangeMax=Range.Max;
m_nNumberInQuestion=m_nRangeMin=Range.Min;
m_rAmicableNumberPairs=gcnew List<CAmicableNumberPair^>();
m_rListPrimes=gcnew List<int>();;
GeneratePrimes(m_nRangeMax+1);
return true;
}
void CAmicableNumberPairGenerator::GeneratePrimes(int nMax)

...{
//should be able to use vector, but since this is not the bottleneck, use manageed array
array<Primality>^ arPrimality=gcnew array<Primality>(nMax);
for each (Primality p in arPrimality)

...{
p=Primality ::Unknown;
}
//not really, but it helps
arPrimality[0]=Primality::Prime;
arPrimality[1]=Primality::Prime;
int i,j;
//Loop through each unknown value
for (i= 2; i<nMax; i++)

...{
if(arPrimality[i]!=Primality::Unknown) continue;
//The next FirstUnknown value must be prime
arPrimality[i] = Primality::Prime;
m_rListPrimes->Add(i);
//mark out all multiples of this prime as being Composite
for (j = i * 2; j < nMax; j += i)

...{
arPrimality[j] = Primality::Composite;
}
}
}
- 质数表可以用来判断一个数是否是质数。质数不是相亲数。在这里我用的是遍历,但是可以用二叉树来进一步优化。
//Primality check. Primes are not amicable
for each (int i in m_rListPrimes)

...{
if(i==nNumberInQuestion) return true;//next number
if(i>nNumberInQuestion) break;
}

另外一些优化包括
- 仅在计算约数和时使用64位整数
- 跳过已经找出的相亲数对中的数。由于相亲数对并不多,这里不必用二叉树。
//amicable pair check. skip if it is in an existing amicable pair
for each (CAmicableNumberPair^ anp in m_rAmicableNumberPairs)

...{
if(anp->n==nNumberInQuestion) return true;//next number
}
Int64 nRestrictedDivisorFunction =RestrictedDivisorFunction(nNumberInQuestion);
// if nNumberInQuestion > RestrictedDivisorFunction (nNumberInQuestion) and nNumberInQuestion is amicable
// then (nRestrictedDivisorFunction,nNumberInQuestion) can not pass the amicable pair check
if(nRestrictedDivisorFunction< nNumberInQuestion) return true;//next number
跳过约数和小于284的数。284以内只有220一个是相亲数,其约数之和是284。
//the first amicable partner is 284, and perfect numbers are not our target
if( nRestrictedDivisorFunction < 284 || nRestrictedDivisorFunction ==nNumberInQuestion) return true;//next number

如果一个数可以被6整除,且其约数之和是奇数,那么这个数不是相亲数,可以跳过一步求约数和运算
//if nNumberInQuestion mod 6==0 and nRestrictedDivisorFunction is even
//then they can not be amicable pairs
//Lee, E. J. "On Divisibility of the Sums of Even Amicable Pairs." Math. Comput. 23, 545-548, 1969.
if(nNumberInQuestion % 6 == 0 && nRestrictedDivisorFunction % 2 == 1) return true;//next number

质因数分解中,不用遍历整个质数表,在商小于当前质数的平方时就可以终止分解
Int64 CAmicableNumberPairGenerator::RestrictedDivisorFunction(Int64 nNumber)

...{
if(nNumber<4) return 1; //4 is the first composite number
//decomposition results
stack<CFactor> stackFactors;
//temp vars
int nPrime;
CFactor f;
Int64 nNumberToFactor=nNumber;
for each (nPrime in m_rListPrimes)

...{
if(nNumberToFactor==1) break;//done
//if nPrime> sqrt(nNumberToFactor), then nNumberToFactor is a prime
if(nPrime > nNumberToFactor/nPrime)

...{
//nNumberToFactor is a prime
f.exponent=1;f.prime=(int)nNumberToFactor;
stackFactors.push(f);
break;//done
}
//composite
while(nNumberToFactor % nPrime==0)

...{
//increase the exponent if it is already a factor
if(stackFactors.size()>0 && nPrime==stackFactors.top().prime)

...{
f = stackFactors.top();
stackFactors.pop();
f.exponent = f.exponent + 1;
stackFactors.push(f);
}
else

...{
//a new factor
f.exponent=1;f.prime=nPrime;
stackFactors.push(f);
}
nNumberToFactor /= nPrime;
}
}
//calculate the sum of the aliquot divisors
Int64 nSumDivisor=1;
Int64 nPrime64;//cache for 64 bit operations
//for each factor f, calculate the sum of the divisors of pow( f.nPrime, f.exponent)
//and multiply with existing result, nSumDivisor
while(stackFactors.size()>0)

...{
f = stackFactors.top();
stackFactors.pop();
nPrime64=f.prime;
//the sum of the divisors of pow( nPrime, exponent)
//is 1 + nPrime + nPrime ^ 2 + nPrime ^ 3 +...+ nPrime ^ exponent
//and can be calculated by the fomula
// ( pow ( nPrime, exponent + 1) - 1 ) / ( nPrime - 1 )
nSumDivisor*= ( pow ( nPrime64, f.exponent + 1) - 1 );
nSumDivisor/=(nPrime64- 1);
}
//calculate the restricted divisor function
return nSumDivisor-nNumber;
}

计算乘幂时,使用平方乘幂法
Int64 CAmicableNumberPairGenerator::pow(Int64 m, int n)

...{
//uses
//Local Data
Int64 p = 1; //working storage for the power.

if( n<0 )...{
p=0;
}
else // n>=0

...{
while(n>0) // here pow(m0,n0) is p * pow(m, n)
// where m0 and n0 were the initial values of m,n.

...{ //n>0
if( n % 2 == 0 ) // so that n is 2*k or equivalently
// k is n/2.

...{
m*= m;
n = n/2;
}
else // n is odd and 2*k+1

...{
p = p * m;
n--;
}
} // n==0
// So pow(m0,n0) is p*pow(m,0) is p.
}
return p;
}

尽可能使用STL和基本数据类型来替换范型和托管数据类型。
尽管编译器可以对代码进行优化,但是真正高效的算法还是要手工编写的。
C语言中并未规定
char、
int和
long这样的基本类型的长度和符号。这给与了编译器很大的灵活性,但是也产生了很多问题。
int是程序中最常用的类型。理论上来说,int应该被定义为CPU中运算最快的类型,比如80386系列中的32位整数。在升级程序到64位CPU环境的时候,应该可以重新编译程序来把32位运算升级到64位运算以增加性能。但是太多代码不正确地使用int和long来做指针运算,以至于会在升级时崩溃。目前很多64位编译器中的int仍然是32位,在有的编译器中甚至连long也仍旧是32位,而引入新的数据类型来代表64位变量。
另外一个问题是基本类型的符号。在Visual C++中,基本类型是有符号的,如果在开发时使用无符号修饰符的基本类型的话,这会在移植程序到使用无符号基本类型的编译器时出现问题。举例来说,在基本类型的符号不同的情况下,下面两行代码
int b=-1;unsigned int a=b;
int b=-1;b>>5;

的行为会有所不同。Visual C++ 2005编译器开发人员试图关闭/J开关(这个开关决定char是unsigned还是signed),并且用改进的警告功能来在编译未指定符号的基本类型时给出警告信息。但是这会使得绝大多数代码工作不正常,用户的负面反馈淹没了这个动议。
为了代码的可移植性起见,建议在使用基本类型的时候加上符号修饰符。
好长时间没更新BLOG了,向大家拜个晚年先。最近没怎么写代码,转几篇在网易虚拟社区发的文章过来充数。
对于BUG的自信
Donald E. Knuth(高德纳)在TeX: The Program的前言中说:
"我相信,在1985年11月27日,TeX代码里面的最后一个BUG已经被发现和解决了。但是,如果代码中仍旧有BUG,我很高兴付给任何第一个发现BUG的人20.48美元(这是前一个金额的两倍,而且我计划在一年内把它翻倍。你看,我很自信!)"
想知道后来发生了什么吗?
在http://truetex.com/knuthchk.htm可以看到他写出去的支票的金额是从2.56美元开始翻倍的。微基百科中关于这种支票的文章(http://en.wikipedia.org/wiki/Knuth_reward_check)说,截至2001年10月为止,他写出去了超过两千张这样的支票,但是他的BUG支票是如此有名,以至于很多人把他的支票收藏起来而不是拿出去兑现(http://www.tug.org/whatis.html)。
有多少程序员在发布产品的时候可以这样自信地声明产品没有问题?
遗憾的是,现在的程序员经常把发现BUG的责任推给测试人员——“不用担心,测试人员会发现所有BUG的,这是他们的工作”。实际上,测试人员并没有开发人员的条件,他们不可能进行源代码级别的调试,很大程度上只能靠运气——没错,是靠运气,如果一个BUG很容易被发现,程序员不太可能自己没有发现它——来发现BUG。
还有一些人干脆就认为BUG是不可避免的,或者认为不值得这么精益求精(参见网易虚拟社区http://p5.club.163.com/viewArticleByWWW.m?boardId=clanguage&articleId=clanguage_108eacc622169e7&boardOffset=0的讨论),但是实际上防止BUG出现的最好的时机,就是在编写代码的时候。在编写代码一段时间之后,即使是编写者本人也可能需要一段时间来理解代码(如果不习惯写注释的话,这段时间会更长),更别说定位问题所在了。在编写代码时,如果具有良好的习惯,可以免去很多在之后消灭BUG的困难。
规范不是语法
太多人把不要使用goto奉为圣旨,从来不想去打破。他们会争论,goto会造成难以维护的难读的代码,以及使编译器无法进行优化。这两点在很大程度上是真的,但是也有使用goto可以增加程序可读性和效率时候。在这种情况下,遵循“不使用goto语句”规范会产生更糟糕的代码。
一些人喜欢在成员函数后面加const,但是另外一些人没有养成这个习惯。一个直接的结果就是,一些看起来对对象完全没有影响的函数不能在const函数里面使用。这时候应该怎么办?看看Paul DiLascia建议的,把this指针强行转化为一个非const指针(http://www.microsoft.com/msj/archive/S126E.aspx)。如果函数实际上会对对象成员造成影响(例如CToolBar::GetItemRect),这也会带来潜在危险。
为了和ANSI标准之前编写的代码兼容,ANSI C中的memchr函数的声明为
void *memchr(
const void *buf,
int c,
size_t count
);

这里c是一个字符。很明显,标准为了兼容性放弃了明确性和更强的类型检查。如果放弃兼容性,这个函数应该声明为如下形式
void *memchr(
const void *buf,
unsigned char c,
size_t count
);
微软的很多代码使用一种叫做匈牙利表示法的命名规范。这使得标识符的含义和类型更加明确——但是这是从广义的角度来说的。考虑如下函数声明
char *strcpy(
char *strDestination,
const char *strSource
);
如果严格遵循原始的匈牙利表示法,那么两个参数的声明应该是pch开头。但是以str开头给这两个参数更多含义:它们指向以\0为结束符的字符串。
规范是用来在大部分时间里遵循,以及在可以得到更好的结果时打破的。
编译警告的意义
智能化的编译器开始将语法正确的语句列为警告:
while(size-->0);//注意这里有个分号
*pTo++=*pFrom++;
编译器会报告空循环问题。
但是对于以0结尾的字符串复制
while(*pTo++=*pFrom++);
,这样的警告是多余的。
更加常见的警告是在条件判断语句中
if(ch='\0')
EndOfString();
为了绕过这个警告,需要添加额外的运算或者语句,或者更正错误的赋值。

while((*pTo++=*pFrom++)!='\0')...{}
if(ch=='\0')
一些程序员甚至将比较语句修改成
if('\0'==ch)
这样作的原因显而易见:为了减少潜在的BUG。如果你的编译器没有这样的警告,那么你可以使用一些工具来检查那些语法正确但是有潜在BUG的代码。LintProject (http://www.codeproject.com/tools/lintproject.asp)就是其中一个。但是,良好的编程习惯还是减少BUG出现的最好的方法。
在觉得警告消息太烦人的时候,不妨想想编译器的开发人员为什么要编写这么多警告消息,而不是仅仅寻求关闭警告的方法。
P.S. Visual C++的默认警告等级是3级。发布软件之前应该改成4级,之后检查所有的编译警告。
无处不在的断言
使用编译器来捕获BUG的主意很好,Visual Studio 2005甚至会报告定义的变量不符合命名规范(Warning 1 CA1709 : Microsoft.Naming : Correct the casing of type name 'welcome'.);但是我敢打赌你检查BUG列表的时候,你会发现只有一小部分BUG会被编译器抓到。很多BUG在程序运行过程中很少会出现,例如内存分配失败的问题
char* strBuffer=new char [length];
MyZeroMemory(strBuffer,length);
这段代码在绝大多数情况下会成功,但是在虚拟内存不足的时候,Windows会报告“您的系统虚拟内存太低,WINDOWS会增加虚拟内存页面文件的大小。在这个过程中,一些应用程序的内存请求会被拒绝”然后开始增加虚拟内存,在这个过程中,new这样的内存分配可能会因为内存不足而失败,而MyZeroMemory则可能会造成访问越界。如果你足够幸运,你会在产品发布之前发现这个BUG,否则,你的用户会代替你发现这个BUG。要是用户刚好没有备份的习惯,丢失了几十分钟甚至是几小时的工作进度,用户会很生气,后果很严重。
编译器不能捕获这种运行时才会出现的错误(顺便说一下,我在CSDN上居然还看到有人抱怨编译器不会报告除0错误);也不能捕获算法中的BUG和检验参数中的数据。但要是你知道怎么做的话,这类问题很容易被发现。你可以用SetProcessWorkingSetSize函数或者msconfig工具减少虚拟内存大小,或者使用Virtual PC之类的虚拟机或者磁盘配额策略来模拟内存和磁盘空间不足的情况。
你有可能想在这种极限情况下调试你的代码,但是大多数时间内,内存分配不会失败,而设置条件断点又太麻烦了。这时候可以在代码里面加上一段用来在内存分配失败时触发调试器的断言代码
void MyZeroMemory(char* strBuffer, int length)

...{
assert(strBuffer!=NULL);
}
如果使用的是MFC或者ATL,建议使用对应的宏ASSERT和ATLASSERT。现在你可以编写健壮的代码使得程序在strBuffer这块内存分配失败时也能够正常运行。
现在的问题是,加入的这些代码增加了应用程序的大小,减慢了运行速度。在解决了内存分配失败造成的程序崩溃的问题之后,有必要在发布的版本中去掉这些断言代码。一个简单的办法是使用预处理标识符:
void MyZeroMemory(char* strBuffer, int length)

...{
#ifdef DEBUG
assert(strBuffer!=NULL);
#endif
}
这样你可以只维护同一份代码。当然,这也意味着调试的代码在发行版中会被去除,所以为了避免不可预料的行为,为了调试而加入的代码应该尽可能少地影响应用程序的行为。
你有可能需要重新定义assert来实现扩展的行为——例如在assert断言失败中断程序时打开源文件并且跳到assert那一行——这时候你可以编写自己的断言函数,然后重定义assert为这个断言函数。
#ifdef DEBUG

/**//*display a dialog and if the user selected break
, jump to the assert line*/
void _assert(char*,unsigned int);
#define assert(f)\
if(f)\

...{}\
else
_assert(__FILE__,__LINE__)
#else
#define assert(f)
#endif

空的if语句块可能看起来有点奇怪,但是这可以避免和宏外的if-else产生冲突。同样,最后一行语句没有结束的分号,因为在使用的时候再加上会更加自然。
assert最有用的地方就是用来检验函数的参数——但是也可以在其他地方起作用。在程序中的断言语句越多,异常的情况就越容易被侦测到。
既然assert是代码,它不可避免的需要注释。即使是自己写的代码,过了六个月之后再来审视也可能需要一点时间来重新理解这部分代码。一个简单的注释可以把这部分时间减少:
void MyZeroMemory(char* strBuffer, int length)

...{

/**//*should not be called when buffer allocation failed*/
#ifdef DEBUG
assert(strBuffer!=NULL);
#endif
}

在编写完函数之后,应该审视函数中的代码,之后在函数的开头验证函数正常运行所需的条件。如果你在写一个库函数,那么应该在函数的文档中加入函数正常运行所需的条件——否则就会增加使用者发现BUG的难度。举例来说,Windows API的文档不可谓不详尽,但是我在用汇编调用Windows API的时候,也花了很长时间才发现调用Windows API之前栈顶要设置成4的倍数。
注意不要把一些条件当成默认成立的了。assert(sizeof(int)==4);这样的语句在一些人看来很荒谬,但是在Windows开发中通常是32位的long在一些64位平台上已经是64位的了,而在目前还不知道sizeof(int)在什么时候会升位。如果你的代码依赖于int的大小,那么写上这行可以在未来升位之后更快发现问题。
一些保守的程序员在参数错误时会让函数继续运行——返回一个错误码——但是不报告错误。在编写核心模块时这可能很有必要,但是这也经常会把BUG藏起来——在多层函数返回之后时候,错误码经常会丢失或者被替代。尽量不要使用保守编程来替代断言,如果你认为保守编程会造成定位问题的困难,那么就加上断言代码。
在一些时候,校验参数数据似乎是不可能的事情——想象一下那些被设计来搞糊涂解密者的加密算法的中间数据——但是校验这种复杂算法的方法也不是没有。为了确认手算和心算的正确性,我们会使用电子计算器的结果来进行比较,反过来,我们也可以编写另外一个的算法来断言计算结果的正确性。这种方法也可以被用来断言一个函数的汇编版本和C版本的一致性——为了获取最大性能,函数的汇编版本的算法可能和C版本的有很大差异。当然,不是每个函数都有必要用这种方式来验证,实际上,只有极其重要的算法和对性能极其敏感的代码才会需要这种双保险来验证。同样,为了调试而加入的算法也应该尽可能少地影响应用程序的行为。
最后,你不应该在发布程序时从代码中去掉断言语句,而是把它们留在那里以供你升级或者查找BUG时使用。帖来自于网易社区:http://p5.club.163.com/viewArticleByWWW.m?boardId=clanguage&articleId=clanguage_10938d0575c4afe
消灭不可预料的行为
断言并不能抓住所有BUG——它们都是人写出来的,而是人就会犯错误。一些常见的错误包括:
- 使用状态不确定的资源
- 在释放资源之后继续访问资源
- 在资源重新定位之后继续引用旧的资源
- 申请资源之后丢失对资源的引用
- 访问时未注意是否越界
- 忽略错误信息
这些并不是杞人忧天的问题——实际上,这些问题属于日常开发中最常见的问题。这些问题的特点是,它们并不是时常造成程序行为的异常,并且症状不可重复。以内存为例,释放内存之后编译器和操作系统通常不会自动去擦除内存中的内容,所以继续访问内存不太可能造成程序行为的异常——直到内存被重新分配出去,而另一块代码开始重写这块数据。申请资源之后丢失对资源的引用可能只是造成长时间运行之后系统资源不足而已。另外,这些问题都是算法的问题,而编译器并不会替你校验你的算法,你自己也不太可能会怀疑你自己的算法。
不要认为职业的程序员就不会犯这类错误。举几个例子来说明这些问题。
- 在IE4.0中,MSHTML的HTMLDocument对象的IPersistStreamInit::Load假定传入的IStream流的访问指针已经定位到开头——而在IE5.0中,IPersistStreamInit::Load会自行调用IStream::Seek
- 在Visual C++ 6.0中,CHTMLView类有字符串资源未释放问题(http://support.microsoft.com/kb/241750)
- 在Visual C++.Net中,CHTMLView类有两个BUG:
- 参数传递错误:
HRESULT CHtmlView::ExecFormsCommand(DWORD dwCommandID, VARIANT* pVarIn,
VARIANT* pVarOut)

...{
HRESULT hr = E_FAIL;
CComPtr spDoc = (IHTMLDocument2*) GetHtmlDocument();
if (spDoc != NULL)

...{
CComQIPtr spCmdTarget = spDoc;
if (spCmdTarget != NULL)
hr = spCmdTarget->Exec(&CMDSETID_Forms3, dwCommandID,
OLECMDEXECOPT_DONTPROMPTUSER, pVarOut, pVarIn);
}
return hr;
}

COM指针未释放错误:
void CHtmlView::OnFilePrint()

...{
// get the HTMLDocument
if (m_pBrowserApp != NULL)

...{
CComPtr spDisp = GetHtmlDocument();
if (spDisp != NULL)

...{
// the control will handle all printing UI
CComQIPtr spTarget = spDisp;
if (spTarget != NULL)
spTarget->Exec(NULL, OLECMDID_PRINT, 0, NULL, NULL);
}
}
}
这些不确定的行为是很大一部分不可重复(因此也很难跟踪)的BUG的根源。举例来说,释放一块内存之后,在操作系统切换到另外一个线程之前,重新分配同一块内存,并且写入数据这个事件发生的可能性十分之小。为了解决这些问题,Visual C++编译器采取了一种保护性的措施,在调试模式下再分配和释放内存时将内存用一般不会用到的值填充,例如0xccccccc,0xdddddddd和0xfefefefe(参见编译器文档中的/RTC参数的说明)。这样你可以减少不可预料的程序行为,强迫BUG重现。如果你的编译器没有这么做,你可以自己编写一个调试模式下专用的内存管理程序进行这样的工作。为什么选择这样的值?在Intel系统中,0xcc的含义是int 3中断(参见http://blogs.msdn.com/oldnewthing/archive/2004/11/11/255800.aspx)——如果不小心执行了这块数据,那么程序会马上中断并且提示用户,其他的值则是典型的非法数据。如果你在为其他环境编写程序。你可能需要查阅一些资料来决定使用什么值来在调试模式下填充内存。
MFC的另一个保护措施是内存泄漏监测器——这也是在每个文件开头要加上#define new DEBUG_NEW的原因——但是这也变更了应用程序的行为。举例来说,为了检查内存泄漏,MFC总是分配比所需要多的内存,然后加入调试信息。如果你的程序有访问越界的代码,那么有可能擦除一部分额外分配的内存,可能的结果就是在调试模式下运行正常,而在发布模式下程序崩溃。当然,这是必须的。如果你的发布版本的程序依赖于这些额外字节,那么你就有麻烦了。
在发布模式下程序的崩溃有助于你发现问题,但是也造成定位问题的困难。你可以在发布模式下加入调试信息(没错,在工程的C++和连接选项中选中Program Database和Generate Debug Info)来生成一个中间版本;MSDN文章Generating and Deploying Debug Symbols with Microsoft Visual C++ 6.0(http://msdn.microsoft.com/library/en-us/dnvc60/html/gendepdebug.asp)甚至教你怎么怎么发布这样的版本,但是也要注意这样的版本和最终发布版本还是可能有区别的——特别是在程序中有BUG的情况下。另一个办法是在调试时将EIP寄存器修改成崩溃信息中的值,这样可以很容易在源代码中定位造成崩溃的代码的位置(参见http://www.codeproject.com/debug/XCrashReportPt1.asp)。
MFC开发中另一个比较有用的定位内存访问越界方法是,将数据封装成对象成员变量,尽量可能让所有类都从CObject派生,并且在代码中大量加上ASSERT_VALID。如果成员变量被越界的访问重写了,那么CObject指向AssertValid的虚函数表会被改写,而ASSERT_VALID会报告这个错误。
不要发布调试版本,这对用户来说并无意义。虽然这么说可能是多此一举,但是我在玩游戏的时候还真看见过断言对话框。调试信息是被设计用来发现问题的,不是用来隐藏问题的。如果你确实需要这么做(微软就定期发布核心模块的调试信息以供软件开发人员定位问题),那么你需要让用户认识到调试版本和最终发布版本的性能差异,例如在程序开始时显示一个消息。
广告时间:
最新Windows SDK不支持Visual C++ 6.0
可能大部分人已经知道了,但是CSDN论坛上仍旧不断出现关于这个兼容性的提问。最新的支持Visual C++ 6.0的版本是2003年2月版,下载地址是http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdk-full.htm。
取消对Visual C++ 6.0的支持的原因是为了支持新的/GS参数。XP SP2和Windows Server 2003 SP 1都增加了很多安全特性,以致于新的Windows SDK中包含的编译器和库文件不再和Visual C++ 6.0兼容。
参见
个人觉得这次MVP峰会最大的进步就是技术相关的Session数量大大增加,按照MVP专长来分类;而不像上次那样按主题分类。我只需要在VC产品组的日程里面选择就可以了,而不是像上回那样不得不去听移动开发。当然这回也有MVP不去参加VC的Session,跑去听IE和移动开发。内容方面也比上次活泼很多,Don Box还是那么幽默,比尔·盖茨也有搞笑的演出,不过他看起来比去年七月份在北京的时候老多了。
一些可能有人会感兴趣的技术信息
一些建议
- 停止开发新的面向Win9x的程序和静态链接MFC的程序。使用新的MFC版本编译旧的程序来增加应用程序的安全性。
- 在新的程序中使用Unicode编码,同时尽可能将现有程序移植到Unicode。
- 移植到Visual C++ 2005来使用强大的编译器和调试器。
尽管限于保密协议我不能说得更多,但是微软在11月7号就会正式发布Visual Studio 2005、SQL Server 2005和BizTalk Server 2006了。新的Visual Studio版本(代号Orca和Hawaii)也正在规划中。
Hook DHTML Commands
浏览器在执行很多命令之前都会允许容器来替换默认的处理。在执行一些默认的命令之前,系统会查询用户对IDocHostUIHandler的实现对象的IOleCommandTarget接口,调用默认(NULL)或者CGID_DocHostCommandHandler命令组的命令。如果容器的对应命令处理返回S_OK。那么默认的处理就不会被调用。
下面列出一些可以在容器中自定义的操作:(这些常量的定义位于docobj.h中)
- OLECMDID_PRINT,默认命令组
- OLECMDID_SHOWSCRIPTERROR, CGID_DocHostCommandHandler命令组
- OLECMDID_FOCUSVIEWCONTROLSQUERY, CGID_DocHostCommandHandler命令组
- OLECMDID_SHOWPAGEACTIONMENU, CGID_DocHostCommandHandler命令组
MFC提供了一些比较容易扩展的类和宏,这样可以很容易地在扩展容器来实现新的接口。这里使用CCmdTarget类提供的GetInterfaceHook虚函数来进行扩展。
浏览器控件访问容器的方式是查询容器的IDocHostUIHandler接口,而对于浏览器辅助对象(Browser Helper Object/ BHO),不能修改容器来增加一个新的接口。这时候可以通过HTML文档的ICustomDoc接口来设置自己的IDocHostUIHandler接口。
在捕获容器的命令的过程中发现一些其他命令也被发送到容器(按时间顺序):
| 行为 |
命令组 |
命令 |
| 加载 |
NULL |
OLECMDID_SETDOWNLOADSTATE |
| 000214D0-0000-0000-C000-000000000046 |
OLECMDID_PASTESPECIAL/ OLECMDID_HIDETOOLBARS / OLECMDID_PREREFRESH/ OLECMDID_ONUNLOAD |
| NULL |
OLECMDID_SETPROGRESSMAX/ OLECMDID_SETPROGRESSPOS/ OLECMDID_SETDOWNLOADSTATE |
| 打印 |
CGID_DocHostCommandHandler |
OLECMDID_PRINT |
| 刷新 |
CGID_DocHostCommandHandler |
0x1799 |
| NULL |
OLECMDID_PREREFRESH/ OLECMDID_SETPROGRESSMAX/ OLECMDID_SETPROGRESSPOS/ OLECMDID_SETDOWNLOADSTATE |
| 000214D0-0000-0000-C000-000000000046
|
OLECMDID_ONUNLOAD/ OLECMDID_PREREFRESH |
不是所有常用操作都会转发到容器。OLECMDID_PRINTPREVIEW、OLECMDID_SAVEAS、OLECMDID_REFRESH看起来就没有被转发。
参考文档:
Visual Studio 2005 June CTP的资源编辑器似乎有点问题,改过资源就保存不到原文件了,只能保存到另一个文件,关闭Visual Stuio之后替换(关闭solution都不行)。
使用WinDbg调试VC程序
虽然在VC6.0中可以通过安装Visual C++ Toolkit(网站:http://msdn.microsoft.com/visualc/vctoolkit2003/)来编写基于最新版本的平台SDK、DirectX SDK的程序以及托管代码,但是VC6附带的调试器并不支持新版本的调试信息,所以实际上是不能用VC6来调试新版本编译器生成的程序的。一个替代的解决方案是使用新版本的Windows调试工具Windbg(网站:http://www.microsoft.com/whdc/DevTools/Debugging/default.mspx)。Windbg的调试功能基本和Visual C++中的相同,但是需要手动设定源文件和调试符号文件的搜索路径(可以参考VC6.0中的对应设置)。一些代码,例如MFC的代码比较难于定位,这时可以双击调用堆栈中的函数名称来打开文件并定位到函数所在位置。Windbg可以进行有限的托管代码调试,但是调试过程比较麻烦。在没有安装Visual Studio的计算机上调试,例如进行远程调试的时候可能还需要部署调试符号(Generating and Deploying Debug Symbols With Visual C++ 6.0)。
调试Visual Studio .Net 的程序需要用户是管理员或者"Debug User"用户组成员。如果登录用户不是该组成员,那么也可以用WinDbg来调试程序。