Macros 让Visual Studio在多行宏语句中的正确位置显示错误

Macros 让Visual Studio在多行宏语句中的正确位置显示错误,macros,visual-studio-2013,c++-cli,Macros,Visual Studio 2013,C++ Cli,我主要在C#中工作,但目前正在为COM对象编写一个C++/CLI包装器,当直接从.Net进行对话时,该包装器无法正常工作。我们的一个.Net对象允许进行日志记录,并且有一种简便的方法,可以通过(mis)使用IDisposable记录代码块中的进入/退出。在C#中,这非常方便,因为您可以简单地执行以下操作: using (_log.Enter("some block")) { //code } 在C++/CLI中,没有使用这样的东西。因此,我的初始选择是将所有块包装为: IMethodBlo

我主要在C#中工作,但目前正在为COM对象编写一个C++/CLI包装器,当直接从.Net进行对话时,该包装器无法正常工作。我们的一个.Net对象允许进行日志记录,并且有一种简便的方法,可以通过(mis)使用
IDisposable
记录代码块中的进入/退出。在C#中,这非常方便,因为您可以简单地执行以下操作:

using (_log.Enter("some block")) {
  //code
}
在C++/CLI中,没有使用这样的东西。因此,我的初始选择是将所有块包装为:

IMethodBlock^ mb = _log->Enter("some block");
try {
    //code
}
finally {
    delete mb;
}
这没关系,但会让人厌烦。因此,我定义了一些宏:

#define MACRO_CONCAT( x, y ) x##y
#define LogBlockImpl(var, y, z) IMethodBlock^ var = y; try {z} finally {delete var; }
#define LogBlock(y, z) LogBlockImpl(MACRO_CONCAT(MethodBlock_mb, __COUNTER__), y, z)
这意味着我现在可以写下:

LogBlock(_log->Enter("some block"), {
  //code
})
这很好,我可以接受,但是如果块中的某个地方有错误,Visual Studio会将错误报告为调用宏的位置-即在块的开头,这使得很难准确地确定问题所在


是否有一种方法可以完成我在这里所做的工作,但允许Visual Studio正确解释我正在尝试的内容,并向我展示语法错误等在其设计方式中的位置?

这里的解决方案是远离多行宏,并以其他方式执行此操作

幸运的是,由于您使用的是
IDisposable
,因此有一个非常恰当的修复方法:在不使用句柄的情况下声明变量(不使用
^
),并让RAII为您处理事情

下面是与您的C#代码等效的C++/CLI RAII:

但是,这确实对代码提出了一些附加要求:

  • 您必须将变量声明为实际的类名,而不是父类,并且不是
    IDisposable
    。(接口需要
    ^
    ;不能直接使用。)
  • 该类需要实现一个复制构造函数,这不是在C#中实现的标准内容
  • 调用复制构造函数时,需要将“copy from”对象标记为未使用,以便在销毁时不会影响任何内容
这些约束并不理想,因此我将为您实现一个类来封装
IDisposable

public ref class IDisposableHolder
{
private:
    initonly IDisposable^ held;

public:
    IDisposableHolder(IDisposable^ held) { this->held = held; }
    ~IDisposableHolder() { delete held; }
};

{
    IDisposableHolder holder(_log->Enter("some block"));

    // your code here

    // At the end of the block, IDisposableHolder is automatically disposed, 
    // and it disposes the IDisposable that it's holding.
}
这将为您带来以下好处:

  • 实现
    IDisposable
    的类不需要是公共的
  • 没有仅为在副本构造函数中使用而创建的临时对象
  • 这意味着日志代码现在是标准的C#,而不是针对C++/CLI定制的
这是我的测试代码和程序输出

public ref class LogEntryObject
{
private:
    static int nextID = 0;
    int id;
public:
    LogEntryObject()
    { 
        this->id = ++nextID; 
        Debug::WriteLine("LogEntryObject.ctor({0})", id);
    }

    ~LogEntryObject() { Debug::WriteLine("LogEntryObject.Dispose({0})", id); }
    !LogEntryObject() { Debug::WriteLine("LogEntryObject.Finalize({0})", id); }

    LogEntryObject(LogEntryObject^ copyFrom)
    { 
        this->id = ++nextID; 
        Debug::WriteLine(
            "LogEntryObject.copy ctor({0} -> {1})", 
            copyFrom->id, this->id);
    }
};

public ref class Logger
{
public:
    LogEntryObject^ Enter_ConcreteType(String^ name)
    {
        Debug::WriteLine("Logger.Enter_ConcreteType(" + name + ")");
        return gcnew LogEntryObject();
    }

    IDisposable^ Enter_IDisposable(String^ name)
    {
        Debug::WriteLine("Logger.Enter(" + name + ")");
        return gcnew LogEntryObject();
    }
};

public ref class IDisposableHolder
{
private:
    initonly IDisposable^ held;

public:
    IDisposableHolder(IDisposable^ held)
    {
        this->held = held;
        Debug::WriteLine("IDisposableHolder.ctor()");
    }

    ~IDisposableHolder()
    {
        delete held;
        Debug::WriteLine("IDisposableHolder.Dispose()");
    }
};

