Share via


CA1838: P/Invokes에 StringBuilder 매개 변수를 사용하지 마세요

속성
규칙 ID CA1838
타이틀 P/Invokes에 대한 매개 변수 방지 StringBuilder
범주 성능
수정 사항이 주요 변경인지 여부 주요 변경 아님
.NET 8에서 기본적으로 사용 아니요

원인

P/InvokeStringBuilder 매개 변수가 있습니다.

규칙 설명

마샬링은 StringBuilder 항상 네이티브 버퍼 복사본을 만들어 하나의 P/Invoke 호출에 대해 여러 할당을 생성합니다. StringBuilder를 P/Invoke 매개 변수로 마샬링하기 위해 런타임은 다음을 수행합니다.

  • 네이티브 버퍼를 할당합니다.
  • In 매개 변수인 경우 StringBuilder의 내용을 네이티브 버퍼에 복사합니다.
  • Out 매개 변수인 경우 네이티브 버퍼를 새로 할당된 관리형 배열에 복사합니다.

기본적으로 StringBuilderInOut입니다.

문자열 마샬링에 대한 자세한 내용은 문자열에 대한 기본 마샬링을 참조 하세요.

이 규칙은 위반이 중요한지와 위반을 해결하기 위한 어려운 리팩터링인지 여부에 대한 사례별 분석이 필요할 수 있기 때문에 기본적으로 사용하지 않도록 설정되어 있습니다. 사용자는 심각도를 구성하여 이 규칙을 사용하도록 명시적으로 설정할 수 있습니다.

위반 문제를 해결하는 방법

일반적으로 위반 문제 해결에는 StringBuilder 대신 버퍼를 사용하도록 P/Invoke 및 해당 호출자를 재작업하는 과정이 포함됩니다. 세부 사항은 P/Invoke에 대한 사용 사례에 따라 달라집니다.

다음은 네이티브 함수에서 채울 출력 버퍼로 StringBuilder를 사용하는 일반적인 시나리오 예제입니다.

// Violation
[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern void Foo(StringBuilder sb, ref int length);

public void Bar()
{
    int BufferSize = ...
    StringBuilder sb = new StringBuilder(BufferSize);
    int len = sb.Capacity;
    Foo(sb, ref len);
    string result = sb.ToString();
}

버퍼가 작고 unsafe 코드가 허용되는 사용 사례에서는 stackalloc를 사용하여 스택에서 버퍼를 할당할 수 있습니다

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern unsafe void Foo(char* buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    unsafe
    {
        char* buffer = stackalloc char[BufferSize];
        int len = BufferSize;
        Foo(buffer, ref len);
        string result = new string(buffer);
    }
}

더 큰 버퍼에 대해서는 새 배열을 버퍼로 할당할 수 있습니다.

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern void Foo([Out] char[] buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    char[] buffer = new char[BufferSize];
    int len = buffer.Length;
    Foo(buffer, ref len);
    string result = new string(buffer);
}

더 큰 버퍼에 대해 P/Invoke가 자주 호출되는 경우 ArrayPool<T>을 사용하여 반복되는 할당과 메모리 압력을 방지할 수 있습니다.

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern unsafe void Foo([Out] char[] buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    char[] buffer = ArrayPool<char>.Shared.Rent(BufferSize);
    try
    {
        int len = buffer.Length;
        Foo(buffer, ref len);
        string result = new string(buffer);
    }
    finally
    {
        ArrayPool<char>.Shared.Return(buffer);
    }
}

런타임까지 버퍼 크기를 알 수 없는 경우 stackalloc을 사용하여 큰 버퍼를 할당하지 않으려면 크기에 따라 버퍼를 다르게 만들어야 할 수 있습니다.

위 예제에서는 2바이트 와이드 문자(CharSet.Unicode)를 사용합니다. 네이티브 함수에서 1바이트 문자(CharSet.Ansi)를 사용하는 경우 byte 버퍼를 char 버퍼 대신 사용할 수 있습니다. 예시:

[DllImport("MyLibrary", CharSet = CharSet.Ansi)]
private static extern unsafe void Foo(byte* buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    unsafe
    {
        byte* buffer = stackalloc byte[BufferSize];
        int len = BufferSize;
        Foo(buffer, ref len);
        string result = Marshal.PtrToStringAnsi((IntPtr)buffer);
    }
}

매개 변수가 입력으로도 사용되는 경우 null 종결자가 명시적으로 추가된 문자열 데이터로 버퍼를 채워야 합니다.

경고를 표시하지 않는 경우

마샬링이 성능에 미치는 영향을 염려하지 않는 경우 이 규칙 위반을 StringBuilder표시하지 않습니다.

경고 표시 안 함

단일 위반만 표시하지 않으려면 원본 파일에 전처리기 지시문을 추가하여 규칙을 사용하지 않도록 설정한 후 다시 사용하도록 설정합니다.

#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838

파일, 폴더 또는 프로젝트에 대한 규칙을 사용하지 않도록 설정하려면 구성 파일에서 심각도를 none으로 설정합니다.

[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none

자세한 내용은 방법: 코드 분석 경고 표시 안 함을 참조하세요.

참고 항목