2012-03-12 1 views
2

저는 Windows Forms 컨트롤을 다루는 C# 코드를 만들고 있습니다..NET에서 런타임에 C# 코드를 늦추기위한 가장 침해적인 방법은 무엇입니까?

public class GUIObject { 
    protected Control m_control; 

    // [..] 

    public virtual Rectangle Bounds { 
     get { 
      Rectangle r = m_control.Bounds; 
      if (m_control.Parent != null) { 
       return m_control.Parent.RectangleToScreen(r); 
      } 
      return r; 
     } 
    } 
} 

이 코드는에 "플러그인"으로 배포되는 라이브러리로 컴파일이 고객으로로드 할 : 여기에 작은 예를 들어, 일부 제어 (화면 좌표) 경계 사각형을 얻기를위한 작은 래퍼입니다 응용 프로그램. 그러나 일부 고객은 플러그인에서 링크 된 것과 다른 버전의 Windows Forms를 응용 프로그램에서 사용하는 것으로 나타났습니다. 내 계획은 위의 코드를 늦게 처리하여이 문제를 해결하여 현재 응용 프로그램 도메인에로드 된 Windows Forms 버전과 함께 작동하도록했습니다. .NET 4를 사용하면 dynamic 키워드를 사용할 수 있지만 슬프게도이 코드는 .NET3 응용 프로그램에서도 작동합니다.

public class LateBoundObject { 
    private Object m_o; 

    // [..] 

    public Object GetProperty(String name) { 
     PropertyInfo pi = m_o.GetType().GetProperty(name); 
     return pi == null ? null 
          : pi.GetValue(m_o, null); 
    } 

    public Object InvokeMethod(String name, Object[] args) { 
     MethodInfo mi = m_o.GetType().GetMethod(name); 
     return mi == null ? null 
          : mi.Invoke(m_o, args); 
    } 
} 

public class GUIObject { 
    protected LateBoundObject m_control; 

    // [..] 

    public virtual Rectangle Bounds { 
     get { 
      Object r = m_control.GetProperty("Bounds"); 
      if (r == null) { 
       return new Rectangle(); 
      } 

      Object parent = m_control.GetProperty("Parent"); 
      if (parent != null) { 
       LateBoundObject po = new LateBoundObject(parent); 
       r = po.InvokeMethod("RectangleToScreen", 
            new Object[] { r }); 
      } 
      return (Rectangle)r; 
     } 
    } 
} 

하지 아주 예쁜 : 따라서, 나는 반사 API 조금 더 좋은를 사용하여 만드는 작은 도우미 개체를 도입, 반사 API를 사용하기 시작. 호출자 측에서 많은 캐스팅이 필요하며 조만간 오버로드 된 메서드 나 속성을 조만간 처리해야 할 것으로 의심됩니다. 이상적으로, 래퍼 객체는 원본 코드를 거의 동일하게 유지할 수 있습니다.

그래서 LateBoundObject 래퍼 클래스를 수정하기 전에 다른 사용자가 리플렉션 API를 사용하여 후기 바인딩 된 C# 코드를 작성한 경험이 있습니까? 그렇다면 raw reflection API 사용의 고통을 최소화하기 위해 어떻게 접근 했습니까? LateBoundObject 라인을 따라 래퍼 클래스를 사용 했습니까? 아니면 전혀 다른 경로를 사용 했습니까? 나는 원래 코드에 관한 한 최소한 침입 방법을 찾고있다.

+0

얼마나 많은 'WinForms 버전'이 있습니까? 나는 당신이 Fx2.0을위한 lib를 대상으로하고 Fx1과 Fx4를 위해 별도의 라이브러리를 만들어야한다고 생각한다. –

+0

@HenkHolterman : 사실, Windows Forms의 경우 플러그인을 여러 번 빌드하면 충분할 것이다.그러나 다른 도구 키트 (WPF 및 '확장 WPF'와 같은 많은 타사 도구 키트)에서 동일한 문제가 발생합니다. 따라서 필자의 코드를 늦게 작성함으로써 여러 번 모든 것을 빌드하는 것을 피할 수 있는지 궁금하다. –

+0

늦은 바운드 Fx1.1 코드로 Fx4 사용자에게 부담을주고 싶습니까? –

답변

1

하나의 아이디어는 다음 실제 인스턴스를 coerece 수있는 클래스를 생성 할 System.Reflection.Emit을 사용하여 객체의 모양을 원하는에 대한 인터페이스를 생성하는 것입니다. 인터페이스 메서드에서 호출하는 실제 인스턴스로 프록시를 호출하는 동적으로 생성 된 객체에 배치하여이 작업을 수행 할 수 있습니다.

사용법은 다음과 같이 보일 것입니다 :

