Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)
(시리즈 글이 16개 있습니다.)
.NET Framework: 112. How to Interop DISPPARAMS
; https://www.sysnet.pe.kr/2/0/617

.NET Framework: 137. C#에서 Union 구조체 다루기
; https://www.sysnet.pe.kr/2/0/728

.NET Framework: 141. Win32 Interop - 크기가 정해지지 않은 배열을 C++에서 C#으로 전달하는 경우
; https://www.sysnet.pe.kr/2/0/737

.NET Framework: 168. [in,out] 배열을 C#에서 C/C++로 넘기는 방법
; https://www.sysnet.pe.kr/2/0/810

.NET Framework: 169. [in, out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/811

.NET Framework: 183. 구조체 포인터 인자에 대한 P/Invoke 정의
; https://www.sysnet.pe.kr/2/0/912

.NET Framework: 472. C/C++과 C# 사이의 메모리 할당/해제 방법
; https://www.sysnet.pe.kr/2/0/1784

.NET Framework: 620. C#에서 C/C++ 함수로 콜백 함수를 전달하는 예제 코드
; https://www.sysnet.pe.kr/2/0/11099

.NET Framework: 627. C++로 만든 DLL을 C#에서 사용하기
; https://www.sysnet.pe.kr/2/0/11111

.NET Framework: 686. C# - string 배열을 담은 구조체를 직렬화하는 방법
; https://www.sysnet.pe.kr/2/0/11319

.NET Framework: 757. 포인터 형 매개 변수를 갖는 C++ DLL의 함수를 C#에서 호출하는 방법
; https://www.sysnet.pe.kr/2/0/11533

.NET Framework: 978. C# - GUID 타입 전용의 UnmanagedType.LPStruct
; https://www.sysnet.pe.kr/2/0/12444

C/C++: 158. Visual C++ - IDL 구문 중 "unsigned long"을 인식하지 못하는 #import
; https://www.sysnet.pe.kr/2/0/13128

.NET Framework: 2058. [in,out] 배열을 C#에서 C/C++로 넘기는 방법 - 세 번째 이야기
; https://www.sysnet.pe.kr/2/0/13141

.NET Framework: 2083. C# - C++과의 연동을 위한 구조체의 fixed 배열 필드 사용 (2)
; https://www.sysnet.pe.kr/2/0/13205

닷넷: 2152. Win32 Interop - C/C++ DLL로부터 이중 포인터 버퍼를 C#으로 받는 예제
; https://www.sysnet.pe.kr/2/0/13429





[in, out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기


지난 글에 이어서.

[in, out] 배열을 C#에서 C/C++로 넘기는 방법
; https://www.sysnet.pe.kr/2/0/810

마이크로소프트가 원래 의도한 바는 아니었겠지만, tlbimp.exe의 기능을 보정할 수 있는 방법을 별도로 제공하고 있습니다.

.NET Framework Developer's Guide
- Customizing Runtime Callable Wrappers
; https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/e753eftz(v=vs.100)

위의 글에 포함된 그림이 재미있습니다. ^^

[그림 1: RCW DLL 생성 방법]
how_to_customize_rcw_1.gif

RCW를 생성하는 3가지 방법 중에서 중간 그림이 의미가 있는데, 일단 한번 tlbimp.exe에 의해서 생성된 DLL을 역어셈블한 뒤, 원하는 대로 마샬링 정보를 바꾸고 다시 ilasm을 이용해서 DLL을 생성하고 있습니다. 오호... 이 정도면 훌륭한 대안이죠. ^^




이것을 이용해서 지난번 예제를 개선해 보겠습니다.

IDL은 원래 정의된 그대로 사용하고,

[
	object,
	uuid(1A38076B-3D6D-4F20-8B4D-C72EF6AE1204),
	dual,
	nonextensible,
	helpstring("IMyTest Interface"),
	pointer_default(unique)
]
interface IMyTest : IDispatch
{
	[id(0x3003), helpstring("method PrepareBuf")] 
	HRESULT PrepareBuf([in, out, size_is(bufLength)] __int64 buffer [], [in] int bufLength);
};

마샬링 정보가 올바르진 않겠지만 일단 tlbimp.exe(또는 Visual Studio의 DLL 참조)를 이용하여 interop DLL을 생성합니다.

tlbimp testatl.dll /out:interop.testatl.dll

ildasm으로 DLL을 역어셈블하면 해당 메서드가 다음과 같이 선언된 것을 볼 수 있습니다.

ildasm interop.testatl.dll /out:interop.testatl.il

.method public hidebysig newslot virtual 
      instance void  PrepareBuf([in][out] int64& buffer,
                                 [in] int32 bufLength) runtime managed internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 03 30 00 00 00 00 )   // ...0....
.override interop.testatl.IMyTest::PrepareBuf
} // end of method MyTestClass::PrepareBuf