int main(array<System::String ^> ^args)
{
    Logger^ _log = gcnew Logger();

    {
        // error C3149: 'System::IDisposable' : cannot use this type here
        // without a top-level '^'
        // IDisposable foo = _log->Enter_IDisposable("some block");

        LogEntryObject foo = _log->Enter_ConcreteType("some block");

        Debug::WriteLine("Code inside block");
    }

    Debug::WriteLine("GC::Collect()");
    GC::Collect();
    GC::WaitForPendingFinalizers();
    GC::Collect();

    Debug::WriteLine("----------------------");

    {
        IDisposableHolder holder(_log->Enter_IDisposable("some block"));

        Debug::WriteLine("Code inside block");
    }

    Debug::WriteLine("GC::Collect()");
    GC::Collect();
    GC::WaitForPendingFinalizers();
    GC::Collect();

    return 0;
}
public ref类LogEntryObject
{
私人:
静态int nextID=0;
int-id;
公众:
LogEntryObject()
{ 
此->id=++nextID;
Debug::WriteLine(“LogEntryObject.ctor({0})”,id);
}
~LogEntryObject(){Debug::WriteLine(“LogEntryObject.Dispose({0})”,id);}
!LogEntryObject(){Debug::WriteLine(“LogEntryObject.Finalize({0})”,id);}
LogEntryObject(LogEntryObject^copyFrom)
{ 
此->id=++nextID;
调试::WriteLine(
“LogEntryObject.copy ctor({0}->{1})”,
copyFrom->id,此->id);
}
};
公共参考类记录器
{
公众:
LogEntryObject^输入具体类型(字符串^name)
{
调试::WriteLine(“Logger.Enter_ConcreteType(“+name+”)”);
返回gcnew LogEntryObject();
}
IDisposable^输入_IDisposable(字符串^name)
{
调试::WriteLine(“Logger.Enter(“+name+”));
返回gcnew LogEntryObject();
}
};
公共参考类IDisposableHolder
{
私人:
仅持有初始IDisposable^;
公众:
IDisposableHolder(IDisposable^hold)
{
此->保持=保持;
调试::WriteLine(“IDisposableHolder.ctor()”;
}
~IDisposableHolder()
{
删除保留;
Debug::WriteLine(“IDisposableHolder.Dispose()”;
}
};
int main(数组^args)
{
记录器^u log=gcnew Logger();
{
//错误C3149:“System::IDisposable”:无法在此处使用此类型
//没有顶级“^”
//IDisposable foo=_log->输入_IDisposable(“某些块”);
LogEntryObject foo=\u log->输入\u ConcreteType(“某些块”);
调试::WriteLine(“块内代码”);
}
Debug::WriteLine(“GC::Collect()”);
GC::Collect();
GC::WaitForPendingFinalizers();
GC::Collect();
调试::WriteLine(“-------------------------”);
{
IDisposableHolder holder(_log->输入_IDisposable(“某些块”));
调试::WriteLine(“块内代码”);
}
Debug::WriteLine(“GC::Collect()”);
GC::Collect();
GC::WaitForPendingFinalizers();
GC::Collect();
返回0;
}
记录器。输入类型(某些块) LogEntryObject.ctor(1) LogEntryObject.copy ctor(1->2) 块内代码 LogEntryObject.Dispose(2) GC::Collect() ---------------------- Logger.Enter(某些块) LogEntryObject.ctor(3) IDisposableHolder.ctor() 块内代码 LogEntryObject.Dispose(3) IDisposableHolder.Dispose() GC::Collect() LogEntryObject.Finalize(1)
这里的解决方案是远离多行宏,并以其他方式执行此操作

幸运的是,由于您使用的是
IDisposable
,因此有一个非常恰当的修复方法:在不使用句柄的情况下声明变量(不使用
^
),并让RAII为您处理事情

下面是与您的C#代码等效的C++/CLI RAII:

但是,这确实对代码提出了一些附加要求:

  • 您必须将变量声明为实际的类名,而不是父类,并且不是
    IDisposable
    。(接口需要
    ^
    ;不能直接使用。)
  • 该类需要实现一个复制构造函数,这不是在C#中实现的标准内容
  • 调用复制构造函数时,需要将“copy from”对象标记为未使用,以便在销毁时不会影响任何内容
这些约束并不理想,因此我将为您实现一个类来封装
IDisposable

public ref class IDisposableHolder
{
private:
    initonly IDisposable^ held;

public:
    IDisposableHolder(IDisposable^ held) { this->held = held; }
    ~IDisposableHolder() { delete held; }
};

{
    IDisposableHolder holder(_log->Enter("some block"));

    // your code here

    // At the end of the block, IDisposableHolder is automatically disposed, 
    // and it disposes the IDisposable that it's holding.
}
这会给你f Logger.Enter_ConcreteType(some block) LogEntryObject.ctor(1) LogEntryObject.copy ctor(1 -> 2) Code inside block LogEntryObject.Dispose(2) GC::Collect() ---------------------- Logger.Enter(some block) LogEntryObject.ctor(3) IDisposableHolder.ctor() Code inside block LogEntryObject.Dispose(3) IDisposableHolder.Dispose() GC::Collect() LogEntryObject.Finalize(1)