interface IGUIObject 
{ 
    Rectangle Bounds { get; } 
    Rectangle RectangleToScreen(Rectangle bounds); 
    IGUIObject Parent { get; } 
} 

var obj = GetInstance(); 
var proxy = Reflection.Coerce<IGUIObject>(obj); 
return proxy.Parent.RectangleToScreen(proxy.Bounds); 

을 내가 샘플 응용 프로그램을 포함하여, 동적 강제 작업을 수행하는 방법에 대한 간단한 출발점으로 여기에 블로그 게시물이 있습니다 coercing types and unloading assemblies

흥미로운 것은에 있다는 것입니다을 이 기법을 사용하면 실제로 호출 당 반사를 제거 할 수 있습니다. 이는 매우 비싼 성능입니다. 대신 당신은 프록시 생성기에서 한 번 반영을 수행하고 실제로 생성 한 것은 그 후에 직접 해당 속성/메소드/필드를 호출합니다. 또한이 트릭을 사용하면 프록시 인스턴스에 대한 참조를 삭제할 때 생성 된 동적 어셈블리가 언로드됩니다. 후속 프록시 생성을 매우 빠르게하기 위해 유형 생성 유형을 캐시 할 수 있습니다.

귀하의 상황은 내 작은 샘플보다 복잡하지만 시작 지점으로 매우 멀어 질 수 있다고 생각합니다.

+0

매우 흥미 롭습니다! 나는 확실히 보일 것이다. 나는 Emit 시설에 대해 끈적 거리는 소리를 계속 들었고, 나는 그것이 도움이 될지도 모른다고 생각했다. 포인터 주셔서 감사합니다! –

1

나는 그것을 얻지 못한다. .NET 4 컨트롤을 .NET 2에 대해 컴파일 된 dll에 전달하면 제대로 작동합니다. 반성

+0

문제는 고객 응용 프로그램 도메인에 동적으로로드 된 내 어셈블리가 자신이 사용하는 것보다 다른 버전의 타사 어셈블리와 연결된다는 것입니다. 그래서 나는 그가로드 한 버전을 사용하고 싶습니다. 플러그인이 만들어졌습니다. –

+0

귀하의 경우에는로드 할 때 (플러그인을로드하고 결과 어셈블리를로드 할 때 csc.exe를 호출하여) 컴파일하는 것을 선호합니다. – Joshua

1

사용 도우미 확장 :

var r = m_control._P<Rectangle>("Bounds") ?? new Rectangle(); 
var parent = m_control._P<Control>("Parent"); 
if (parent != null) 
    r = parent._M<Rectangle>("RectangleToScreen", r); 



static public class ReflectionHlp2 
{ 
    public static T _P<T>(this object item, string name) 
    { 
    if (item == null) 
     return default(T); 
    var type = item.GetType(); 

    var members = type.GetMembers(BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(_member => _member.Name == name) 
     .ToArray(); 
    if (members.Length == 0) 
     return default(T); 
    if (members.Length > 1) 
     throw new Exception(string.Format("У объекта полей/свойств с именем '{0}' больше чем один: '{1}'", name, members.Length)); 
    var member = members.First(); 
    object result; 
    if (member is FieldInfo) 
     result = ((FieldInfo)member).GetValue(item); 
    else 
     result = ((PropertyInfo)member).GetValue(item, null); 
    if (result is T) 
     return (T)result; 
    return default(T); 
    } 
    public static void _P<T>(this object item, string name, T value) 
    { 
    if (item == null) 
     return; 
    var type = item.GetType(); 

    var members = type.GetMembers(BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(_member => _member.Name == name) 
     .ToArray(); 
    if (members.Length == 0) 
     return; 
    if (members.Length > 1) 
     throw new Exception(string.Format("У объекта полей/свойств с именем '{0}' больше чем один: '{1}'", name, members.Length)); 
    var member = members.First(); 
    if (member is FieldInfo) 
     ((FieldInfo)member).SetValue(item, value); 
    else 
     ((PropertyInfo)member).SetValue(item, value, null); 
    } 
    public static void _M(this object item, string name, params object[] args) 
    { 
    _M<object>(item, name, args); 
    } 
    public static T _M<T>(this object item, string name, params object[] args) 
    { 
    if (item == null) 
     return default(T); 
    var type = item.GetType(); 

    var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(_member => _member.Name == name) 
     .ToArray(); 
    if (methods.Length == 0) 
     return default(T); 
    if (methods.Length > 1) 
     throw new Exception(string.Format("Вызов перегруженных методов не поддерживается, у объекта методов с именем '{0}' больше чем один: '{1}'.", name, methods.Length)); 
    var method = methods.First(); 
    var result = method.Invoke(item, args); 
    if (result is T) 
     return (T)result; 
    return default(T); 
    } 
} 
+0

키릴 문자 예외 메시지 +1. ;-) –

관련 문제