2013-08-20 2 views
1

// --------------------------- C# 코드 -------- ----------------------SysFreeString() 호출시 힙 손상 오류

[DllImport("MarshallStringsWin32.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
    extern static void PassStringOut([MarshalAs(UnmanagedType.BStr)] out String str); 

    [DllImport("MarshallStringsWin32.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
    extern static void FreeString([MarshalAs(UnmanagedType.BStr)] String str); 

    static void Main(string[] args) 
    { 
     String str; 
     PassStringOut(out str); 
     FreeString(str); 
    } 

// ------------------- -------- C + 코드 ------------------------------

void PassStringOut(__out BSTR* str) 
{ 
    const std::string stdStr = "The quick brown fox jumps over the lazy dog"; 
    _bstr_t bstrStr = stdStr.c_str(); 
    *str = bstrStr.copy(); 
} 

void FreeString(BSTR str) 
{ 
    SysFreeString(str); 
} 

PassStringOut() 및 FreeString()에서 'str'포인터가 다르며 SysFreeString()을 호출 할 때 힙 손상 오류가 발생합니다. FreeString()에 대한 참조로 'str'을 전달해야합니까? 그렇다면 C# 및 C++에서 사용해야하는 구문은 무엇입니까?

답변

6

마샬링 계층은 복사본을 관리되는 메모리에 할당합니다. 그 사본은 가비지 컬렉터에 의해 해제 될 것입니다. 사실 String은 C#에서 SysFreeString 일 필요가 없습니다. 실제로 시도하면 힙을 손상시킬 수있는 좋은 방법입니다.

문자열에 2 개의 복사본이 있어야합니까? *str = bstrStr.copy(); 그리고 마샬링 레이어에 의해?

여기서 어떤 일이 일어나는지 자세히 설명해 드리겠습니다.

Main 메서드는 String 유형의 로컬 변수의 관리되는 주소를 전달하여 비 관리 코드를 호출합니다. 마샬링 계층은 적절한 크기의 자체 저장소를 만들어 BSTR을 보유하고 해당 저장소에 대한 참조를 관리되지 않는 코드에 전달합니다.

관리되지 않는 코드가 할당 BSTR 그리고, 문자와 연관된 스토리지를 참조하는 string 객체를 할당하고, 원래의 문자열로의 첫 번째 복사본을 만드는 힙을 할당 BSTR. 그런 다음 BSTR의 두 번째 복사본을 만들고 out 매개 변수에 해당 저장소에 대한 참조를 채 웁니다. bstrStr 개체가 범위를 벗어나 소멸자가 원본 BSTR을 해제합니다.

마샬링 계층은 적절한 크기의 관리되는 문자열을 만들어 세 번째 문자열을 복사합니다. 그런 다음 전달 된 BSTR을 해제합니다. 컨트롤이 이제 관리되는 문자열을 가진 C# 코드로 돌아갑니다.

해당 문자열은 FreeString에게 전달됩니다. 마샬링 계층은 BSTR을 할당하고 네 번째 시간은 문자열의 복사본을 BSTR으로 만들고 비 관리 코드에 전달합니다. 그런 다음 소유하지 않고 반환하는 BSTR을 해제합니다. 마샬링 계층은 할당 된 BSTR을 해제하고 힙을 손상시킵니다.

관리되는 힙은 손상되지 않습니다. 가비지 수집가가 선택할 때 관리 문자열은 가비지 수집기에 의해 해제됩니다.

FreeString()을 참조하여 'str'을 전달해야합니까? 당신이 직장을 정렬 화의 방법을 모든 측면의 철저한깊은 이해가 될 때까지

호 오히려, 당신은 상호 운용성 코드를 작성 중지해야합니다.

전문가가 올바른 정보를 얻을 때도 관리 코드와 비 관리 코드를 마샬링하는 것이 어렵습니다. 제 조언은 여러분이 한 걸음 더 나아가서 interop 코드를 안전하게 작성하고 올바르게 작성하는 방법을 가르쳐 줄 수있는 전문가의 서비스를 얻는 것입니다.

+0

문자열에 대해 2 개의 복사본이 생성됩니다. "* str = bstrStr.copy();" 그리고 마샬링 층에 의해? –

+0

@Stephenosella : 원래 문자열로 만들어진 복사본이 4 개 있습니다. 이러한 복사본 중 세 개는 BSTR로, 한 개는 관리되는 String으로 변환됩니다. 마지막 복사본이 두 번 해제되어 힙이 손상됩니다. 자세한 내용은 내 대답을 참조하십시오. –

2

이것이 작동한다고 생각하는 방식대로 작동하지 않습니다. pinvoke marshaller는 이미 BSTR을 자동으로 릴리스했습니다. 그것은 PassStringOut()을 호출 할 때 발생했습니다. 마샬 러는이를 System.String으로 변환하고 BSTR을 릴리스했습니다. 이것은 네이티브 코드와 관리 코드간에 BSTR을 전달하기위한 정상적이고 필요한 프로토콜입니다.() FreeString의 잘못은 무엇

새로운 BSTR이 PInvoke를 마샬에 의해 할당있어 것입니다. 그리고 은 두번으로 발표되었습니다. 먼저 네이티브 코드에 의해, 다시 pinvoke marshaller에 의해. 디버거가 부착 된 코드를 실행할 때 사용되는 디버그 힙의 Kaboom.

너무 많은 도움을 주므로 FreeString()을 호출하지 마십시오. 그들은 기존의 C 코드에서 아주 일반적이기 때문에


당신은 당신을 위해 ANSI 문자열을 처리 할 수있는 PInvoke를 마샬를 얻을 수 있습니다, 그것은 사실 기본 동작입니다. 귀하의 C++ 함수는 다음과 같이 수 :

extern "C" __declspec(dllexport) 
void __stdcall PassStringOut(char* buffer, size_t bufferLen) 
{ 
    const std::string stdStr = "The quick brown fox jumps over the lazy dog"; 
    strcpy_s(buffer, bufferLen, stdStr.c_str()); 
} 

일치하는 C# 코드와 함께 : 버퍼의 적절한 크기를 추측하는 데

class Program { 
    static void Main(string[] args) { 
     var buffer = new StringBuilder(666); 
     PassStringOut(buffer, buffer.Capacity); 
     Console.WriteLine(buffer.ToString()); 
     Console.ReadLine(); 
    } 
    [DllImport("Example.dll")] 
    private static extern bool PassStringOut(StringBuilder buffer, int capacity); 
} 

는 그러나 못 생기고 세부입니다.

+0

확인. 이해 했어. 궁극적으로, 내가 필요로하는 것은 C++의 std :: string에서 C++의 System.String으로 이동하는 것입니다. 나는 필요한 std :: string의 크기를 미리 알지 못한다. 나는 이전에 이것을 게시했다. BSTR을 매체로 사용하는 것이 가장 우아 해 보였습니다. 비록 오버 헤드로 인해 특히 좋아하지 않는 _bstr_t를 만들어야했습니다. 내가 뭘 사용하려고 최선의 접근 방식은 무엇입니까? –

+0

BSTR에 아무런 문제가 없으며, pinvoke marshaller가 많이 좋아합니다. _bstr_t에 문제가 없어도 적어도 std :: string을 유니 코드로 변환하는 데 도움이됩니다. 그것을 사용하고 싶지 않다면 MultiByteToWideChar()가 유니 코드로 가고 SysAllocString()이 BSTR을 얻기 위해 필요합니다. _bstr_t도 마찬가지입니다. 변환으로 인해 오버 헤드가 항상 발생합니다. std :: string은 이전 세기의 문자열 유형입니다. BSTR 중간 사람을 잘라내려면 즉시 MultiByteToWideChar()에 전달하는 초기화 된 StringBuilder를 전달하면 함수는 wchar_t *를 가져와야합니다. –

+0

사실, (C++에서) 인터페이스하는 백엔드 코드는 엄격하게 ANSI입니다. 따라서, 내가 시뮬레이션 한 백엔드 코드에서 얻을 std :: string은 ANSI가 될 것이다. 따라서 실제로 문자열을 C#으로 표현하는 방법 이외의 다른 문자열을 유니 코드로 가져올 필요는 없습니다. 이것을 감안할 때 BSTR과 _bstr_t를 여전히 최선의 방법으로 사용하고 있습니까? –