여기에서 "[in][out] int64& buffer" 구문을 "[in][out] int64[] marshal([]) buffer"과 같이 바꿔줍니다. (또 한 군데 더 정의되어 있기 때문에 그 부분도 마저 바꿔줍니다.) 이렇게 변경하고 다시 ilasm으로 어셈블해주면, C#에서 정상적으로 long []으로 사용할 수 있습니다.

ilasm interop.testatl.il /dll

public virtual void PrepareBuf(long[] buffer, int bufLength);

우와~~~ ^^ 깔끔하죠! (첨부된 파일은 위의 예제와 지난번 글의 예제를 함께 테스트한 프로젝트입니다.)

그러고 보니, 이걸 하면서 예전에 쓴 글이 하나 생각났습니다.

COM 개체의 이벤트를 구독하는 코드 제작
; https://www.sysnet.pe.kr/2/0/589

위의 방법 역시 interop DLL 코드가 잘못 생성된 경우인데, ildasm/ilasm 조합으로 해결할 수도 있었지 않았을까 싶네요. ^^



[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/19/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2009-12-20 01시53분
[김종혁] [id(1), helpstring("method Test")] HRESULT Test([in] double* arr);

위 COM 함수를 tlbimp 를 통해 il 로 변환하면 float64& 로 변환되죠.

1. il 변환없이 C# 에서 아래와 같은 코드를 작성하고 테스트를 하면,

double[] dValues = { 1.1, 2.2, 3.3 };
objCOM.Test(ref dValues[0]);

.NET 1.1 에서는 정상 출력됩니다. 그러나 2.0, 3.0 에서는 첫번째 값만 제대로 출력이 됩니다.

2. il 을 Test([in] float64[] marshal([]) arr) 로 변환 후 아래와 같이 실행을 해도

objCOM.Test(dValues);

.NET 1.1 에서만 정상 출력될 뿐 결과는 동일합니다.

3. double* 는 4바이트 정수형 타입으로 처리될 것이기에,

il 을 Test([in] int32 arr) 로 변환 후 아래와 같이 실행을 하면

IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(dValues, 0);
objCOM.Test(ptr.ToInt32());

결과는 동일합니다. 어떤 테스트를 해도 오직 .NET 1.1 에서만 정상입니다.


제 PC에는 VS 6.0, 2003, 2005, 2008 이 설치되어 있습니다.

혹시 어떤 문제인지 아신다면 염치불구하고 도움 부탁드립니다.
[guest]
2009-12-20 02시02분
[김종혁] 댓글이 삭제/수정이 안되네요, ㅎㅎ

시삽님 코드를 2008 에서 실행하면 정상입니다. 그러나 2005로 실행하면 PrepareBuf1 는 모두 0 으로 출력됩니다. 동일 문제 같습니다만,,
[guest]
2009-12-20 03시34분
[김종혁] 정정합니다. 2008에서는 정상 작동하네요. 2008의 Target 이 .NET Framework 2.0 으로 설정되어 있는데, 결국 2005와 차이가 없어야 할텐데요.
[guest]
2009-12-20 10시18분
문제를 정리해보겠습니다. ^^

1. 이 글에 첨부된 Interop.zip을 다운로드 받는다.
2. .\TestATL\Debug\TestATL.dll을 regsvr32로 레지스트리에 등록한다.
3. Visual Studio 2005에서 C# 프로젝트를 만든다.
4. .\ConsoleApplication1\ConsoleApplication1\bin\Debug\interop.testatl.dll을 참조한다.
5. 새로 만든 프로젝트의 Program.cs에 .\ConsoleApplication1\ConsoleApplication1\Program.cs에 준하는 코드를 작성하고 빌드한다.

위와 같이 했는데, 결과는 오동작이라는 것인가요? 하지만, 3번 단계의 작업을 Visual Studio 2008로 하면 정상동작한다는 것이고?

맞나요? ^^
kevin25
2009-12-20 11시07분
[김종혁] 제가 너무 두서 없었죠? 죄송합니다.

시삽님께서 작성하신 내용은 제가 현재 진행중인 프로젝트 때문에 전부터 고민하던 문제였고, MSDN 에서 동일한 내용의 글을 참고하여 테스트를 해보았으나 2005 에서만 실패를 했던 경우입니다.

복잡도를 줄이기 위해 아주 간단히 [in] double* 를 인자로 받아 그대로 출력하는 COM을 C++로 작성을 하고 2003, 2005, 2008 에서 테스트를 진행하면 유독 2005 에서만 실패를 합니다.

잘 아시다시피, COM 에 C#의 배열을 전달하는 방법은 시삽님의 글과 제 댓글처럼 몇 가지 있을 수가 있는데요. 2005에서만 실패하는 이유가 뭘까요. 첫번째 인덱스의 값만 정상 출력이 되고 나머지 값은 Gabage 가 출력됩니다.

질문하셨던 것처럼 시삽님의 소스도 마찬가지 현상이 발생하였습니다. 2008은 정상 출력이 되나, 2005에서는 전부 0 이 출력이 됩니다. COM 에서 대입한 값이 마샬링 과정에서 누락되는 듯 합니다.

2008의 환경은 .NET Framework 2.0 로 설정되어 있으며, 이는 런타임 환경은 .NET Framework 2.0 을 사용한다는 얘기인데, 결국 차이는 컴파일러 뿐입니다. 2005를 주로 사용하기에 모르고 있었던 사실인데 2008에서 Target을 2.0 으로 두더라도 컴파일러는 3.5 버전의 csc.exe 를 사용하더군요.

주말 동안 구글에서 정신없었는데, 제 문제에 흥미를 가져주신 것만으로도 정말 감사합니다.
[guest]
2009-12-21 09시53분
현재 이 글에 첨부 파일로 "Interop2005.zip"을 추가했습니다.
그걸 다운로드 한 후, Interop2005.sln 파일을 VS2005에서 열고, TestATL 프로젝트를 빌드한 다음 ConsoleApplication1.exe를 실행해 보세요.

제가 해보니, Visual Studio 2005에서도 정상적으로 동작했습니다.

참고로, IL 코드 변환한 DLL은 .\bin\debug\interop.testatl.dll로 미리 생성해 두었고 현재의 ConsoleApplication1 프로젝트에서는 그 DLL을 참조하도록 지정되어 있습니다.
kevin25
2009-12-21 08시41분
[김종혁] 정답을 찾았습니다. 시삽님의 2005 프로젝트를 그대로 실행하면 성공, 제가 만든 프로젝트로 실행하면 오류.

소스를 비교하던 중, [STAThread] 누락 발견 !!!

2005와 2008 콘솔 애플리케이션에는 [STAThread] 를 자동으로 추가하지 않는다는 것을 생각 못했네요.

In the .NET Framework version 2.0, new threads are initialized as ApartmentState..::.MTA if their apartment state has not been set before they are started. The main application thread is initialized to ApartmentState..::.MTA by default. (MSDN 발췌)

2008에서 되었다 안되었다했던 이유도 명확해 지네요. 윈도우 애플리케이션으로 테스트를 했으면 이렇게 고민하지 않았을텐데 말이죠. 그래도 의문인 것은 왜 배열형태의 케이스에서만 위와문제가 발생하는 걸까요,

시삽님 코드 아니였으면 오늘도 방황하고 있었을 겁니다. 다시 한번 감사의 말씀 드립니다.
[guest]
2009-12-21 09시22분
STA가 아닌 경우에도 정상적으로 마샬링을 하고 싶다면, [프로젝트명]PS.csproj를 빌드해서 산출된 dll을 등록해 주시면 됩니다. 또는 ATL COM 개체 생성시에 아예 Merge Proxy인가 하는 옵션을 선택해주시는 것도 좋은 방법이고.
kevin25

... 121  122  123  [124]  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10822정성태7/7/201527265오류 유형: 299. The 'Visual C++ Project System Package' package did not load correctly.
10821정성태7/7/201519525오류 유형: 298. Unable to start debugging on the web server. IIS does not list a web site that matches the launched URL.
10820정성태7/7/201525465오류 유형: 297. HTTP Error 503. The service is unavailable. - 두 번째
10819정성태7/2/201528923오류 유형: 296. SQL Server Express 시작 오류 - error code 3417
10818정성태7/1/201527737오류 유형: 295. HTTP Error 503. The service is unavailable. [1]
10817정성태6/29/201531435.NET Framework: 523. C# 람다(Lambda)에서 변수 캡처 방식 [3]
10816정성태6/25/201527778.NET Framework: 522. 닷넷의 어셈블리 서명 데이터 확인 방법파일 다운로드1
10815정성태6/23/201525611Graphics: 1. 자네 나와 함께... UNITY 하지 않겠는가! [4]
10814정성태6/22/201523370.NET Framework: 521. Roslyn을 이용해 C# 문법 변형하기 (2) [5]
10813정성태6/21/201525165.NET Framework: 520. Roslyn을 이용해 C# 문법 변형하기 (1)
10812정성태6/20/201526299.NET Framework: 519. C# 6.0 오픈 소스 컴파일러 Roslyn - 빌드 및 테스트 방법 [1]
10811정성태6/20/201523166오류 유형: 294. OpenAuth 사용 시 System.Data.SqlClient.SqlException 예외가 Output 창에 출력되는 문제
10810정성태6/18/201522147개발 환경 구성: 270. Visual Studio에서 github 오픈 소스를 fork해서 테스트하는 방법 [1]
10809정성태6/18/201520042.NET Framework: 518. AllowPartiallyTrustedCallers 특성이 적용된 GAC 어셈블리에서 DynamicMethod의 calli 명령어 사용파일 다운로드1
10808정성태6/17/201522439.NET Framework: 517. calli IL 호출이 DllImport 호출보다 빠를까요? [1]파일 다운로드1
10807정성태6/16/201523511.NET Framework: 516. Microsoft.AspNet.Membership.OpenAuth 사용 시 "Local Database Runtime error occurred" 오류
10806정성태6/16/201541390.NET Framework: 515. OpenAuth.VerifyAuthentication 호출 시 The remote server returned an error: (400) Bad Request
10805정성태6/15/201522670Java: 17. 자바의 재미있는 상수 처리 방식
10804정성태6/10/201522338.NET Framework: 514. .NET CLR2 보안 모델에서의 APTCA 역할 (2)파일 다운로드1
10803정성태6/2/201524261.NET Framework: 513. UWP(Universal Windows Platform) 응용 프로그램의 새로운 라이브러리 버전 관리 해법 [2]
10802정성태6/2/201524363개발 환경 구성: 269. 마이크로소프트 온라인 강좌 소개 - Azure VPN 구성 방법 [1]
10801정성태5/31/201528914.NET Framework: 512. async/await 사용 시 hang 문제가 발생하는 경우 - 두 번째 이야기 [3]
10800정성태5/29/201524361개발 환경 구성: 268. 소개 - 프로세싱(https://processing.org/)
10799정성태5/29/201522140사물인터넷: 3. 책 소개 - 라즈베리 파이로 구현하는 사물 인터넷 프로젝트 [1]
10798정성태5/26/201521857기타: 53. 2015년 6월 10일 밤 10시 온라인 세미나 - 새로운 Windows 10 App을 개발하는 방법
10797정성태5/23/201521540VC++: 91. 자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드파일 다운로드1
... 121  122  123  [124]  125  126  127  128  129  130  131  132  133  134  135  ...